diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..23108d9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,83 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive", + "files.associations": { + "xstring": "cpp", + "algorithm": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "coroutine": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "format": "cpp", + "forward_list": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "regex": "cpp", + "set": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "variant": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "codecvt": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 9f31676..e1ad644 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ LBJ Console 是一款应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](ht - 监控指定列车的轨迹,在地图上显示。 - 在地图上显示预警消息的 GPS 信息。 - 基于内置数据文件显示机车配属,机车类型和车次类型。 +- [WIP] 从 RTL-TCP 获取数据。 [android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。 @@ -21,11 +22,17 @@ LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机 # 计划实现的功能 -- [ ] 集成 ESP-Touch 协议,实现设备 WiFi 凭证的配置。 -- [ ] 从设备端拉取历史数据记录。 -- [ ] 从 RTL-TCP 解析数据。 +- 集成 ESP-Touch 协议,实现设备 WiFi 凭证的配置。 +- 从设备端拉取历史数据记录。 + +# 致谢 + +本项目的 RTL-TCP 解析功能以 [RailwayPagerDemod](https://github.com/Arch-Jason/RailwayPagerDemod) 为基础,并移植了 [SX1276_Receive_LBJ](https://github.com/FLN1021/SX1276_Receive_LBJ) 的部分解析逻辑。 + +感谢以上项目作者的贡献。 # 许可证 该项目采用 GNU 通用公共许可证 v3.0(GPLv3)授权。 + diff --git a/android/app/build.gradle b/android/app/build.gradle index edfdc22..7c061f9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -72,6 +72,12 @@ android { universalApk true } } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version "3.22.1" + } + } } dependencies { diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..ebef049 --- /dev/null +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,22 @@ +# android/app/src/main/cpp/CMakeLists.txt + +cmake_minimum_required(VERSION 3.22.1) +project("railwaypagerdemod") + +add_library(${CMAKE_PROJECT_NAME} SHARED + demod.cpp + demod.h + native-lib.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dsp/firfilter.cpp + ) + +target_link_libraries(${CMAKE_PROJECT_NAME} + android + log + m) + +add_definitions(-DSDRBASE_API=) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/fcdlib +) \ No newline at end of file diff --git a/android/app/src/main/cpp/demod.cpp b/android/app/src/main/cpp/demod.cpp new file mode 100644 index 0000000..9dffcde --- /dev/null +++ b/android/app/src/main/cpp/demod.cpp @@ -0,0 +1,410 @@ +#include "demod.h" + +bool is_message_ready = false; + +PhaseDiscriminators phaseDiscri; +Lowpass lowpassBaud; +MovingAverageUtil preambleMovingAverage; + +bool got_SC = false; +double dc_offset = 0.0; +bool prev_data, bit_inverted, data_bit; +int sync_cnt, bit_cnt = 0, word_cnt = 0; +uint32_t bits; +uint32_t code_words[PAGERDEMOD_BATCH_WORDS]; +bool code_words_bch_error[PAGERDEMOD_BATCH_WORDS]; + +std::string numeric_msg, alpha_msg; +int function_bits; +uint32_t address; +uint32_t alpha_bit_buffer; // Bit buffer to 7-bit chars spread across codewords +int alpha_bit_buffer_bits; // Count of bits in alpha_bit_buffer +int parity_errors; // Count of parity errors in current message +int bch_errors; // Count of BCH errors in current message +int batch_num; // Count of batches in current transmission + +double magsqRaw; + +int pop_cnt(uint32_t cw) +{ + int cnt = 0; + for (int i = 0; i < 32; i++) + { + cnt += cw & 1; + cw = cw >> 1; + } + return cnt; +} + +uint32_t bchEncode(const uint32_t cw) +{ + uint32_t bit = 0; + uint32_t localCW = cw & 0xFFFFF800; // Mask off BCH parity and even parity bits + uint32_t cwE = localCW; + + // Calculate BCH bits + for (bit = 1; bit <= 21; bit++) + { + if (cwE & 0x80000000) + { + cwE ^= 0xED200000; + } + cwE <<= 1; + } + localCW |= (cwE >> 21); + + return localCW; +} + +// Use BCH decoding to try to fix any bit errors +// Returns true if able to be decode/repair successful +// See: https://www.eevblog.com/forum/microcontrollers/practical-guides-to-bch-fec/ +bool bchDecode(const uint32_t cw, uint32_t &correctedCW) +{ + // Calculate syndrome + // We do this by recalculating the BCH parity bits and XORing them against the received ones + uint32_t syndrome = ((bchEncode(cw) ^ cw) >> 1) & 0x3FF; + + if (syndrome == 0) + { + // Syndrome of zero indicates no repair required + correctedCW = cw; + return true; + } + + // Meggitt decoder + + uint32_t result = 0; + uint32_t damagedCW = cw; + + // Calculate BCH bits + for (uint32_t xbit = 0; xbit < 31; xbit++) + { + // Produce the next corrected bit in the high bit of the result + result <<= 1; + if ((syndrome == 0x3B4) || // 0x3B4: Syndrome when a single error is detected in the MSB + (syndrome == 0x26E) || // 0x26E: Two adjacent errors + (syndrome == 0x359) || // 0x359: Two errors, one OK bit between + (syndrome == 0x076) || // 0x076: Two errors, two OK bits between + (syndrome == 0x255) || // 0x255: Two errors, three OK bits between + (syndrome == 0x0F0) || // 0x0F0: Two errors, four OK bits between + (syndrome == 0x216) || + (syndrome == 0x365) || + (syndrome == 0x068) || + (syndrome == 0x25A) || + (syndrome == 0x343) || + (syndrome == 0x07B) || + (syndrome == 0x1E7) || + (syndrome == 0x129) || + (syndrome == 0x14E) || + (syndrome == 0x2C9) || + (syndrome == 0x0BE) || + (syndrome == 0x231) || + (syndrome == 0x0C2) || + (syndrome == 0x20F) || + (syndrome == 0x0DD) || + (syndrome == 0x1B4) || + (syndrome == 0x2B4) || + (syndrome == 0x334) || + (syndrome == 0x3F4) || + (syndrome == 0x394) || + (syndrome == 0x3A4) || + (syndrome == 0x3BC) || + (syndrome == 0x3B0) || + (syndrome == 0x3B6) || + (syndrome == 0x3B5)) + { + // Syndrome matches an error in the MSB + // Correct that error and adjust the syndrome to account for it + syndrome ^= 0x3B4; + result |= (~damagedCW & 0x80000000) >> 30; + } + else + { + // No error + result |= (damagedCW & 0x80000000) >> 30; + } + damagedCW <<= 1; + + // Handle syndrome shift register feedback + if (syndrome & 0x200) + { + syndrome <<= 1; + syndrome ^= 0x769; // 0x769 = POCSAG generator polynomial -- x^10 + x^9 + x^8 + x^6 + x^5 + x^3 + 1 + } + else + { + syndrome <<= 1; + } + // Mask off bits which fall off the end of the syndrome shift register + syndrome &= 0x3FF; + } + + // Check if error correction was successful + if (syndrome != 0) + { + // Syndrome nonzero at end indicates uncorrectable errors + correctedCW = cw; + return false; + } + + correctedCW = result; + return true; +} + +int xorBits(uint32_t word, int firstBit, int lastBit) +{ + int x = 0; + for (int i = firstBit; i <= lastBit; i++) + { + x ^= (word >> i) & 1; + } + return x; +} + +// Check for even parity +bool evenParity(uint32_t word, int firstBit, int lastBit, int parityBit) +{ + return xorBits(word, firstBit, lastBit) == parityBit; +} + +// Reverse order of bits +uint32_t reverse(uint32_t x) +{ + x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1)); + x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2)); + x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4)); + x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8)); + return ((x >> 16) | (x << 16)); +} + +// Decode a batch of codewords to addresses and messages +// Messages may be spreadout over multiple batches +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-1-198607-S!!PDF-E.pdf +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf +void decodeBatch() +{ + int i = 1; + for (int frame = 0; frame < PAGERDEMOD_FRAMES_PER_BATCH; frame++) + { + for (int word = 0; word < PAGERDEMOD_CODEWORDS_PER_FRAME; word++) + { + bool addressCodeWord = ((code_words[i] >> 31) & 1) == 0; + + // Check parity bit + bool parityError = !evenParity(code_words[i], 1, 31, code_words[i] & 0x1); + + if (code_words[i] == PAGERDEMOD_POCSAG_IDLECODE) + { + // Idle + } + else if (addressCodeWord) + { + // Address + function_bits = (code_words[i] >> 11) & 0x3; + int addressBits = (code_words[i] >> 13) & 0x3ffff; + address = (addressBits << 3) | frame; + numeric_msg = ""; + alpha_msg = ""; + alpha_bit_buffer_bits = 0; + alpha_bit_buffer = 0; + parity_errors = parityError ? 1 : 0; + bch_errors = code_words_bch_error[i] ? 1 : 0; + } + else + { + // Message - decode as both numeric and ASCII - not all operators use functionBits to indidcate encoding + int messageBits = (code_words[i] >> 11) & 0xfffff; + if (parityError) + { + parity_errors++; + } + if (code_words_bch_error[i]) + { + bch_errors++; + } + + // Numeric format + for (int j = 16; j >= 0; j -= 4) + { + uint32_t numericBits = (messageBits >> j) & 0xf; + numericBits = reverse(numericBits) >> (32 - 4); + // Spec has 0xa as 'spare', but other decoders treat is as . + const char numericChars[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'U', ' ', '-', ')', '('}; + char numericChar = numericChars[numericBits]; + numeric_msg.push_back(numericChar); + } + + // 7-bit ASCII alpnanumeric format + alpha_bit_buffer = (alpha_bit_buffer << 20) | messageBits; + alpha_bit_buffer_bits += 20; + while (alpha_bit_buffer_bits >= 7) + { + // Extract next 7-bit character from bit buffer + char c = (alpha_bit_buffer >> (alpha_bit_buffer_bits - 7)) & 0x7f; + // Reverse bit ordering + c = reverse(c) >> (32 - 7); + // Add to received message string (excluding, null, end of text, end ot transmission) + if (c != 0 && c != 0x3 && c != 0x4) + { + alpha_msg.push_back(c); + } + // Remove from bit buffer + alpha_bit_buffer_bits -= 7; + if (alpha_bit_buffer_bits == 0) + { + alpha_bit_buffer = 0; + } + else + { + alpha_bit_buffer &= (1 << alpha_bit_buffer_bits) - 1; + } + } + } + + // Move to next codeword + i++; + } + } +} + +void processOneSample(int8_t i, int8_t q) +{ + float fi = ((float)i) / 128.0f; + float fq = ((float)q) / 128.0f; + + std::complex iq(fi, fq); + + float deviation; + double fmDemod = phaseDiscri.phaseDiscriminatorDelta(iq, magsqRaw, deviation); + // printf("fmDemod: %.3f\n", fmDemod); + + double filt = lowpassBaud.filter(fmDemod); + + if (!got_SC) + { + preambleMovingAverage(filt); + dc_offset = preambleMovingAverage.asDouble(); + } + + bool data = (filt - dc_offset) >= 0.0; + // printf("filt - dc: %.3f\n", filt - dc_offset); + + if (data != prev_data) + { + sync_cnt = SAMPLES_PER_SYMBOL / 2; // reset + } + else + { + sync_cnt--; // wait until next bit's midpoint + + if (sync_cnt <= 0) + { + if (bit_inverted) + { + data_bit = data; + } + else + { + data_bit = !data; + } + + // printf("%d", data_bit); + + bits = (bits << 1) | data_bit; + bit_cnt++; + + if (bit_cnt > 32) + { + bit_cnt = 32; + } + + if (bit_cnt == 32 && !got_SC) + { + // printf("pop count: %d\n", pop_cnt(bits ^ POCSAG_SYNCCODE)); + // printf("pop count inv: %d\n", pop_cnt(bits ^ POCSAG_SYNCCODE_INV)); + + if (bits == POCSAG_SYNCCODE) + { + got_SC = true; + bit_inverted = false; + printf("\nSync code found\n"); + } + else if (bits == POCSAG_SYNCCODE_INV) + { + got_SC = true; + bit_inverted = true; + printf("\nSync code found\n"); + } + else if (pop_cnt(bits ^ POCSAG_SYNCCODE) <= 3) + { + uint32_t corrected_cw; + if (bchDecode(bits, corrected_cw) && corrected_cw == POCSAG_SYNCCODE) + { + got_SC = true; + bit_inverted = false; + printf("\nSync code found\n"); + } + // else printf("\nSync code not found\n"); + } + else if (pop_cnt(bits ^ POCSAG_SYNCCODE_INV) <= 3) + { + uint32_t corrected_cw; + if (bchDecode(~bits, corrected_cw) && corrected_cw == POCSAG_SYNCCODE) + { + got_SC = true; + bit_inverted = true; + printf("\nSync code found\n"); + } + // else printf("\nSync code not found\n"); + } + + if (got_SC) + { + bits = 0; + bit_cnt = 0; + code_words[0] = POCSAG_SYNCCODE; + word_cnt = 1; + } + } + else if (bit_cnt == 32 && got_SC) + { + uint32_t corrected_cw; + code_words_bch_error[word_cnt] = !bchDecode(bits, corrected_cw); + code_words[word_cnt] = corrected_cw; + word_cnt++; + + if (word_cnt == 1 && corrected_cw != POCSAG_SYNCCODE) + { + got_SC = false; + bit_inverted = false; + } + + if (word_cnt == PAGERDEMOD_BATCH_WORDS) + { + decodeBatch(); + batch_num++; + word_cnt = 0; + } + + bits = 0; + bit_cnt = 0; + + if (address > 0 && !numeric_msg.empty()) + { + is_message_ready = true; + printf("Addr: %d | Numeric: %s | Alpha: %s\n", address, numeric_msg.c_str(), alpha_msg.c_str()); + } + else + { + is_message_ready = false; + } + } + + sync_cnt = SAMPLES_PER_SYMBOL; + } + } + + prev_data = data; +} diff --git a/android/app/src/main/cpp/demod.h b/android/app/src/main/cpp/demod.h new file mode 100644 index 0000000..57a720d --- /dev/null +++ b/android/app/src/main/cpp/demod.h @@ -0,0 +1,39 @@ +#ifndef DEMOD_H +#define DEMOD_H + +#include +#include +#include +#include +#include + +#include "dsp/phasediscri.h" +#include "dsp/firfilter.h" +#include "util/movingaverage.h" + +#define SDR_RX_SCALED 32768.0 +#define SAMPLE_RATE 48000.0 +#define BAUD_RATE 1200.0 +#define DEVIATION 4500.0 +#define SAMPLES_PER_SYMBOL (SAMPLE_RATE / BAUD_RATE) +#define POCSAG_SYNCCODE 0x7CD215D8 +#define POCSAG_SYNCCODE_INV ~0x7CD215D8 +#define PAGERDEMOD_POCSAG_IDLECODE 0x7A89C197 +#define PAGERDEMOD_BATCH_WORDS 17 +#define PAGERDEMOD_FRAMES_PER_BATCH 8 +#define PAGERDEMOD_CODEWORDS_PER_FRAME 2 + +extern bool is_message_ready; +extern std::string numeric_msg; +extern std::string alpha_msg; +extern uint32_t address; +extern int function_bits; + +extern PhaseDiscriminators phaseDiscri; +extern Lowpass lowpassBaud; +extern MovingAverageUtil preambleMovingAverage; +extern double magsqRaw; + +void processOneSample(int8_t i, int8_t q); + +#endif \ No newline at end of file diff --git a/android/app/src/main/cpp/dsp/afsquelch.cpp b/android/app/src/main/cpp/dsp/afsquelch.cpp new file mode 100644 index 0000000..6366c46 --- /dev/null +++ b/android/app/src/main/cpp/dsp/afsquelch.cpp @@ -0,0 +1,278 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "dsp/afsquelch.h" + + +AFSquelch::AFSquelch() : + m_nbAvg(128), + m_N(24), + m_sampleRate(48000), + m_samplesProcessed(0), + m_samplesAvgProcessed(0), + m_maxPowerIndex(0), + m_nTones(2), + m_samplesAttack(0), + m_attackCount(0), + m_samplesDecay(0), + m_decayCount(0), + m_squelchCount(0), + m_isOpen(false), + m_threshold(0.0) +{ + m_k = new double[m_nTones]; + m_coef = new double[m_nTones]; + m_toneSet = new double[m_nTones]; + m_u0 = new double[m_nTones]; + m_u1 = new double[m_nTones]; + m_power = new double[m_nTones]; + m_movingAverages.resize(m_nTones, MovingAverage(m_nbAvg, 0.0f)); + + for (unsigned int j = 0; j < m_nTones; ++j) + { + m_toneSet[j] = j == 0 ? 1000.0 : 6000.0; + m_k[j] = ((double)m_N * m_toneSet[j]) / (double) m_sampleRate; + m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double) m_sampleRate); + m_u0[j] = 0.0; + m_u1[j] = 0.0; + m_power[j] = 0.0; + m_movingAverages[j].fill(0.0); + } +} + + +AFSquelch::~AFSquelch() +{ + delete[] m_k; + delete[] m_coef; + delete[] m_toneSet; + delete[] m_u0; + delete[] m_u1; + delete[] m_power; +} + +void AFSquelch::setCoefficients( + unsigned int N, + unsigned int nbAvg, + unsigned int sampleRate, + unsigned int samplesAttack, + unsigned int samplesDecay, + const double *tones) +{ + m_N = N; // save the basic parameters for use during analysis + m_nbAvg = nbAvg; + m_sampleRate = sampleRate; + m_samplesAttack = samplesAttack; + m_samplesDecay = samplesDecay; + m_movingAverages.resize(m_nTones, MovingAverage(m_nbAvg, 0.0)); + m_samplesProcessed = 0; + m_samplesAvgProcessed = 0; + m_maxPowerIndex = 0; + m_attackCount = 0; + m_decayCount = 0; + m_squelchCount = 0; + m_isOpen = false; + m_threshold = 0.0; + + // for each of the frequencies (tones) of interest calculate + // k and the associated filter coefficient as per the Goertzel + // algorithm. Note: we are using a real value (as opposed to + // an integer as described in some references. k is retained + // for later display. The tone set is specified in the + // constructor. Notice that the resulting coefficients are + // independent of N. + + for (unsigned int j = 0; j < m_nTones; ++j) + { + m_toneSet[j] = tones[j] < ((double) m_sampleRate) * 0.4 ? tones[j] : ((double) m_sampleRate) * 0.4; // guarantee 80% Nyquist rate + m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate; + m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate); + m_u0[j] = 0.0; + m_u1[j] = 0.0; + m_power[j] = 0.0; + m_movingAverages[j].fill(0.0); + } +} + + +// Analyze an input signal +bool AFSquelch::analyze(double sample) +{ + + feedback(sample); // Goertzel feedback + + if (m_samplesProcessed < m_N) // completed a block of N + { + m_samplesProcessed++; + return false; + } + else + { + feedForward(); // calculate the power at each tone + m_samplesProcessed = 0; + + if (m_samplesAvgProcessed < m_nbAvg) + { + m_samplesAvgProcessed++; + return false; + } + else + { + return true; // have a result + } + } +} + + +void AFSquelch::feedback(double in) +{ + double t; + + // feedback for each tone + for (unsigned int j = 0; j < m_nTones; ++j) + { + t = m_u0[j]; + m_u0[j] = in + (m_coef[j] * m_u0[j]) - m_u1[j]; + m_u1[j] = t; + } +} + + +void AFSquelch::feedForward() +{ + for (unsigned int j = 0; j < m_nTones; ++j) + { + m_power[j] = (m_u0[j] * m_u0[j]) + (m_u1[j] * m_u1[j]) - (m_coef[j] * m_u0[j] * m_u1[j]); + m_movingAverages[j].feed(m_power[j]); + m_u0[j] = 0.0; + m_u1[j] = 0.0; // reset for next block. + } + + evaluate(); +} + + +void AFSquelch::reset() +{ + for (unsigned int j = 0; j < m_nTones; ++j) + { + m_u0[j] = 0.0; + m_u1[j] = 0.0; + m_power[j] = 0.0; + m_movingAverages[j].fill(0.0); + } + + m_samplesProcessed = 0; + m_maxPowerIndex = 0; + m_isOpen = false; +} + + +bool AFSquelch::evaluate() +{ + double maxPower = 0.0; + double minPower; + int minIndex = 0, maxIndex = 0; + + for (unsigned int j = 0; j < m_nTones; ++j) + { + if (m_movingAverages[j].sum() > maxPower) + { + maxPower = m_movingAverages[j].sum(); + maxIndex = j; + } + } + + if (maxPower == 0.0) + { + return m_isOpen; + } + + minPower = maxPower; + + for (unsigned int j = 0; j < m_nTones; ++j) + { + if (m_movingAverages[j].sum() < minPower) { + minPower = m_movingAverages[j].sum(); + minIndex = j; + } + } + +// m_isOpen = ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)); + if ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)) // open condition + { + + if (m_squelchCount < m_samplesAttack + m_samplesDecay) + { + m_squelchCount++; + } + } + else + { + if (m_squelchCount > m_samplesAttack) + { + m_squelchCount--; + } + else + { + m_squelchCount = 0; + } + } + + m_isOpen = (m_squelchCount >= m_samplesAttack) ; + +// if ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)) // open condition +// { +// if ((m_samplesAttack > 0) && (m_attackCount < m_samplesAttack)) +// { +// m_isOpen = false; +// m_attackCount++; +// } +// else +// { +// m_isOpen = true; +// m_decayCount = 0; +// } +// } +// else +// { +// if ((m_samplesDecay > 0) && (m_decayCount < m_samplesDecay)) +// { +// m_isOpen = true; +// m_decayCount++; +// } +// else +// { +// m_isOpen = false; +// m_attackCount = 0; +// } +// } + + return m_isOpen; +} + +void AFSquelch::setThreshold(double threshold) +{ + qDebug("AFSquelch::setThreshold: threshold: %f", threshold); + m_threshold = threshold; + reset(); +} diff --git a/android/app/src/main/cpp/dsp/afsquelch.h b/android/app/src/main/cpp/dsp/afsquelch.h new file mode 100644 index 0000000..59c88ca --- /dev/null +++ b/android/app/src/main/cpp/dsp/afsquelch.h @@ -0,0 +1,91 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GPL_DSP_AFSQUELCH_H_ +#define INCLUDE_GPL_DSP_AFSQUELCH_H_ + +#include "dsp/movingaverage.h" +#include "export.h" + +/** AFSquelch: AF squelch class based on the Modified Goertzel + * algorithm. + */ +class SDRBASE_API AFSquelch { +public: + // constructor with default values + AFSquelch(); + virtual ~AFSquelch(); + + // setup the basic parameters and coefficients + void setCoefficients( + unsigned int N, //!< the algorithm "block" size + unsigned int nbAvg, //!< averaging size + unsigned int sampleRate, //!< input signal sample rate + unsigned int samplesAttack, //!< number of results before squelch opens + unsigned int samplesDecay, //!< number of results keeping squelch open + const double *tones); //!< center frequency of tones tested + + // set the detection threshold + void setThreshold(double _threshold); + + // analyze a sample set and optionally filter + // the tone frequencies. + bool analyze(double sample); // input signal sample + bool evaluate(); // evaluate result + + // get the tone set + const double *getToneSet() const + { + return m_toneSet; + } + + bool open() const { + return m_isOpen; + } + + void reset(); // reset the analysis algorithm + +protected: + void feedback(double sample); + void feedForward(); + +private: + unsigned int m_nbAvg; //!< number of power samples taken for moving average + unsigned int m_N; + unsigned int m_sampleRate; + unsigned int m_samplesProcessed; + unsigned int m_samplesAvgProcessed; + unsigned int m_maxPowerIndex; + unsigned int m_nTones; + unsigned int m_samplesAttack; + unsigned int m_attackCount; + unsigned int m_samplesDecay; + unsigned int m_decayCount; + unsigned int m_squelchCount; + bool m_isOpen; + double m_threshold; + double *m_k; + double *m_coef; + double *m_toneSet; + double *m_u0; + double *m_u1; + double *m_power; + std::vector > m_movingAverages; +}; + + +#endif /* INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ */ diff --git a/android/app/src/main/cpp/dsp/agc.cpp b/android/app/src/main/cpp/dsp/agc.cpp new file mode 100644 index 0000000..905edc7 --- /dev/null +++ b/android/app/src/main/cpp/dsp/agc.cpp @@ -0,0 +1,191 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// + +#include +#include "dsp/agc.h" + +#include "util/stepfunctions.h" + +AGC::AGC(int historySize, double R) : + m_u0(1.0), + m_R(R), + m_moving_average(historySize, m_R), + m_historySize(historySize), + m_count(0) +{} + +AGC::~AGC() +{} + +void AGC::resize(int historySize, double R) +{ + m_R = R; + m_moving_average.resize(historySize, R); + m_historySize = historySize; + m_count = 0; +} + +Real AGC::getValue() +{ + return m_u0; +} + +Real AGC::getAverage() +{ + return m_moving_average.average(); +} + + +MagAGC::MagAGC(int historySize, double R, double threshold) : + AGC(historySize, R), + m_squared(false), + m_magsq(0.0), + m_threshold(threshold), + m_thresholdEnable(true), + m_gate(0), + m_stepLength(std::min(2400, historySize/2)), // max 50 ms (at 48 kHz) + m_stepDelta(1.0/m_stepLength), + m_stepUpCounter(0), + m_stepDownCounter(0), + m_gateCounter(0), + m_stepDownDelay(historySize), + m_hardLimiting(false) +{} + +MagAGC::~MagAGC() +{} + +void MagAGC::resize(int historySize, int stepLength, Real R) +{ + m_stepLength = stepLength; + m_stepDelta = 1.0 / m_stepLength; + m_stepUpCounter = 0; + m_stepDownCounter = 0; + AGC::resize(historySize, R); + m_moving_average.fill(m_squared ? R : R*R); +} + +void MagAGC::setOrder(double R) +{ + AGC::setOrder(R); + m_moving_average.fill(m_squared ? R : R*R); +} + +void MagAGC::setThresholdEnable(bool enable) +{ + if (m_thresholdEnable != enable) + { + m_stepUpCounter = 0; + m_stepDownCounter = 0; + } + + m_thresholdEnable = enable; +} + +void MagAGC::feed(Complex& ci) +{ + ci *= feedAndGetValue(ci); +} + +double MagAGC::hardLimiter(double multiplier, double magsq) +{ + if ((m_hardLimiting) && (multiplier*multiplier*magsq > 1.0)) { + return 1.0 / (multiplier*sqrt(magsq)); + } else { + return multiplier; + } +} + +double MagAGC::feedAndGetValue(const Complex& ci) +{ + m_magsq = ci.real()*ci.real() + ci.imag()*ci.imag(); + m_moving_average.feed(m_magsq); + m_u0 = m_R / (m_squared ? m_moving_average.average() : sqrt(m_moving_average.average())); + + if (m_thresholdEnable) + { + bool open = false; + + if (m_magsq > m_threshold) + { + if (m_gateCounter < m_gate) { + m_gateCounter++; + } else { + open = true; + } + } + else + { + m_gateCounter = 0; + } + + if (open) + { + m_count = m_stepDownDelay; // delay before step down (grace delay) + } + else + { + m_count--; + m_gateCounter = m_gate; // keep gate open during grace + } + + if (m_count > 0) // up phase + { + m_stepDownCounter = m_stepUpCounter; // prepare for step down + + if (m_stepUpCounter < m_stepLength) // step up + { + m_stepUpCounter++; + return hardLimiter(m_u0 * StepFunctions::smootherstep(m_stepUpCounter * m_stepDelta), m_magsq); + } + else // steady open + { + return hardLimiter(m_u0, m_magsq); + } + } + else // down phase + { + m_stepUpCounter = m_stepDownCounter; // prepare for step up + + if (m_stepDownCounter > 0) // step down + { + m_stepDownCounter--; + return hardLimiter(m_u0 * StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta), m_magsq); + } + else // steady closed + { + return 0.0; + } + } + } + else + { + return hardLimiter(m_u0, m_magsq); + } +} + +float MagAGC::getStepValue() const +{ + if (m_count > 0) // up phase + { + return StepFunctions::smootherstep(m_stepUpCounter * m_stepDelta); // step up + } + else // down phase + { + return StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta); // step down + } +} diff --git a/android/app/src/main/cpp/dsp/agc.h b/android/app/src/main/cpp/dsp/agc.h new file mode 100644 index 0000000..6c50226 --- /dev/null +++ b/android/app/src/main/cpp/dsp/agc.h @@ -0,0 +1,137 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GPL_DSP_AGC_H_ +#define INCLUDE_GPL_DSP_AGC_H_ + +#include "dsp/dsptypes.h" +#include "movingaverage.h" +#include "export.h" + +class SDRBASE_API AGC +{ +public: + AGC(int historySize, double R); + virtual ~AGC(); + + void resize(int historySize, double R); + void setOrder(double R) { m_R = R; } + Real getValue(); + Real getAverage(); + void reset(double R) { m_moving_average.fill(R); } + virtual void feed(Complex& ci) = 0; + +protected: + double m_u0; //!< AGC factor + double m_R; //!< ordered magnitude + MovingAverage m_moving_average; //!< Averaging engine. The stack length conditions the smoothness of AGC. + int m_historySize; //!< Averaging length (the longer the slower the AGC) + int m_count; //!< Samples counter +}; + + +class SDRBASE_API MagAGC : public AGC +{ +public: + MagAGC(int historySize, double R, double threshold); + virtual ~MagAGC(); + void setSquared(bool squared) { m_squared = squared; } + void resize(int historySize, int stepLength, Real R); + void setOrder(double R); + virtual void feed(Complex& ci); + double feedAndGetValue(const Complex& ci); + double getMagSq() const { return m_magsq; } + void setThreshold(double threshold) { m_threshold = threshold; } + void setThresholdEnable(bool enable); + void setGate(int gate) { m_gate = gate; m_gateCounter = 0; m_count = 0; } + void setStepDownDelay(int stepDownDelay) { m_stepDownDelay = stepDownDelay; m_gateCounter = 0; m_count = 0; } + int getStepDownDelay() const { return m_stepDownDelay; } + float getStepValue() const; + void setHardLimiting(bool hardLimiting) { m_hardLimiting = hardLimiting; } + void resetStepCounters() { m_stepUpCounter = 0; m_stepDownCounter = 0; } + +private: + bool m_squared; //!< use squared magnitude (power) to compute AGC value + double m_magsq; //!< current squared magnitude (power) + double m_threshold; //!< squelch on magsq average + bool m_thresholdEnable; //!< enable squelch on power threshold + int m_gate; //!< power threshold gate in number of samples + int m_stepLength; //!< transition step length in number of samples + double m_stepDelta; //!< transition step unit by sample + int m_stepUpCounter; //!< step up transition samples counter + int m_stepDownCounter; //!< step down transition samples counter + int m_gateCounter; //!< threshold gate samples counter + int m_stepDownDelay; //!< delay in samples before cutoff (release) + bool m_hardLimiting; //!< hard limit multiplier so that resulting sample magnitude does not exceed 1.0 + + double hardLimiter(double multiplier, double magsq); +}; + +template +class SimpleAGC +{ +public: + SimpleAGC(Real initial, Real cutoff=0, Real clip=0) : + m_cutoff(cutoff), + m_clip(clip), + m_moving_average(AvgSize, initial) + { + } + + void resize(Real initial, Real cutoff=0, Real clip=0) + { + m_cutoff = cutoff; + m_clip = clip; + m_moving_average.resize(AvgSize, initial); + } + + void resizeNew(uint32_t newSize, Real initial, Real cutoff=0, Real clip=0) + { + m_cutoff = cutoff; + m_clip = clip; + m_moving_average.resize(newSize, initial); + } + + void fill(double value) + { + m_moving_average.fill(value); + } + + Real getValue() + { + if ((Real) m_moving_average.average() > m_clip) { + return (Real) m_moving_average.average(); + } else { + return m_clip; + } + } + + void feed(Real value) + { + if (value > m_cutoff) { + m_moving_average.feed(value); + } + } + +private: + Real m_cutoff; // consider samples only above this level + Real m_clip; // never go below this level + MovingAverage m_moving_average; // Averaging engine. The stack length conditions the smoothness of AGC. + //MovingAverageUtil m_moving_average; +}; + +#endif /* INCLUDE_GPL_DSP_AGC_H_ */ diff --git a/android/app/src/main/cpp/dsp/autocorrector.h b/android/app/src/main/cpp/dsp/autocorrector.h new file mode 100644 index 0000000..2a59687 --- /dev/null +++ b/android/app/src/main/cpp/dsp/autocorrector.h @@ -0,0 +1,106 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_AUTOCORRECTOR_H_ +#define SDRBASE_DSP_AUTOCORRECTOR_H_ + +#include "util/movingaverage.h" + +template +class AutoCorrector +{ +public: + AutoCorrector(qint32 intBits); + void process(const T& inreal, const T& inimag, qint32& outreal, qint32& outimag); + void process(qint32& xreal, qint32& ximag); + void setIQCorrection(bool iqCorrection) { m_iqCorrection = iqCorrection; } +private: + bool m_iqCorrection; + float m_scalef; + MovingAverageUtil m_iBeta; + MovingAverageUtil m_qBeta; + MovingAverageUtil m_avgII; + MovingAverageUtil m_avgIQ; + MovingAverageUtil m_avgII2; + MovingAverageUtil m_avgQQ2; + MovingAverageUtil m_avgPhi; + MovingAverageUtil m_avgAmp; +}; + +template +AutoCorrector::AutoCorrector(qint32 intBits) : + m_iqCorrection(false), + m_scalef((float) (1<<(intBits-1))) +{ +} + +template +void AutoCorrector::process(const T& inreal, const T& inimag, qint32& outreal, qint32& outimag) +{ + outreal = inreal; + outimag = inimag; + process(outreal, outimag); +} + +template +void AutoCorrector::process(qint32& xreal, qint32& ximag) +{ + m_iBeta(xreal); + m_qBeta(ximag); + + if (m_iqCorrection) + { + // DC correction and conversion + float xi = (xreal - (int32_t) m_iBeta) / m_scalef; + float xq = (ximag - (int32_t) m_qBeta) / m_scalef; + + // phase imbalance + m_avgII(xi*xi); // + m_avgIQ(xi*xq); // + + + if (m_avgII.asDouble() != 0) { + m_avgPhi(m_avgIQ.asDouble()/m_avgII.asDouble()); + } + + float yi = xi - m_avgPhi.asDouble()*xq; + float yq = xq; + + // amplitude I/Q imbalance + m_avgII2(yi*yi); // + m_avgQQ2(yq*yq); // + + if (m_avgQQ2.asDouble() != 0) { + m_avgAmp(sqrt(m_avgII2.asDouble() / m_avgQQ2.asDouble())); + } + + // final correction + float zi = yi; + float zq = m_avgAmp.asDouble() * yq; + + // convert and store + xreal = zi * m_scalef; + ximag = zq * m_scalef; + } + else + { + xreal -= (int32_t) m_iBeta; + ximag -= (int32_t) m_qBeta; + } +} + +#endif /* SDRBASE_DSP_AUTOCORRECTOR_H_ */ diff --git a/android/app/src/main/cpp/dsp/basebandsamplesink.cpp b/android/app/src/main/cpp/dsp/basebandsamplesink.cpp new file mode 100644 index 0000000..a2910f2 --- /dev/null +++ b/android/app/src/main/cpp/dsp/basebandsamplesink.cpp @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2018, 2020-2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "basebandsamplesink.h" + +BasebandSampleSink::BasebandSampleSink() +{ +} + +BasebandSampleSink::~BasebandSampleSink() +{ +} diff --git a/android/app/src/main/cpp/dsp/basebandsamplesink.h b/android/app/src/main/cpp/dsp/basebandsamplesink.h new file mode 100644 index 0000000..db0efd1 --- /dev/null +++ b/android/app/src/main/cpp/dsp/basebandsamplesink.h @@ -0,0 +1,44 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SAMPLESINK_H +#define INCLUDE_SAMPLESINK_H + +#include + +#include "dsp/dsptypes.h" +#include "export.h" + +class Message; + +class SDRBASE_API BasebandSampleSink { +public: + BasebandSampleSink(); + virtual ~BasebandSampleSink(); + + virtual void start() = 0; + virtual void stop() = 0; + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) = 0; + virtual void feed(const Complex*, unsigned int) { //!< Special feed directly with complex array + } + virtual void pushMessage(Message *msg) = 0; + virtual std::string getSinkName() = 0; +}; + +#endif // INCLUDE_SAMPLESINK_H diff --git a/android/app/src/main/cpp/dsp/basebandsamplesource.cpp b/android/app/src/main/cpp/dsp/basebandsamplesource.cpp new file mode 100644 index 0000000..1dce1d0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/basebandsamplesource.cpp @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2019, 2021-2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/basebandsamplesource.h" + +BasebandSampleSource::BasebandSampleSource() +{ +} + +BasebandSampleSource::~BasebandSampleSource() +{ +} diff --git a/android/app/src/main/cpp/dsp/basebandsamplesource.h b/android/app/src/main/cpp/dsp/basebandsamplesource.h new file mode 100644 index 0000000..0528075 --- /dev/null +++ b/android/app/src/main/cpp/dsp/basebandsamplesource.h @@ -0,0 +1,40 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2021-2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_BASEBANDSAMPLESOURCE_H_ +#define SDRBASE_DSP_BASEBANDSAMPLESOURCE_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +class Message; + +class SDRBASE_API BasebandSampleSource { +public: + BasebandSampleSource(); + virtual ~BasebandSampleSource(); + + virtual void start() = 0; + virtual void stop() = 0; + virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples) = 0; + virtual void pushMessage(Message *msg) = 0; + virtual QString getSourceName() = 0; +}; + +#endif /* SDRBASE_DSP_BASEBANDSAMPLESOURCE_H_ */ diff --git a/android/app/src/main/cpp/dsp/channelmarker.cpp b/android/app/src/main/cpp/dsp/channelmarker.cpp new file mode 100644 index 0000000..07c7dd6 --- /dev/null +++ b/android/app/src/main/cpp/dsp/channelmarker.cpp @@ -0,0 +1,260 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015, 2017-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGChannelMarker.h" +#include "dsp/channelmarker.h" +#include "util/simpleserializer.h" + +QRgb ChannelMarker::m_colorTable[] = { + qRgb(0xc0, 0x00, 0x00), + qRgb(0x00, 0xc0, 0x00), + qRgb(0x00, 0x00, 0xc0), + + qRgb(0xc0, 0xc0, 0x00), + qRgb(0xc0, 0x00, 0xc0), + qRgb(0x00, 0xc0, 0xc0), + + qRgb(0xc0, 0x60, 0x00), + qRgb(0xc0, 0x00, 0x60), + qRgb(0x60, 0x00, 0xc0), + + qRgb(0x60, 0x00, 0x00), + qRgb(0x00, 0x60, 0x00), + qRgb(0x00, 0x00, 0x60), + + qRgb(0x60, 0x60, 0x00), + qRgb(0x60, 0x00, 0x60), + qRgb(0x00, 0x60, 0x60), + + 0 +}; +int ChannelMarker::m_nextColor = 0; + +ChannelMarker::ChannelMarker(QObject* parent) : + QObject(parent), + m_centerFrequency(0), + m_bandwidth(0), + m_oppositeBandwidth(0), + m_lowCutoff(0), + m_shift(0), + m_sidebands(dsb), + m_visible(false), + m_highlighted(false), + m_color(m_colorTable[m_nextColor]), + m_movable(true), + m_fScaleDisplayType(FScaleDisplay_freq), + m_sourceOrSinkStream(true), + m_enabledStreamsBits(1) +{ + ++m_nextColor; + if(m_colorTable[m_nextColor] == 0) + m_nextColor = 0; +} + +void ChannelMarker::emitChangedByAPI() +{ + emit changedByAPI(); +} + +void ChannelMarker::setTitle(const QString& title) +{ + m_title = title; + emit changedByAPI(); +} + +void ChannelMarker::setCenterFrequency(int centerFrequency) +{ + m_centerFrequency = centerFrequency; + emit changedByAPI(); +} + +void ChannelMarker::setCenterFrequencyByCursor(int centerFrequency) +{ + m_centerFrequency = centerFrequency; + emit changedByCursor(); +} + +void ChannelMarker::setBandwidth(int bandwidth) +{ + m_bandwidth = bandwidth; + emit changedByAPI(); +} + +void ChannelMarker::setOppositeBandwidth(int bandwidth) +{ + m_oppositeBandwidth = bandwidth; + emit changedByAPI(); +} + +void ChannelMarker::setLowCutoff(int lowCutoff) +{ + m_lowCutoff = lowCutoff; + emit changedByAPI(); +} + +void ChannelMarker::setShift(int shift) +{ + m_shift = shift; + emit changedByAPI(); +} + +void ChannelMarker::setSidebands(sidebands_t sidebands) +{ + m_sidebands = sidebands; + emit changedByAPI(); +} + +void ChannelMarker::setVisible(bool visible) +{ + m_visible = visible; + emit changedByAPI(); +} + +void ChannelMarker::setHighlighted(bool highlighted) +{ + m_highlighted = highlighted; + emit changedByAPI(); +} + +void ChannelMarker::setHighlightedByCursor(bool highlighted) +{ + m_highlighted = highlighted; + emit highlightedByCursor(); +} + +void ChannelMarker::setColor(const QColor& color) +{ + m_color = color; + emit changedByAPI(); +} + +void ChannelMarker::resetToDefaults() +{ + setTitle("Channel"); + setCenterFrequency(0); + setColor(Qt::white); + setFrequencyScaleDisplayType(FScaleDisplay_freq); +} + +QByteArray ChannelMarker::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, getCenterFrequency()); + s.writeU32(2, getColor().rgb()); + s.writeString(3, getTitle()); + s.writeS32(7, (int) getFrequencyScaleDisplayType()); + return s.final(); +} + +bool ChannelMarker::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + quint32 u32tmp; + qint32 tmp; + QString strtmp; + + blockSignals(true); + + d.readS32(1, &tmp, 0); + setCenterFrequency(tmp); + if(d.readU32(2, &u32tmp)) { + setColor(u32tmp); + } + d.readString(3, &strtmp, "Channel"); + setTitle(strtmp); + d.readS32(7, &tmp, 0); + if ((tmp >= 0) && (tmp < FScaleDisplay_none)) { + setFrequencyScaleDisplayType((frequencyScaleDisplay_t) tmp); + } else { + setFrequencyScaleDisplayType(FScaleDisplay_freq); + } + + blockSignals(false); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +void ChannelMarker::formatTo(SWGSDRangel::SWGObject *swgObject) const +{ + SWGSDRangel::SWGChannelMarker *swgChannelMarker = static_cast(swgObject); + + swgChannelMarker->setCenterFrequency(getCenterFrequency()); + swgChannelMarker->setColor(getColor().rgb()); + swgChannelMarker->setFrequencyScaleDisplayType((int) getFrequencyScaleDisplayType()); + + if (swgChannelMarker->getTitle()) { + *swgChannelMarker->getTitle() = getTitle(); + } else { + swgChannelMarker->setTitle(new QString(getTitle())); + } +} + +void ChannelMarker::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject) +{ + SWGSDRangel::SWGChannelMarker *swgChannelMarker = + static_cast(const_cast(swgObject)); + + if (keys.contains("channelMarker.centerFrequency")) { + setCenterFrequency(swgChannelMarker->getCenterFrequency()); + } + if (keys.contains("channelMarker.color")) { + setColor(swgChannelMarker->getColor()); + } + if (keys.contains("channelMarker.frequencyScaleDisplayType")) { + setFrequencyScaleDisplayType((frequencyScaleDisplay_t) swgChannelMarker->getFrequencyScaleDisplayType()); + } + if (keys.contains("channelMarker.title")) { + setTitle(*swgChannelMarker->getTitle()); + } +} + +void ChannelMarker::updateSettings(const ChannelMarker *channelMarker) +{ + m_fScaleDisplayType = channelMarker->m_fScaleDisplayType; +} + +void ChannelMarker::addStreamIndex(int streamIndex) +{ + m_enabledStreamsBits |= (1< // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CHANNELMARKER_H +#define INCLUDE_CHANNELMARKER_H + +#include +#include +#include + +#include "settings/serializable.h" +#include "export.h" + +class SDRBASE_API ChannelMarker : public QObject, public Serializable { + Q_OBJECT + +public: + typedef enum sidebands_e + { + dsb, + lsb, + usb, + vusb, //!< USB with vestigial LSB + vlsb //!< LSB with vestigial USB + } sidebands_t; + + typedef enum frequencyScaleDisplay_e + { + FScaleDisplay_freq, + FScaleDisplay_title, + FScaleDisplay_addressSend, + FScaleDisplay_addressReceive, + FScaleDisplay_none + } frequencyScaleDisplay_t; + + ChannelMarker(QObject* parent = NULL); + + void emitChangedByAPI(); + + void setTitle(const QString& title); + const QString& getTitle() const { return m_title; } + + void setCenterFrequency(int centerFrequency); + void setCenterFrequencyByCursor(int centerFrequency); + int getCenterFrequency() const { return m_centerFrequency; } + + void setBandwidth(int bandwidth); + int getBandwidth() const { return m_bandwidth; } + + void setOppositeBandwidth(int bandwidth); + int getOppositeBandwidth() const { return m_oppositeBandwidth; } + + void setLowCutoff(int lowCutoff); + int getLowCutoff() const { return m_lowCutoff; } + + void setShift(int shift); + int getShift() const { return m_shift; } + + void setSidebands(sidebands_t sidebands); + sidebands_t getSidebands() const { return m_sidebands; } + + void setVisible(bool visible); + bool getVisible() const { return m_visible; } + + void setHighlighted(bool highlighted); + void setHighlightedByCursor(bool highlighted); + bool getHighlighted() const { return m_highlighted; } + + void setColor(const QColor& color); + const QColor& getColor() const { return m_color; } + + void setMovable(bool movable) { m_movable = movable; } + bool getMovable() const { return m_movable; } + + void setFrequencyScaleDisplayType(frequencyScaleDisplay_t type) { m_fScaleDisplayType = type; } + frequencyScaleDisplay_t getFrequencyScaleDisplayType() const { return m_fScaleDisplayType; } + + const QString& getDisplayAddressSend() const { return m_displayAddressSend; } + const QString& getDisplayAddressReceive() const { return m_displayAddressReceive; } + + void setSourceOrSinkStream(bool sourceOrSinkStream) { m_sourceOrSinkStream = sourceOrSinkStream; } + bool getSourceOrSinkStream() const { return m_sourceOrSinkStream; } + void addStreamIndex(int streamIndex); + void removeStreamIndex(int streamIndex); + void clearStreamIndexes(); + + bool streamIndexApplies(int streamIndex) const { + return m_enabledStreamsBits & (1< // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "channelsamplesink.h" + +ChannelSampleSink::ChannelSampleSink() +{} + +ChannelSampleSink::~ChannelSampleSink() +{} diff --git a/android/app/src/main/cpp/dsp/channelsamplesink.h b/android/app/src/main/cpp/dsp/channelsamplesink.h new file mode 100644 index 0000000..34daa10 --- /dev/null +++ b/android/app/src/main/cpp/dsp/channelsamplesink.h @@ -0,0 +1,38 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// This is a lightweight channel sample source interface + +#ifndef SDRBASE_DSP_CHANNELSAMPLESINK_H_ +#define SDRBASE_DSP_CHANNELSAMPLESINK_H_ + +#include "export.h" +#include "dsp/dsptypes.h" + +class Message; + +class SDRBASE_API ChannelSampleSink { +public: + ChannelSampleSink(); + virtual ~ChannelSampleSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) = 0; +}; + +#endif // SDRBASE_DSP_CHANNELSAMPLESINK_H_ diff --git a/android/app/src/main/cpp/dsp/channelsamplesource.cpp b/android/app/src/main/cpp/dsp/channelsamplesource.cpp new file mode 100644 index 0000000..a20e61a --- /dev/null +++ b/android/app/src/main/cpp/dsp/channelsamplesource.cpp @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "channelsamplesource.h" + +ChannelSampleSource::ChannelSampleSource() +{} + +ChannelSampleSource::~ChannelSampleSource() +{} diff --git a/android/app/src/main/cpp/dsp/channelsamplesource.h b/android/app/src/main/cpp/dsp/channelsamplesource.h new file mode 100644 index 0000000..dc3de77 --- /dev/null +++ b/android/app/src/main/cpp/dsp/channelsamplesource.h @@ -0,0 +1,38 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// This is a lightweight channel sample source interface + +#ifndef SDRBASE_DSP_CHANNELSAMPLESOURCE_H_ +#define SDRBASE_DSP_CHANNELSAMPLESOURCE_H_ + +#include "export.h" +#include "dsp/dsptypes.h" + +class SDRBASE_API ChannelSampleSource { +public: + ChannelSampleSource(); + virtual ~ChannelSampleSource(); + + virtual void pull(SampleVector::iterator begin, unsigned int nbSamples) = 0; //!< pull nbSamples from the source and write them starting at begin + virtual void pullOne(Sample& sample) = 0; //!< pull a single sample from the source + virtual void prefetch(unsigned int nbSamples) = 0; //!< Do operation(s) before pulling nbSamples +}; + +#endif // SDRBASE_DSP_CHANNELSAMPLESOURCE_H_ diff --git a/android/app/src/main/cpp/dsp/complex.h b/android/app/src/main/cpp/dsp/complex.h new file mode 100644 index 0000000..5a7fcea --- /dev/null +++ b/android/app/src/main/cpp/dsp/complex.h @@ -0,0 +1,61 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +// ---------------------------------------------------------------------------- +// complex.h -- Complex arithmetic +// +// Copyright (C) 2006-2008 +// Dave Freese, W1HKJ +// Copyright (C) 2008 +// Stelios Bounanos, M0GLD +// +// This file is part of fldigi. +// +// Fldigi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Fldigi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with fldigi. If not, see . +// ---------------------------------------------------------------------------- + +#ifndef _COMPLEX_H +#define _COMPLEX_H + +#include +#include + +typedef std::complex cmplx; + +inline cmplx cmac (const cmplx *a, const cmplx *b, int ptr, int len) { + cmplx z; + ptr %= len; + for (int i = 0; i < len; i++) { + z += a[i] * b[ptr]; + ptr = (ptr + 1) % len; + } + return z; +} + +#endif diff --git a/android/app/src/main/cpp/dsp/costasloop.cpp b/android/app/src/main/cpp/dsp/costasloop.cpp new file mode 100644 index 0000000..172015d --- /dev/null +++ b/android/app/src/main/cpp/dsp/costasloop.cpp @@ -0,0 +1,112 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright 2006-2021 Free Software Foundation, Inc. // +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// Based on the Costas Loop from GNU Radio // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "costasloop.h" +#include + +// Loop bandwidth supposedly ~ 2pi/100 rads/sample +// pskOrder 2, 4 or 8 +CostasLoop::CostasLoop(float loopBW, unsigned int pskOrder) : + m_maxFreq(1.0f), + m_minFreq(-1.0f), + m_pskOrder(pskOrder) +{ + computeCoefficients(loopBW); + reset(); +} + +CostasLoop::~CostasLoop() +{ +} + +void CostasLoop::reset() +{ + m_y.real(1.0f); + m_y.imag(0.0f); + m_freq = 0.0f; + m_phase = 0.0f; + m_freq = 0.0f; + m_error = 0.0f; +} + +// 2nd order loop with critical damping +void CostasLoop::computeCoefficients(float loopBW) +{ + float damping = sqrtf(2.0f) / 2.0f; + float denom = (1.0 + 2.0 * damping * loopBW + loopBW * loopBW); + m_alpha = (4 * damping * loopBW) / denom; + m_beta = (4 * loopBW * loopBW) / denom; +} + +void CostasLoop::setSampleRate(unsigned int sampleRate) +{ + (void) sampleRate; + + reset(); +} + +static float branchlessClip(float x, float clip) +{ + return 0.5f * (std::abs(x + clip) - std::abs(x - clip)); +} + +// Don't use built-in complex.h multiply to avoid NaN/INF checking +static void fastComplexMultiply(std::complex &out, const std::complex cc1, const std::complex cc2) +{ + float o_r, o_i; + + o_r = (cc1.real() * cc2.real()) - (cc1.imag() * cc2.imag()); + o_i = (cc1.real() * cc2.imag()) + (cc1.imag() * cc2.real()); + + out.real(o_r); + out.imag(o_i); +} + +void CostasLoop::feed(float re, float im) +{ + std::complex nco(::cosf(-m_phase), ::sinf(-m_phase)); + + std::complex in, out; + in.real(re); + in.imag(im); + fastComplexMultiply(out, in, nco); + + switch (m_pskOrder) + { + case 2: + m_error = phaseDetector2(out); + break; + case 4: + m_error = phaseDetector4(out); + break; + case 8: + m_error = phaseDetector8(out); + break; + } + m_error = branchlessClip(m_error, 1.0f); + + advanceLoop(m_error); + phaseWrap(); + frequencyLimit(); + + m_y.real(-nco.real()); + m_y.imag(nco.imag()); +} diff --git a/android/app/src/main/cpp/dsp/costasloop.h b/android/app/src/main/cpp/dsp/costasloop.h new file mode 100644 index 0000000..f641e8d --- /dev/null +++ b/android/app/src/main/cpp/dsp/costasloop.h @@ -0,0 +1,122 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright 2006-2021 Free Software Foundation, Inc. // +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// Based on the Costas Loop from GNU Radio // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_COSTASLOOP_H_ +#define SDRBASE_DSP_COSTASLOOP_H_ + +#include + +#include "dsp/dsptypes.h" +#include "export.h" + +/** Costas Loop for phase and frequency tracking. */ +class SDRBASE_API CostasLoop +{ +public: + CostasLoop(float loopBW, unsigned int pskOrder); + ~CostasLoop(); + + void computeCoefficients(float loopBW); + void setPskOrder(unsigned int pskOrder) { m_pskOrder = pskOrder; } + void reset(); + void setSampleRate(unsigned int sampleRate); + void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_y.real(); } + float getImag() const { return m_y.imag(); } + float getFreq() const { return m_freq; } + float getPhiHat() const { return m_phase; } + +private: + + std::complex m_y; + float m_phase; + float m_freq; + float m_error; + float m_maxFreq; + float m_minFreq; + float m_alpha; + float m_beta; + unsigned int m_pskOrder; + + void advanceLoop(float error) + { + m_freq = m_freq + m_beta * error; + m_phase = m_phase + m_freq + m_alpha * error; + } + + void phaseWrap() + { + const float two_pi = (float)(2.0 * M_PI); + + while (m_phase > two_pi) + m_phase -= two_pi; + while (m_phase < -two_pi) + m_phase += two_pi; + } + + void frequencyLimit() + { + if (m_freq > m_maxFreq) + m_freq = m_maxFreq; + else if (m_freq < m_minFreq) + m_freq = m_minFreq; + } + + void setMaxFreq(float freq) + { + m_maxFreq = freq; + } + + void setMinFreq(float freq) + { + m_minFreq = freq; + } + + float phaseDetector2(std::complex sample) const // for BPSK + { + return (sample.real() * sample.imag()); + } + + float phaseDetector4(std::complex sample) const // for QPSK + { + return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() - + (sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real()); + }; + + float phaseDetector8(std::complex sample) const // for 8PSK + { + const float K = (sqrtf(2.0) - 1); + if (fabsf(sample.real()) >= fabsf(sample.imag())) + { + return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() - + (sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real() * K); + } + else + { + return ((sample.real() > 0.0f ? 1.0f : -1.0f) * sample.imag() * K - + (sample.imag() > 0.0f ? 1.0f : -1.0f) * sample.real()); + } + }; + +}; + +#endif /* SDRBASE_DSP_COSTASLOOP_H_ */ diff --git a/android/app/src/main/cpp/dsp/ctcssdetector.cpp b/android/app/src/main/cpp/dsp/ctcssdetector.cpp new file mode 100644 index 0000000..ac40068 --- /dev/null +++ b/android/app/src/main/cpp/dsp/ctcssdetector.cpp @@ -0,0 +1,159 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2017, 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// See: http://www.embedded.com/design/connectivity/4025660/Detecting-CTCSS-tones-with-Goertzel-s-algorithm // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/ctcssdetector.h" + +CTCSSDetector::CTCSSDetector() : + m_N(0), + m_sampleRate(0), + m_samplesProcessed(0), + m_maxPowerIndex(0), + m_toneDetected(false), + m_maxPower(0.0) +{ + m_k = new Real[CTCSSFrequencies::m_nbFreqs]; + m_coef = new Real[CTCSSFrequencies::m_nbFreqs]; + m_u0 = new Real[CTCSSFrequencies::m_nbFreqs]; + m_u1 = new Real[CTCSSFrequencies::m_nbFreqs]; + m_power = new Real[CTCSSFrequencies::m_nbFreqs]; +} + +CTCSSDetector::~CTCSSDetector() +{ + delete[] m_k; + delete[] m_coef; + delete[] m_u0; + delete[] m_u1; + delete[] m_power; +} + + +void CTCSSDetector::setCoefficients(int N, int sampleRate) +{ + m_N = N; // save the basic parameters for use during analysis + m_sampleRate = sampleRate; + + // for each of the frequencies (tones) of interest calculate + // k and the associated filter coefficient as per the Goertzel + // algorithm. Note: we are using a real value (as apposed to + // an integer as described in some references. k is retained + // for later display. The tone set is specified in the + // constructor. Notice that the resulting coefficients are + // independent of N. + for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j) + { + m_k[j] = ((double) m_N * CTCSSFrequencies::m_Freqs[j]) / (double)m_sampleRate; + m_coef[j] = 2.0 * cos((2.0 * M_PI * CTCSSFrequencies::m_Freqs[j])/(double)m_sampleRate); + } +} + + +// Analyze an input signal for the presence of CTCSS tones. +bool CTCSSDetector::analyze(Real *sample) +{ + + feedback(*sample); // Goertzel feedback + m_samplesProcessed += 1; + + if (m_samplesProcessed == m_N) // completed a block of N + { + feedForward(); // calculate the m_power at each tone + m_samplesProcessed = 0; + return true; // have a result + } + else + { + return false; + } +} + + +void CTCSSDetector::feedback(Real in) +{ + Real t; + + // feedback for each tone + for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j) + { + t = m_u0[j]; + m_u0[j] = in + (m_coef[j] * m_u0[j]) - m_u1[j]; + m_u1[j] = t; + } +} + + +void CTCSSDetector::feedForward() +{ + initializePower(); + + for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j) + { + m_power[j] = (m_u0[j] * m_u0[j]) + (m_u1[j] * m_u1[j]) - (m_coef[j] * m_u0[j] * m_u1[j]); + m_u0[j] = m_u1[j] = 0.0; // reset for next block. + } + + evaluatePower(); +} + + +void CTCSSDetector::reset() +{ + for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j) + { + m_power[j] = m_u0[j] = m_u1[j] = 0.0; // reset + } + + m_samplesProcessed = 0; + m_maxPower = 0.0; + m_maxPowerIndex = 0; + m_toneDetected = false; +} + + +void CTCSSDetector::initializePower() +{ + for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j) + { + m_power[j] = 0.0; // reset + } +} + + +void CTCSSDetector::evaluatePower() +{ + Real sumPower = 0.0; + Real aboveAvg = 2.0; // Arbitrary max m_power above average threshold + m_maxPower = 0.0; + + for (int j = 0; j < CTCSSFrequencies::m_nbFreqs; ++j) + { + sumPower += m_power[j]; + + if (m_power[j] > m_maxPower) + { + m_maxPower = m_power[j]; + m_maxPowerIndex = j; + } + } + + m_toneDetected = (m_maxPower > (sumPower/CTCSSFrequencies::m_nbFreqs) + aboveAvg); +} diff --git a/android/app/src/main/cpp/dsp/ctcssdetector.h b/android/app/src/main/cpp/dsp/ctcssdetector.h new file mode 100644 index 0000000..d2304c0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/ctcssdetector.h @@ -0,0 +1,94 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB // +// // +// See: http://www.embedded.com/design/connectivity/4025660/Detecting-CTCSS-tones-with-Goertzel-s-algorithm // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ +#define INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ + +#include "dsp/dsptypes.h" +#include "export.h" +#include "ctcssfrequencies.h" + +/** CTCSSDetector: Continuous Tone Coded Squelch System + * tone detector class based on the Modified Goertzel + * algorithm. + */ +class SDRBASE_API CTCSSDetector { +public: + CTCSSDetector(); + virtual ~CTCSSDetector(); + + // setup the basic parameters and coefficients + void setCoefficients( + int N, // the algorithm "block" size + int sampleRate // input signal sample rate + ); + + // set the detection threshold + void setThreshold(double thold); + + // analyze a sample set and optionally filter the tone frequencies. + bool analyze(Real *sample); // input signal sample + + // get the number of defined tones. + static int getNTones() { + return CTCSSFrequencies::m_nbFreqs; + } + + // get the tone set + static const Real *getToneSet() { + return CTCSSFrequencies::m_Freqs; + } + + // get the currently detected tone, if any + bool getDetectedTone(int &maxTone) const + { + maxTone = m_maxPowerIndex; + return m_toneDetected; + } + + // Get the max m_power at the detected tone. + Real getMaxPower() const { + return m_maxPower; + } + + void reset(); // reset the analysis algorithm + +protected: + // Override these to change behavior of the detector + virtual void initializePower(); + virtual void evaluatePower(); + void feedback(Real sample); + void feedForward(); + +private: + int m_N; + int m_sampleRate; + int m_samplesProcessed; + int m_maxPowerIndex; + bool m_toneDetected; + Real m_maxPower; + Real *m_k; + Real *m_coef; + Real *m_u0; + Real *m_u1; + Real *m_power; +}; + + +#endif /* INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ */ diff --git a/android/app/src/main/cpp/dsp/ctcssfrequencies.cpp b/android/app/src/main/cpp/dsp/ctcssfrequencies.cpp new file mode 100644 index 0000000..c921c83 --- /dev/null +++ b/android/app/src/main/cpp/dsp/ctcssfrequencies.cpp @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software, you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY, without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "ctcssfrequencies.h" + +// The 51 tones from various standards (https://en.wikipedia.org/wiki/Continuous_Tone-Coded_Squelch_System) +const float CTCSSFrequencies::m_Freqs[] = { + 67.0, // 0 + 69.3, + 71.9, + 74.4, + 77.0, + 79.7, + 82.5, + 85.4, + 88.5, + 91.5, + 94.8, // 10 + 97.4, + 100.0, + 103.5, + 107.2, + 110.9, + 114.8, + 118.8, + 123.0, + 127.3, + 131.8, // 20 + 136.5, + 141.3, + 146.2, + 150.0, + 151.4, + 156.7, + 159.8, + 162.2, + 165.5, + 167.9, // 30 + 171.3, + 173.8, + 177.3, + 179.9, + 183.5, + 186.2, + 189.9, + 192.8, + 196.6, + 199.5, // 40 + 203.5, + 206.5, + 210.7, + 218.1, + 225.7, + 229.1, + 233.6, + 241.8, + 250.3, + 254.1, // 50 +}; + +const int CTCSSFrequencies::m_nbFreqs = 51; diff --git a/android/app/src/main/cpp/dsp/ctcssfrequencies.h b/android/app/src/main/cpp/dsp/ctcssfrequencies.h new file mode 100644 index 0000000..ec7564e --- /dev/null +++ b/android/app/src/main/cpp/dsp/ctcssfrequencies.h @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "export.h" + +struct SDRBASE_API CTCSSFrequencies { + static const int m_nbFreqs; + static const float m_Freqs[]; +}; diff --git a/android/app/src/main/cpp/dsp/cudavkfftengine.cpp b/android/app/src/main/cpp/dsp/cudavkfftengine.cpp new file mode 100644 index 0000000..4a7eeab --- /dev/null +++ b/android/app/src/main/cpp/dsp/cudavkfftengine.cpp @@ -0,0 +1,155 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/cudavkfftengine.h" +#include "util/profiler.h" + +CUDAvkFFTEngine::CUDAvkFFTEngine() +{ + VkFFTResult resFFT; + resFFT = gpuInit(); + if (resFFT != VKFFT_SUCCESS) + { + qDebug() << "CUDAvkFFTEngine::CUDAvkFFTEngine: Failed to initialise GPU" << getVkFFTErrorString(resFFT); + delete vkGPU; + vkGPU = nullptr; + } +} + +CUDAvkFFTEngine::~CUDAvkFFTEngine() +{ + if (vkGPU) + { + freeAll(); + cuCtxDestroy(vkGPU->context); + } +} + +const QString CUDAvkFFTEngine::m_name = "vkFFT (CUDA)"; + +QString CUDAvkFFTEngine::getName() const +{ + return m_name; +} + +VkFFTResult CUDAvkFFTEngine::gpuInit() +{ + CUresult res = CUDA_SUCCESS; + cudaError_t res2 = cudaSuccess; + res = cuInit(0); + if (res != CUDA_SUCCESS) { + return VKFFT_ERROR_FAILED_TO_INITIALIZE; + } + res2 = cudaSetDevice((int)vkGPU->device_id); + if (res2 != cudaSuccess) { + return VKFFT_ERROR_FAILED_TO_SET_DEVICE_ID; + } + res = cuDeviceGet(&vkGPU->device, (int)vkGPU->device_id); + if (res != CUDA_SUCCESS) { + return VKFFT_ERROR_FAILED_TO_GET_DEVICE; + } + res = cuDevicePrimaryCtxRetain(&vkGPU->context, (int)vkGPU->device); + if (res != CUDA_SUCCESS) { + return VKFFT_ERROR_FAILED_TO_CREATE_CONTEXT; + } + return VKFFT_SUCCESS; +} + +VkFFTResult CUDAvkFFTEngine::gpuAllocateBuffers() +{ + cudaError_t res; + CUDAPlan *plan = reinterpret_cast(m_currentPlan); + + // Allocate DMA accessible pinned memory, which may be faster than malloc'ed memory + res = cudaHostAlloc(&plan->m_in, sizeof(Complex) * plan->n, cudaHostAllocMapped); + if (res != cudaSuccess) { + return VKFFT_ERROR_FAILED_TO_ALLOCATE; + } + res = cudaHostAlloc(&plan->m_out, sizeof(Complex) * plan->n, cudaHostAllocMapped); + if (res != cudaSuccess) { + return VKFFT_ERROR_FAILED_TO_ALLOCATE; + } + + // Allocate GPU memory + res = cudaMalloc((void**)&plan->m_buffer, sizeof(cuFloatComplex) * plan->n * 2); + if (res != cudaSuccess) { + return VKFFT_ERROR_FAILED_TO_ALLOCATE; + } + + plan->m_configuration->buffer = (void**)&plan->m_buffer; + + return VKFFT_SUCCESS; +} + +VkFFTResult CUDAvkFFTEngine::gpuConfigure() +{ + return VKFFT_SUCCESS; +} + +void CUDAvkFFTEngine::transform() +{ + if (m_currentPlan) + { + CUDAPlan *plan = reinterpret_cast(m_currentPlan); + cudaError_t res = cudaSuccess; + void* buffer = ((void**)&plan->m_buffer)[0]; + + // Transfer input from CPU to GPU memory + PROFILER_START() + res = cudaMemcpy(buffer, plan->m_in, plan->m_bufferSize, cudaMemcpyHostToDevice); + PROFILER_STOP(QString("%1 TX %2").arg(getName()).arg(m_currentPlan->n)) + if (res != cudaSuccess) { + qDebug() << "CUDAvkFFTEngine::transform: cudaMemcpy host to device failed"; + } + + // Perform FFT + PROFILER_RESTART() + VkFFTLaunchParams launchParams = {}; + VkFFTResult resFFT = VkFFTAppend(plan->m_app, plan->m_inverse ? 1 : -1, &launchParams); + PROFILER_STOP(QString("%1 FFT %2").arg(getName()).arg(m_currentPlan->n)) + if (resFFT != VKFFT_SUCCESS) { + qDebug() << "CUDAvkFFTEngine::transform: VkFFTAppend failed:" << getVkFFTErrorString(resFFT); + } + + // Transfer result from GPU to CPU memory + PROFILER_RESTART() + res = cudaMemcpy(plan->m_out, buffer, plan->m_bufferSize, cudaMemcpyDeviceToHost); + PROFILER_STOP(QString("%1 RX %2").arg(getName()).arg(m_currentPlan->n)) + if (res != cudaSuccess) { + qDebug() << "CUDAvkFFTEngine::transform: cudaMemcpy device to host failed"; + } + } + +} + +vkFFTEngine::Plan *CUDAvkFFTEngine::gpuAllocatePlan() +{ + return new CUDAPlan(); +} + +void CUDAvkFFTEngine::gpuDeallocatePlan(Plan *p) +{ + CUDAPlan *plan = reinterpret_cast(p); + + cudaFree(plan->m_in); + plan->m_in = nullptr; + cudaFree(plan->m_out); + plan->m_out = nullptr; + cudaFree(plan->m_buffer); +} diff --git a/android/app/src/main/cpp/dsp/cudavkfftengine.h b/android/app/src/main/cpp/dsp/cudavkfftengine.h new file mode 100644 index 0000000..b6d9c89 --- /dev/null +++ b/android/app/src/main/cpp/dsp/cudavkfftengine.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CUDAVKFFTENGINE_H +#define INCLUDE_CUDAVKFFTENGINE_H + +#include "vkfftengine.h" + +#include +#include +#include +#include +#include + +class SDRBASE_API CUDAvkFFTEngine : public vkFFTEngine { +public: + CUDAvkFFTEngine(); + virtual ~CUDAvkFFTEngine(); + + void transform() override; + QString getName() const override; + static const QString m_name; + +protected: + + struct CUDAPlan : Plan { + cuFloatComplex* m_buffer; // GPU memory + }; + + VkFFTResult gpuInit() override; + VkFFTResult gpuAllocateBuffers() override; + VkFFTResult gpuConfigure() override; + Plan *gpuAllocatePlan() override; + void gpuDeallocatePlan(Plan *) override; + +}; + +#endif // INCLUDE_CUDAVKFFTENGINE_H diff --git a/android/app/src/main/cpp/dsp/cwkeyer.cpp b/android/app/src/main/cpp/dsp/cwkeyer.cpp new file mode 100644 index 0000000..609cbf6 --- /dev/null +++ b/android/app/src/main/cpp/dsp/cwkeyer.cpp @@ -0,0 +1,625 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019, 2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "cwkeyer.h" +#include "util/stepfunctions.h" + +MESSAGE_CLASS_DEFINITION(CWKeyer::MsgConfigureCWKeyer, Message) + +/** + * 0: dot + * 1: dash + * -1: end of sequence + */ +const signed char CWKeyer::m_asciiToMorse[128][7] = { + {-1,0,0,0,0,0,0}, // 0 + {-1,0,0,0,0,0,0}, // 1 + {-1,0,0,0,0,0,0}, // 2 + {-1,0,0,0,0,0,0}, // 3 + {-1,0,0,0,0,0,0}, // 4 + {-1,0,0,0,0,0,0}, // 5 + {-1,0,0,0,0,0,0}, // 6 + {-1,0,0,0,0,0,0}, // 7 + {-1,0,0,0,0,0,0}, // 8 + {-1,0,0,0,0,0,0}, // 9 + {-1,0,0,0,0,0,0}, // 10 + {-1,0,0,0,0,0,0}, // 11 + {-1,0,0,0,0,0,0}, // 12 + {-1,0,0,0,0,0,0}, // 13 + {-1,0,0,0,0,0,0}, // 14 + {-1,0,0,0,0,0,0}, // 15 + {-1,0,0,0,0,0,0}, // 16 + {-1,0,0,0,0,0,0}, // 17 + {-1,0,0,0,0,0,0}, // 18 + {-1,0,0,0,0,0,0}, // 19 + {-1,0,0,0,0,0,0}, // 20 + {-1,0,0,0,0,0,0}, // 21 + {-1,0,0,0,0,0,0}, // 22 + {-1,0,0,0,0,0,0}, // 23 + {-1,0,0,0,0,0,0}, // 24 + {-1,0,0,0,0,0,0}, // 25 + {-1,0,0,0,0,0,0}, // 26 + {-1,0,0,0,0,0,0}, // 27 + {-1,0,0,0,0,0,0}, // 28 + {-1,0,0,0,0,0,0}, // 29 + {-1,0,0,0,0,0,0}, // 30 + {-1,0,0,0,0,0,0}, // 31 + {-1,0,0,0,0,0,0}, // 32 space is treated as word separator + {1,0,1,0,1,1,-1}, // 33 ! + {0,1,0,0,1,0,-1}, // 34 " + {-1,0,0,0,0,0,0}, // 35 + {-1,0,0,0,0,0,0}, // 36 + {-1,0,0,0,0,0,0}, // 37 + {-1,0,0,0,0,0,0}, // 38 + {0,1,1,1,1,0,-1}, // 39 ' + {1,0,1,1,0,1,-1}, // 40 ( + {1,0,1,1,0,1,-1}, // 41 ) + {-1,0,0,0,0,0,0}, // 42 + {0,1,0,1,0,-1,0}, // 43 + + {1,1,0,0,1,1,-1}, // 44 , + {1,0,0,0,0,1,-1}, // 45 - + {0,1,0,1,0,1,-1}, // 46 . + {1,0,0,1,0,-1,0}, // 47 / + {1,1,1,1,1,-1,0}, // 48 0 + {0,1,1,1,1,-1,0}, // 49 1 + {0,0,1,1,1,-1,0}, // 50 2 + {0,0,0,1,1,-1,0}, // 51 3 + {0,0,0,0,1,-1,0}, // 52 4 + {0,0,0,0,0,-1,0}, // 53 5 + {1,0,0,0,0,-1,0}, // 54 6 + {1,1,0,0,0,-1,0}, // 55 7 + {1,1,1,0,0,-1,0}, // 56 8 + {1,1,1,1,0,-1,0}, // 57 9 + {1,1,1,0,0,0,-1}, // 58 : + {1,0,1,0,1,0,-1}, // 59 ; + {-1,0,0,0,0,0,0}, // 60 < + {1,0,0,0,1,-1,0}, // 61 = + {-1,0,0,0,0,0,0}, // 62 > + {0,0,1,1,0,0,-1}, // 63 ? + {0,1,1,0,1,0,-1}, // 64 @ + {0,1,-1,0,0,0,0}, // 65 A + {1,0,0,0,-1,0,0}, // 66 B + {1,0,1,0,-1,0,0}, // 67 C + {1,0,0,-1,0,0,0}, // 68 D + {0,-1,0,0,0,0,0}, // 69 E + {0,0,1,0,-1,0,0}, // 70 F + {1,1,0,-1,0,0,0}, // 71 G + {0,0,0,0,-1,0,0}, // 72 H + {0,0,-1,0,0,0,0}, // 73 I + {0,1,1,1,-1,0,0}, // 74 J + {1,0,1,-1,0,0,0}, // 75 K + {0,1,0,0,-1,0,0}, // 76 L + {1,1,-1,0,0,0,0}, // 77 M + {1,0,-1,0,0,0,0}, // 78 N + {1,1,1,-1,0,0,0}, // 79 O + {0,1,1,0,-1,0,0}, // 80 P + {1,1,0,1,-1,0,0}, // 81 Q + {0,1,0,-1,0,0,0}, // 82 R + {0,0,0,-1,0,0,0}, // 83 S + {1,-1,0,0,0,0,0}, // 84 T + {0,0,1,-1,0,0,0}, // 85 U + {0,0,0,1,-1,0,0}, // 86 V + {0,1,1,-1,0,0,0}, // 87 W + {1,0,0,1,-1,0,0}, // 88 X + {1,0,1,1,-1,0,0}, // 89 Y + {1,1,0,0,-1,0,0}, // 90 Z + {-1,0,0,0,0,0,0}, // 91 [ + {-1,0,0,0,0,0,0}, // 92 back / + {-1,0,0,0,0,0,0}, // 93 ] + {-1,0,0,0,0,0,0}, // 94 ^ + {-1,0,0,0,0,0,0}, // 95 _ + {-1,0,0,0,0,0,0}, // 96 ` + {0,1,-1,0,0,0,0}, // 97 A lowercase same as uppercase + {1,0,0,0,-1,0,0}, // 98 B + {1,0,1,0,-1,0,0}, // 99 C + {1,0,0,-1,0,0,0}, // 100 D + {0,-1,0,0,0,0,0}, // 101 E + {0,0,1,0,-1,0,0}, // 102 F + {1,1,0,-1,0,0,0}, // 103 G + {0,0,0,0,-1,0,0}, // 104 H + {0,0,-1,0,0,0,0}, // 105 I + {0,1,1,1,-1,0,0}, // 106 J + {1,0,1,-1,0,0,0}, // 107 K + {0,1,0,0,-1,0,0}, // 108 L + {1,1,-1,0,0,0,0}, // 109 M + {1,0,-1,0,0,0,0}, // 110 N + {1,1,1,-1,0,0,0}, // 111 O + {0,1,1,0,-1,0,0}, // 112 P + {1,1,0,1,-1,0,0}, // 113 Q + {0,1,0,-1,0,0,0}, // 114 R + {0,0,0,-1,0,0,0}, // 115 S + {1,-1,0,0,0,0,0}, // 116 T + {0,0,1,-1,0,0,0}, // 117 U + {0,0,0,1,-1,0,0}, // 118 V + {0,1,1,-1,0,0,0}, // 119 W + {1,0,0,1,-1,0,0}, // 120 X + {1,0,1,1,-1,0,0}, // 121 Y + {1,1,0,0,-1,0,0}, // 122 Z + {-1,0,0,0,0,0,0}, // 123 { + {-1,0,0,0,0,0,0}, // 124 | + {-1,0,0,0,0,0,0}, // 125 } + {-1,0,0,0,0,0,0}, // 126 ~ + {-1,0,0,0,0,0,0}, // 127 DEL +}; + +CWKeyer::CWKeyer() : + m_textPointer(0), + m_elementPointer(0), + m_samplePointer(0), + m_elementSpace(false), + m_characterSpace(false), + m_key(false), + m_dot(false), + m_dash(false), + m_elementOn(false), + m_asciiChar('\0'), + m_keyIambicState(KeySilent), + m_textState(TextStart) +{ + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + applySettings(m_settings, true); +} + +CWKeyer::~CWKeyer() +{ +} + +void CWKeyer::setSampleRate(int sampleRate) +{ + CWKeyerSettings settings = m_settings; + settings.m_sampleRate = sampleRate; + MsgConfigureCWKeyer *msg = MsgConfigureCWKeyer::create(settings, false); + m_inputMessageQueue.push(msg); +} + +int CWKeyer::getSample() +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_settings.m_mode == CWKeyerSettings::CWText) + { + nextStateText(); + return m_key ? 1 : 0; + } + else if ((m_settings.m_mode == CWKeyerSettings::CWDots) || (m_settings.m_mode == CWKeyerSettings::CWDashes)) + { + nextStateIambic(); + return m_key ? 1 : 0; + } + else if (m_settings.m_mode == CWKeyerSettings::CWKeyboard) + { + if (m_settings.m_keyboardIambic) + { + nextStateIambic(); + return m_key ? 1 : 0; + } + else + { + return (m_dot || m_dash) ? 1 : 0; + } + } + else + { + return 0; + } +} + +void CWKeyer::nextStateIambic() +{ + switch (m_keyIambicState) + { + case KeySilent: + if (m_dot) + { + m_keyIambicState = KeyDot; + m_samplePointer = 0; + } + else if (m_dash) + { + m_keyIambicState = KeyDash; + m_samplePointer = 0; + } + + m_key = false; + break; + case KeyDot: + if (m_samplePointer < m_dotLength) // dot key + { + m_key = true; + m_samplePointer++; + } + else if (m_samplePointer < 2*m_dotLength) // dot silence (+1 dot length) + { + m_key = false; + m_samplePointer++; + } + else // end + { + if (m_dash) + { + m_keyIambicState = KeyDash; + } + else if (!m_dot) + { + m_keyIambicState = KeySilent; + } + + m_samplePointer = 0; + m_key = false; + } + break; + case KeyDash: + if (m_samplePointer < 3*m_dotLength) // dash key + { + m_key = true; + m_samplePointer++; + } + else if (m_samplePointer < 4*m_dotLength) // dash silence (+1 dot length) + { + m_key = false; + m_samplePointer++; + } + else // end + { + if (m_dot) + { + m_keyIambicState = KeyDot; + } + else if (!m_dash) + { + m_keyIambicState = KeySilent; + } + + m_samplePointer = 0; + m_key = false; + } + break; + default: + m_samplePointer = 0; + m_key = false; + break; + } +} + +void CWKeyer::nextStateText() +{ + switch (m_textState) + { + case TextStart: + m_samplePointer = 0; + m_elementPointer = 0; + m_textPointer = 0; + m_textState = TextStartChar; + m_key = false; + m_dot = false; + m_dash = false; + break; + case TextStartChar: + m_samplePointer = 0; + m_elementPointer = 0; + if (m_textPointer < m_settings.m_text.length()) + { + m_asciiChar = (m_settings.m_text.at(m_textPointer)).toLatin1(); +// qDebug() << "CWKeyer::nextStateText: TextStartChar: " << m_asciiChar; + + if (m_asciiChar < 0) { // non ASCII + m_asciiChar = 0; + } + + if (m_asciiChar == ' ') + { + m_textState = TextWordSpace; + } + else + { + m_textState = TextStartElement; + } + m_textPointer++; + } + else // end of text + { + m_textState = TextEnd; + } + break; + case TextStartElement: + m_samplePointer = 0; +// qDebug() << "CWKeyer::nextStateText: TextStartElement: " << (int) m_asciiToMorse[m_asciiChar][m_elementPointer]; + if (m_asciiToMorse[(uint8_t)(m_asciiChar&0x7F)][m_elementPointer] == -1) // end of morse character + { + m_elementPointer = 0; + m_textState = TextCharSpace; + } + else + { + if (m_asciiToMorse[(uint8_t)(m_asciiChar&0x7F)][m_elementPointer] == 0) // dot + { + m_dot = true; + m_dash = false; + } + else // dash + { + m_dot = false; + m_dash = true; + } + m_keyIambicState = KeySilent; // reset iambic state + nextStateIambic(); // init dash or dot + m_dot = false; // release keys + m_dash = false; + m_textState = TextElement; + m_elementPointer++; + } + break; + case TextElement: + nextStateIambic(); // dash or dot + if (m_keyIambicState == KeySilent) // done + { + m_textState = TextStartElement; // next element + } + break; + case TextCharSpace: + if (m_samplePointer < 2*m_dotLength) // - 1 dot length space from element + { + m_samplePointer++; + m_key = false; + } + else + { + m_textState = TextStartChar; + } + break; + case TextWordSpace: + if (m_samplePointer < 4*m_dotLength) // - 3 dot length space from character + { + m_samplePointer++; + m_key = false; + } + else + { + m_textState = TextStartChar; + } + break; + case TextEnd: + if (m_settings.m_loop) + { + m_textState = TextStart; + } + m_key = false; + m_dot = false; + m_dash = false; + break; + case TextStop: + default: + m_key = false; + m_dot = false; + m_dash = false; + break; + } +} + +bool CWKeyer::eom() +{ + return !(m_textPointer < m_settings.m_text.length()); +} + +void CWKeyer::setKeyboardDots() +{ + m_dot = true; + m_dash = false; + m_keyIambicState = KeySilent; +} + +void CWKeyer::setKeyboardDashes() +{ + m_dot = false; + m_dash = true; + m_keyIambicState = KeySilent; +} + +void CWKeyer::setKeyboardSilence() +{ + m_dot = false; + m_dash = false; +} + +CWSmoother::CWSmoother() : + m_fadeInCounter(0), + m_fadeOutCounter(0), + m_nbFadeSamples(0), + m_fadeInSamples(0), + m_fadeOutSamples(0) +{ + setNbFadeSamples(192); // default is 4 ms at 48 kHz sample rate +} + +CWSmoother::~CWSmoother() +{ + delete[] m_fadeInSamples; + delete[] m_fadeOutSamples; +} + +void CWSmoother::setNbFadeSamples(unsigned int nbFadeSamples) +{ + if (nbFadeSamples != m_nbFadeSamples) + { + QMutexLocker mutexLocker(&m_mutex); + + m_nbFadeSamples = nbFadeSamples; + + if (m_fadeInSamples) delete[] m_fadeInSamples; + if (m_fadeOutSamples) delete[] m_fadeOutSamples; + + m_fadeInSamples = new float[m_nbFadeSamples]; + m_fadeOutSamples = new float[m_nbFadeSamples]; + + for (unsigned int i = 0; i < m_nbFadeSamples; i++) + { + float x = i/ (float) m_nbFadeSamples; + float y = 1.0f -x; + + m_fadeInSamples[i] = StepFunctions::smootherstep(x); + m_fadeOutSamples[i] = StepFunctions::smootherstep(y); + } + + m_fadeInCounter = 0; + m_fadeOutCounter = 0; + } +} + +bool CWSmoother::getFadeSample(bool on, float& sample) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (on) + { + m_fadeOutCounter = 0; + + if (m_fadeInCounter < m_nbFadeSamples) + { + sample = m_fadeInSamples[m_fadeInCounter]; + m_fadeInCounter++; + return true; + } + else + { + sample = 1.0f; + return false; + } + } + else + { + m_fadeInCounter = 0; + + if (m_fadeOutCounter < m_nbFadeSamples) + { + sample = m_fadeOutSamples[m_fadeOutCounter]; + m_fadeOutCounter++; + return true; + } + else + { + sample = 0.0f; + return false; + } + } +} + +bool CWKeyer::handleMessage(const Message& cmd) +{ + if (MsgConfigureCWKeyer::match(cmd)) + { + MsgConfigureCWKeyer& cfg = (MsgConfigureCWKeyer&) cmd; + qDebug() << "CWKeyer::handleMessage: MsgConfigureCWKeyer"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + + return true; +} + +void CWKeyer::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +void CWKeyer::applySettings(const CWKeyerSettings& settings, bool force) +{ + qDebug() << "CWKeyer::applySettings: " + << " m_dashKey: " << settings.m_dashKey + << " m_dashKeyModifiers: " << settings.m_dashKeyModifiers + << " m_dotKey: " << settings.m_dotKey + << " m_dotKeyModifiers: " << settings.m_dotKeyModifiers + << " m_keyboardIambic: " << settings.m_keyboardIambic + << " m_loop: " << settings.m_loop + << " m_mode: " << settings.m_mode + << " m_sampleRate: " << settings.m_sampleRate + << " m_text: " << settings.m_text + << " m_wpm: " << settings.m_wpm; + + if ((m_settings.m_wpm != settings.m_wpm) + || (m_settings.m_sampleRate != settings.m_sampleRate) || force) + { + QMutexLocker mutexLocker(&m_mutex); + m_dotLength = (int) ((1.2f / settings.m_wpm) * settings.m_sampleRate); + m_cwSmoother.setNbFadeSamples(m_dotLength/10); // 10% the dot time + } + + if ((m_settings.m_mode != settings.m_mode) || force) + { + QMutexLocker mutexLocker(&m_mutex); + + if (settings.m_mode == CWKeyerSettings::CWText) + { + m_textState = TextStart; + } + else if (settings.m_mode == CWKeyerSettings::CWDots) + { + m_dot = true; + m_dash = false; + m_keyIambicState = KeySilent; + } + else if (settings.m_mode == CWKeyerSettings::CWDashes) + { + m_dot = false; + m_dash = true; + m_keyIambicState = KeySilent; + } + else if (settings.m_mode == CWKeyerSettings::CWKeyboard) + { + m_dot = false; + m_dash = false; + m_keyIambicState = KeySilent; + } + } + + if ((m_settings.m_text != settings.m_text) || force) + { + QMutexLocker mutexLocker(&m_mutex); + m_textState = TextStart; + } + + m_settings = settings; +} + +void CWKeyer::webapiSettingsPutPatch( + const QStringList& channelSettingsKeys, + CWKeyerSettings& cwKeyerSettings, + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings +) +{ + cwKeyerSettings.updateFrom(channelSettingsKeys, apiCwKeyerSettings); +} + +void CWKeyer::webapiFormatChannelSettings( + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings, + const CWKeyerSettings& cwKeyerSettings +) +{ + cwKeyerSettings.formatTo(apiCwKeyerSettings); +} diff --git a/android/app/src/main/cpp/dsp/cwkeyer.h b/android/app/src/main/cpp/dsp/cwkeyer.h new file mode 100644 index 0000000..15f4865 --- /dev/null +++ b/android/app/src/main/cpp/dsp/cwkeyer.h @@ -0,0 +1,163 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_CWKEYER_H_ +#define SDRBASE_DSP_CWKEYER_H_ + +#include +#include + +#include "export.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "cwkeyersettings.h" +#include "SWGChannelSettings.h" + +/** + * Ancillary class to smooth out CW transitions with a sine shape + */ +class SDRBASE_API CWSmoother +{ +public: + CWSmoother(); + ~CWSmoother(); + + void setNbFadeSamples(unsigned int nbFadeSamples); + bool getFadeSample(bool on, float& sample); + +private: + QRecursiveMutex m_mutex; + unsigned int m_fadeInCounter; + unsigned int m_fadeOutCounter; + unsigned int m_nbFadeSamples; + float *m_fadeInSamples; + float *m_fadeOutSamples; +}; + +class SDRBASE_API CWKeyer : public QObject { + Q_OBJECT + +public: + class SDRBASE_API MsgConfigureCWKeyer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const CWKeyerSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureCWKeyer* create(const CWKeyerSettings& settings, bool force) + { + return new MsgConfigureCWKeyer(settings, force); + } + + private: + CWKeyerSettings m_settings; + bool m_force; + + MsgConfigureCWKeyer(const CWKeyerSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + enum CWKeyIambicState + { + KeySilent, + KeyDot, + KeyDash + }; + + enum CWTextState + { + TextStart, + TextStartChar, + TextStartElement, + TextElement, + TextCharSpace, + TextWordSpace, + TextEnd, + TextStop + }; + + CWKeyer(); + ~CWKeyer(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + + void setSampleRate(int sampleRate); + const CWKeyerSettings& getSettings() const { return m_settings; } + + void reset() { m_keyIambicState = KeySilent; } + + CWSmoother& getCWSmoother() { return m_cwSmoother; } + int getSample(); + bool eom(); + void resetText() { m_textState = TextStart; } + void stopText() { m_textState = TextStop; } + void setKeyboardDots(); + void setKeyboardDashes(); + void setKeyboardSilence(); + + static void webapiSettingsPutPatch( + const QStringList& channelSettingsKeys, + CWKeyerSettings& cwKeyerSettings, + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings + ); + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings, + const CWKeyerSettings& cwKeyerSettings + ); + +private: + QRecursiveMutex m_mutex; + CWKeyerSettings m_settings; + MessageQueue m_inputMessageQueue; + int m_dotLength; //!< dot length in samples + int m_textPointer; + int m_elementPointer; + int m_samplePointer; + bool m_elementSpace; + bool m_characterSpace; + bool m_key; + bool m_dot; + bool m_dash; + bool m_elementOn; + signed char m_asciiChar; + CWKeyIambicState m_keyIambicState; + CWTextState m_textState; + CWSmoother m_cwSmoother; + + static const signed char m_asciiToMorse[128][7]; + + void applySettings(const CWKeyerSettings& settings, bool force = false); + bool handleMessage(const Message& cmd); + void nextStateIambic(); + void nextStateText(); + +private slots: + void handleInputMessages(); +}; + +#endif /* SDRBASE_DSP_CWKEYER_H_ */ diff --git a/android/app/src/main/cpp/dsp/cwkeyersettings.cpp b/android/app/src/main/cpp/dsp/cwkeyersettings.cpp new file mode 100644 index 0000000..d6dba63 --- /dev/null +++ b/android/app/src/main/cpp/dsp/cwkeyersettings.cpp @@ -0,0 +1,158 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2017, 2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGCWKeyerSettings.h" +#include "util/simpleserializer.h" +#include "cwkeyersettings.h" + +CWKeyerSettings::CWKeyerSettings() +{ + resetToDefaults(); +} + +void CWKeyerSettings::resetToDefaults() +{ + m_loop = false; + m_mode = CWNone; + m_sampleRate = 48000; + m_text = ""; + m_wpm = 13; + m_keyboardIambic = true; + m_dotKey = Qt::Key_Period; + m_dotKeyModifiers = Qt::NoModifier; + m_dashKey = Qt::Key_Minus; + m_dashKeyModifiers = Qt::NoModifier; +} + +QByteArray CWKeyerSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeBool(2, m_loop); + s.writeS32(3, (int) m_mode); + s.writeS32(4, m_sampleRate); + s.writeString(5, m_text); + s.writeS32(6, m_wpm); + s.writeS32(7, (int) m_dotKey); + s.writeU32(8, (unsigned int) m_dotKeyModifiers); + s.writeS32(9, (int) m_dashKey); + s.writeU32(10, (unsigned int) m_dashKeyModifiers); + s.writeBool(11, m_keyboardIambic); + + return s.final(); +} + +bool CWKeyerSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intval; + unsigned int uintval; + + d.readBool(2, &m_loop, false); + d.readS32(3, &intval, 0); + m_mode = (CWMode) intval; + d.readS32(4, &m_sampleRate, 48000); + d.readString(5, &m_text, ""); + d.readS32(6, &m_wpm, 13); + d.readS32(7, &intval, (int) Qt::Key_Period); + m_dotKey = (Qt::Key) (intval < 0 ? 0 : intval); + d.readU32(8, &uintval, 0); + m_dotKeyModifiers = (Qt::KeyboardModifiers) uintval; + d.readS32(9, &intval, (int) Qt::Key_Minus); + m_dashKey = (Qt::Key) (intval < 0 ? 0 : intval); + d.readU32(10, &uintval, 0); + m_dashKeyModifiers = (Qt::KeyboardModifiers) uintval; + d.readBool(11, &m_keyboardIambic, true); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +void CWKeyerSettings::formatTo(SWGSDRangel::SWGObject *swgObject) const +{ + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = static_cast(swgObject); + + apiCwKeyerSettings->setLoop(m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) m_mode); + apiCwKeyerSettings->setSampleRate(m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = m_text; + } else { + apiCwKeyerSettings->setText(new QString(m_text)); + } + + apiCwKeyerSettings->setWpm(m_wpm); + apiCwKeyerSettings->setKeyboardIambic(m_keyboardIambic ? 1 : 0); + apiCwKeyerSettings->setDotKey((int) m_dotKey); + apiCwKeyerSettings->setDotKeyModifiers((unsigned int) m_dotKeyModifiers); + apiCwKeyerSettings->setDashKey((int) m_dashKey); + apiCwKeyerSettings->setDashKeyModifiers((unsigned int) m_dashKeyModifiers); +} + +void CWKeyerSettings::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject) +{ + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = + static_cast(const_cast(swgObject)); + + if (keys.contains("cwKeyer.loop")) { + m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (keys.contains("cwKeyer.mode")) { + m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (keys.contains("cwKeyer.text")) { + m_text = *apiCwKeyerSettings->getText(); + } + if (keys.contains("cwKeyer.sampleRate")) { + m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (keys.contains("cwKeyer.wpm")) { + m_wpm = apiCwKeyerSettings->getWpm(); + } + if (keys.contains("cwKeyer.keyboardIambic")) { + m_keyboardIambic = apiCwKeyerSettings->getKeyboardIambic() != 0; + } + if (keys.contains("cwKeyer.dotKey")) { + m_dotKey = (Qt::Key) apiCwKeyerSettings->getDotKey(); + } + if (keys.contains("cwKeyer.dotKeyModifiers")) { + m_dotKeyModifiers = (Qt::KeyboardModifiers) apiCwKeyerSettings->getDotKeyModifiers(); + } + if (keys.contains("cwKeyer.dashKey")) { + m_dashKey = (Qt::Key) apiCwKeyerSettings->getDashKey(); + } + if (keys.contains("cwKeyer.dashKeyModifiers")) { + m_dashKeyModifiers = (Qt::KeyboardModifiers) apiCwKeyerSettings->getDashKeyModifiers(); + } +} diff --git a/android/app/src/main/cpp/dsp/cwkeyersettings.h b/android/app/src/main/cpp/dsp/cwkeyersettings.h new file mode 100644 index 0000000..7816610 --- /dev/null +++ b/android/app/src/main/cpp/dsp/cwkeyersettings.h @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_CWKEYERSETTINGS_H_ +#define SDRBASE_DSP_CWKEYERSETTINGS_H_ + +#include +#include + +#include "export.h" +#include "settings/serializable.h" + +class SDRBASE_API CWKeyerSettings: public Serializable +{ +public: + typedef enum + { + CWNone, + CWText, + CWDots, + CWDashes, + CWKeyboard + } CWMode; + + bool m_loop; + CWMode m_mode; + int m_sampleRate; + QString m_text; + int m_wpm; + bool m_keyboardIambic; + Qt::Key m_dotKey; + Qt::KeyboardModifiers m_dotKeyModifiers; + Qt::Key m_dashKey; + Qt::KeyboardModifiers m_dashKeyModifiers; + + CWKeyerSettings(); + void resetToDefaults(); + + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const; + virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject); +}; + + + +#endif /* SDRBASE_DSP_CWKEYERSETTINGS_H_ */ diff --git a/android/app/src/main/cpp/dsp/datafifo.cpp b/android/app/src/main/cpp/dsp/datafifo.cpp new file mode 100644 index 0000000..7995ae1 --- /dev/null +++ b/android/app/src/main/cpp/dsp/datafifo.cpp @@ -0,0 +1,314 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016, 2018-2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "datafifo.h" + +void DataFifo::create(unsigned int s) +{ + m_size = 0; + m_fill = 0; + m_head = 0; + m_tail = 0; + + m_data.resize(s); + m_size = m_data.size(); +} + +void DataFifo::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_suppressed = -1; + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +DataFifo::DataFifo(QObject* parent) : + QObject(parent), + m_data(), + m_currentDataType(DataTypeI16) +{ + setObjectName("DataFifo"); + m_suppressed = -1; + m_size = 0; + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +DataFifo::DataFifo(int size, QObject* parent) : + QObject(parent), + m_data(), + m_currentDataType(DataTypeI16) +{ + setObjectName("DataFifo"); + m_suppressed = -1; + create(size); +} + +DataFifo::DataFifo(const DataFifo& other) : + QObject(other.parent()), + m_data(other.m_data), + m_currentDataType(DataTypeI16) +{ + setObjectName("DataFifo"); + m_suppressed = -1; + m_size = m_data.size(); + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +DataFifo::~DataFifo() +{ + QMutexLocker mutexLocker(&m_mutex); + m_size = 0; +} + +bool DataFifo::setSize(int size) +{ + QMutexLocker mutexLocker(&m_mutex); + create(size); + return m_data.size() == size; +} + +unsigned int DataFifo::write(const quint8* data, unsigned int count, DataType dataType) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (dataType != m_currentDataType) + { + m_suppressed = -1; + m_fill = 0; + m_head = 0; + m_tail = 0; + m_currentDataType = dataType; + } + + unsigned int total; + unsigned int remaining; + unsigned int len; + const quint8* begin = (const quint8*) data; + //count /= sizeof(Sample); + + total = std::min(count, m_size - m_fill); + + if (total < count) + { + if (m_suppressed < 0) + { + m_suppressed = 0; + m_msgRateTimer.start(); + qCritical("DataFifo::write: overflow - dropping %u samples (size=%u)", count - total, m_size); + } + else + { + if (m_msgRateTimer.elapsed() > 2500) + { + qCritical("DataFifo::write: %u messages dropped", m_suppressed); + qCritical("DataFifo::write: overflow - dropping %u samples (size=%u)", count - total, m_size); + m_suppressed = -1; + } + else + { + m_suppressed++; + } + } + } + + remaining = total; + + while (remaining > 0) + { + len = std::min(remaining, m_size - m_tail); + std::copy(begin, begin + len, m_data.begin() + m_tail); + m_tail += len; + m_tail %= m_size; + m_fill += len; + begin += len; + remaining -= len; + } + + if (m_fill > 0) { + emit dataReady(); + } + + return total; +} + +unsigned int DataFifo::write(QByteArray::const_iterator begin, QByteArray::const_iterator end, DataType dataType) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (dataType != m_currentDataType) + { + m_suppressed = -1; + m_fill = 0; + m_head = 0; + m_tail = 0; + m_currentDataType = dataType; + } + + unsigned int count = end - begin; + unsigned int total; + unsigned int remaining; + unsigned int len; + + total = std::min(count, m_size - m_fill); + + if (total < count) + { + if (m_suppressed < 0) + { + m_suppressed = 0; + m_msgRateTimer.start(); + qCritical("DataFifo::write: overflow - dropping %u samples", count - total); + } + else + { + if (m_msgRateTimer.elapsed() > 2500) + { + qCritical("DataFifo::write: %u messages dropped", m_suppressed); + qCritical("DataFifo::write: overflow - dropping %u samples", count - total); + m_suppressed = -1; + } + else + { + m_suppressed++; + } + } + } + + remaining = total; + + while (remaining > 0) + { + len = std::min(remaining, m_size - m_tail); + std::copy(begin, begin + len, m_data.begin() + m_tail); + m_tail += len; + m_tail %= m_size; + m_fill += len; + begin += len; + remaining -= len; + } + + if (m_fill > 0) { + emit dataReady(); + } + + return total; +} + +unsigned int DataFifo::read(QByteArray::iterator begin, QByteArray::iterator end, DataType& dataType) +{ + QMutexLocker mutexLocker(&m_mutex); + dataType = m_currentDataType; + unsigned int count = end - begin; + unsigned int total; + unsigned int remaining; + unsigned int len; + + total = std::min(count, m_fill); + + if (total < count) { + qCritical("DataFifo::read: underflow - missing %u samples", count - total); + } + + remaining = total; + + while (remaining > 0) + { + len = std::min(remaining, m_size - m_head); + std::copy(m_data.begin() + m_head, m_data.begin() + m_head + len, begin); + m_head += len; + m_head %= m_size; + m_fill -= len; + begin += len; + remaining -= len; + } + + return total; +} + +unsigned int DataFifo::readBegin(unsigned int count, + QByteArray::iterator* part1Begin, QByteArray::iterator* part1End, + QByteArray::iterator* part2Begin, QByteArray::iterator* part2End, + DataType& dataType) +{ + QMutexLocker mutexLocker(&m_mutex); + dataType = m_currentDataType; + unsigned int total; + unsigned int remaining; + unsigned int len; + unsigned int head = m_head; + + total = std::min(count, m_fill); + + if (total < count) { + qCritical("DataFifo::readBegin: underflow - missing %u samples", count - total); + } + + remaining = total; + + if (remaining > 0) + { + len = std::min(remaining, m_size - head); + *part1Begin = m_data.begin() + head; + *part1End = m_data.begin() + head + len; + head += len; + head %= m_size; + remaining -= len; + } + else + { + *part1Begin = m_data.end(); + *part1End = m_data.end(); + } + + if (remaining > 0) + { + len = std::min(remaining, m_size - head); + *part2Begin = m_data.begin() + head; + *part2End = m_data.begin() + head + len; + } + else + { + *part2Begin = m_data.end(); + *part2End = m_data.end(); + } + + return total; +} + +unsigned int DataFifo::readCommit(unsigned int count) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (count > m_fill) + { + qCritical("DataFifo::readCommit: cannot commit more than available samples"); + count = m_fill; + } + + m_head = (m_head + count) % m_size; + m_fill -= count; + + return count; +} diff --git a/android/app/src/main/cpp/dsp/datafifo.h b/android/app/src/main/cpp/dsp/datafifo.h new file mode 100644 index 0000000..4f9f39c --- /dev/null +++ b/android/app/src/main/cpp/dsp/datafifo.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DATAFIFO_H +#define INCLUDE_DATAFIFO_H + +#include +#include +#include +#include + +#include "export.h" + +class SDRBASE_API DataFifo : public QObject { + Q_OBJECT +public: + enum DataType + { + DataTypeI16, //!< 16 bit signed integer + DataTypeCI16 //!< Complex (i.e. Re, Im pair of) 16 bit signed integer + }; + + DataFifo(QObject* parent = nullptr); + DataFifo(int size, QObject* parent = nullptr); + DataFifo(const DataFifo& other); + ~DataFifo(); + + bool setSize(int size); + void reset(); + inline unsigned int size() const { return m_size; } + inline unsigned int fill() { QMutexLocker mutexLocker(&m_mutex); unsigned int fill = m_fill; return fill; } + + unsigned int write(const quint8* data, unsigned int count, DataType dataType); + unsigned int write(QByteArray::const_iterator begin, QByteArray::const_iterator end, DataType dataType); + + unsigned int read(QByteArray::iterator begin, QByteArray::iterator end, DataType& dataType); + + unsigned int readBegin(unsigned int count, + QByteArray::iterator* part1Begin, QByteArray::iterator* part1End, + QByteArray::iterator* part2Begin, QByteArray::iterator* part2End, + DataType& daaType); + unsigned int readCommit(unsigned int count); + +signals: + void dataReady(); + +private: + QElapsedTimer m_msgRateTimer; + int m_suppressed; + QByteArray m_data; + DataType m_currentDataType; + QRecursiveMutex m_mutex; + + unsigned int m_size; + unsigned int m_fill; + unsigned int m_head; + unsigned int m_tail; + + void create(unsigned int s); +}; + +#endif // INCLUDE_DATAFIFO_H diff --git a/android/app/src/main/cpp/dsp/dcscodes.cpp b/android/app/src/main/cpp/dsp/dcscodes.cpp new file mode 100644 index 0000000..6409562 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dcscodes.cpp @@ -0,0 +1,465 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// Source: http://onfreq.com/syntorx/dcs.html // +// // +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "dcscodes.h" + +const QMap DCSCodes::m_toCanonicalCode { + {0023, 0023}, + {0340, 0023}, + {0766, 0023}, + {0025, 0025}, + {0026, 0026}, + {0566, 0026}, + {0031, 0031}, + {0374, 0031}, + {0643, 0031}, + {0032, 0032}, + {0036, 0036}, + {0137, 0036}, + {0043, 0043}, + {0355, 0043}, + {0047, 0047}, + {0375, 0047}, + {0707, 0047}, + {0051, 0051}, + {0771, 0051}, + {0520, 0051}, + {0053, 0053}, + {0054, 0054}, + {0405, 0054}, + {0675, 0054}, + {0065, 0065}, + {0301, 0065}, + {0071, 0071}, + {0603, 0071}, + {0717, 0071}, + {0746, 0071}, + {0072, 0072}, + {0470, 0072}, + {0701, 0072}, + {0073, 0073}, + {0640, 0073}, + {0074, 0074}, + {0360, 0074}, + {0721, 0074}, + {0112, 0112}, + {0250, 0112}, + {0505, 0112}, + {0512, 0112}, + {0114, 0114}, + {0327, 0114}, + {0615, 0114}, + {0115, 0115}, + {0534, 0115}, + {0674, 0115}, + {0116, 0116}, + {0060, 0116}, + {0737, 0116}, + {0122, 0122}, + {0535, 0125}, + {0125, 0125}, + {0173, 0125}, + {0131, 0131}, + {0572, 0131}, + {0702, 0131}, + {0132, 0132}, + {0605, 0132}, + {0634, 0132}, + {0714, 0132}, + {0134, 0134}, + {0273, 0134}, + {0143, 0143}, + {0333, 0143}, + {0145, 0145}, + {0525, 0145}, + {0152, 0152}, + {0366, 0152}, + {0415, 0152}, + {0155, 0155}, + {0233, 0155}, + {0660, 0155}, + {0156, 0156}, + {0517, 0156}, + {0741, 0156}, + {0162, 0162}, + {0416, 0162}, + {0553, 0162}, + {0165, 0165}, + {0354, 0165}, + {0172, 0172}, + {0057, 0172}, + {0174, 0174}, + {0142, 0174}, + {0270, 0174}, + {0205, 0205}, + {0135, 0205}, + {0610, 0205}, + {0212, 0212}, + {0253, 0212}, + {0223, 0223}, + {0350, 0223}, + {0475, 0223}, + {0750, 0223}, + {0225, 0225}, + {0536, 0225}, + {0226, 0226}, + {0104, 0226}, + {0557, 0226}, + {0243, 0243}, + {0267, 0243}, + {0342, 0243}, + {0244, 0244}, + {0176, 0244}, + {0417, 0244}, + {0245, 0245}, + {0370, 0245}, + {0246, 0246}, + {0542, 0246}, + {0653, 0246}, + {0554, 0245}, + {0251, 0251}, + {0236, 0251}, + {0704, 0251}, + {0742, 0251}, + {0252, 0252}, + {0661, 0252}, + {0255, 0255}, + {0425, 0255}, + {0261, 0261}, + {0227, 0261}, + {0567, 0261}, + {0263, 0263}, + {0213, 0263}, + {0736, 0263}, + {0265, 0265}, + {0171, 0265}, + {0426, 0265}, + {0266, 0266}, + {0655, 0266}, + {0271, 0271}, + {0427, 0271}, + {0510, 0271}, + {0762, 0271}, + {0274, 0274}, + {0652, 0274}, + {0306, 0306}, + {0147, 0306}, + {0303, 0306}, + {0761, 0306}, + {0311, 0311}, + {0330, 0311}, + {0456, 0311}, + {0561, 0311}, + {0315, 0315}, + {0321, 0315}, + {0673, 0315}, + {0325, 0325}, + {0550, 0325}, + {0626, 0325}, + {0331, 0331}, + {0372, 0331}, + {0507, 0331}, + {0332, 0332}, + {0433, 0332}, + {0552, 0332}, + {0343, 0343}, + {0324, 0343}, + {0570, 0343}, + {0346, 0346}, + {0616, 0346}, + {0635, 0346}, + {0724, 0346}, + {0351, 0351}, + {0353, 0351}, + {0435, 0351}, + {0356, 0356}, + {0521, 0356}, + {0364, 0364}, + {0130, 0364}, + {0641, 0364}, + {0365, 0365}, + {0107, 0365}, + {0371, 0371}, + {0217, 0371}, + {0453, 0371}, + {0530, 0371}, + {0411, 0411}, + {0117, 0411}, + {0756, 0411}, + {0412, 0412}, + {0127, 0412}, + {0441, 0412}, + {0711, 0412}, + {0413, 0413}, + {0133, 0413}, + {0620, 0413}, + {0423, 0423}, + {0234, 0423}, + {0563, 0423}, + {0621, 0423}, + {0713, 0423}, + {0431, 0431}, + {0262, 0431}, + {0316, 0431}, + {0730, 0431}, + {0432, 0432}, + {0432, 0432}, + {0276, 0432}, + {0326, 0432}, + {0445, 0445}, + {0222, 0445}, + {0457, 0445}, + {0575, 0445}, + {0446, 0446}, + {0467, 0446}, + {0511, 0446}, + {0672, 0446}, + {0452, 0452}, + {0524, 0452}, + {0765, 0452}, + {0454, 0454}, + {0545, 0454}, + {0513, 0454}, + {0564, 0454}, + {0455, 0455}, + {0533, 0455}, + {0551, 0455}, + {0462, 0462}, + {0462, 0462}, + {0472, 0462}, + {0623, 0462}, + {0725, 0462}, + {0464, 0464}, + {0237, 0464}, + {0642, 0464}, + {0772, 0464}, + {0465, 0465}, + {0056, 0465}, + {0656, 0465}, + {0466, 0466}, + {0144, 0466}, + {0666, 0466}, + {0503, 0503}, + {0157, 0503}, + {0322, 0503}, + {0506, 0506}, + {0224, 0506}, + {0313, 0506}, + {0574, 0506}, + {0516, 0516}, + {0067, 0516}, + {0720, 0516}, + {0523, 0523}, + {0647, 0523}, + {0726, 0523}, + {0526, 0526}, + {0562, 0526}, + {0645, 0526}, + {0532, 0532}, + {0161, 0532}, + {0345, 0532}, + {0546, 0546}, + {0317, 0546}, + {0614, 0546}, + {0751, 0546}, + {0565, 0565}, + {0307, 0565}, + {0362, 0565}, + {0606, 0606}, + {0153, 0606}, + {0630, 0606}, + {0612, 0612}, + {0254, 0612}, + {0314, 0612}, + {0706, 0612}, + {0624, 0624}, + {0075, 0624}, + {0501, 0624}, + {0627, 0627}, + {0037, 0627}, + {0560, 0627}, + {0631, 0631}, + {0231, 0631}, + {0504, 0631}, + {0636, 0631}, + {0745, 0631}, + {0632, 0632}, + {0123, 0632}, + {0657, 0632}, + {0654, 0654}, + {0163, 0654}, + {0460, 0654}, + {0607, 0654}, + {0662, 0662}, + {0363, 0662}, + {0436, 0662}, + {0443, 0662}, + {0444, 0662}, + {0664, 0664}, + {0344, 0664}, + {0471, 0664}, + {0715, 0664}, + {0703, 0703}, + {0150, 0703}, + {0256, 0703}, + {0712, 0712}, + {0136, 0712}, + {0502, 0712}, + {0723, 0723}, + {0235, 0723}, + {0671, 0723}, + {0611, 0723}, + {0731, 0731}, + {0447, 0731}, + {0473, 0731}, + {0474, 0731}, + {0744, 0731}, + {0732, 0732}, + {0164, 0732}, + {0207, 0732}, + {0734, 0734}, + {0066, 0734}, + {0743, 0743}, + {0312, 0743}, + {0515, 0743}, + {0663, 0743}, + {0754, 0754}, + {0076, 0754}, + {0203, 0754}, +}; + +const QMap DCSCodes::m_signFlip = { + {0023, 0047}, + {0025, 0244}, + {0026, 0464}, + {0031, 0627}, + {0032, 0051}, + {0043, 0445}, + {0047, 0023}, + {0051, 0032}, + {0053, 0452}, + {0054, 0413}, + {0065, 0271}, + {0071, 0306}, + {0072, 0245}, + {0073, 0506}, + {0074, 0174}, + {0114, 0712}, + {0115, 0152}, + {0116, 0754}, + {0122, 0225}, + {0125, 0365}, + {0131, 0364}, + {0132, 0546}, + {0134, 0223}, + {0143, 0412}, + {0145, 0274}, + {0152, 0115}, + {0155, 0731}, + {0156, 0265}, + {0162, 0503}, + {0165, 0251}, + {0172, 0036}, + {0174, 0074}, + {0205, 0263}, + {0212, 0356}, + {0223, 0134}, + {0225, 0122}, + {0226, 0411}, + {0243, 0351}, + {0244, 0025}, + {0245, 0072}, + {0246, 0523}, + {0251, 0165}, + {0252, 0462}, + {0255, 0511}, + {0261, 0732}, + {0263, 0205}, + {0265, 0156}, + {0266, 0454}, + {0271, 0065}, + {0274, 0145}, + {0306, 0071}, + {0311, 0664}, + {0315, 0423}, + {0325, 0526}, + {0331, 0465}, + {0332, 0455}, + {0343, 0532}, + {0346, 0612}, + {0351, 0243}, + {0356, 0212}, + {0364, 0131}, + {0365, 0125}, + {0371, 0734}, + {0411, 0226}, + {0412, 0143}, + {0413, 0054}, + {0423, 0315}, + {0431, 0723}, + {0432, 0516}, + {0445, 0043}, + {0446, 0255}, + {0452, 0053}, + {0454, 0655}, + {0455, 0332}, + {0462, 0252}, + {0464, 0026}, + {0465, 0331}, + {0466, 0662}, + {0503, 0162}, + {0506, 0073}, + {0516, 0432}, + {0523, 0246}, + {0526, 0325}, + {0532, 0343}, + {0546, 0132}, + {0565, 0703}, + {0606, 0631}, + {0612, 0346}, + {0624, 0632}, + {0627, 0031}, + {0631, 0606}, + {0632, 0624}, + {0654, 0743}, + {0662, 0466}, + {0664, 0311}, + {0703, 0565}, + {0712, 0114}, + {0723, 0431}, + {0731, 0155}, + {0732, 0261}, + {0734, 0371}, + {0743, 0654}, + {0754, 0116}, +}; + + +void DCSCodes::getCanonicalCodes(QList& codes) +{ + codes.clear(); + + for (auto code : m_toCanonicalCode.keys()) + { + if (code == m_toCanonicalCode.value(code)) { + codes.append(code); + } + } +} diff --git a/android/app/src/main/cpp/dsp/dcscodes.h b/android/app/src/main/cpp/dsp/dcscodes.h new file mode 100644 index 0000000..00d5a45 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dcscodes.h @@ -0,0 +1,35 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSP_DCSCODES_H_ +#define INCLUDE_DSP_DCSCODES_H_ + +#include +#include + +#include "export.h" + +class SDRBASE_API DCSCodes +{ +public: + static void getCanonicalCodes(QList& codes); + static const int m_nbCodes; + static const QMap m_toCanonicalCode; + static const QMap m_signFlip; +}; + +#endif // INCLUDE_DSP_DCSCODES_H_ diff --git a/android/app/src/main/cpp/dsp/decimatorc.cpp b/android/app/src/main/cpp/dsp/decimatorc.cpp new file mode 100644 index 0000000..141e3e7 --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorc.cpp @@ -0,0 +1,223 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "decimatorc.h" + +DecimatorC::DecimatorC() : + m_log2Decim(0), + m_decim(1) +{} + +void DecimatorC::setLog2Decim(unsigned int log2Decim) +{ + m_log2Decim = log2Decim; + m_decim = 1 << log2Decim; +} + +bool DecimatorC::decimate(Complex c, Complex& cd) +{ + if (m_log2Decim == 0) { // no decimation hence no translation possible + return true; + } + + if (m_log2Decim == 1) { + return decimate2(c, cd); + } else if (m_log2Decim == 2) { + return decimate4(c, cd); + } else if (m_log2Decim == 3) { + return decimate8(c, cd); + } else if (m_log2Decim == 4) { + return decimate16(c, cd); + } else if (m_log2Decim == 5) { + return decimate32(c, cd); + } else if (m_log2Decim == 6) { + return decimate64(c, cd); + } else { + return true; // no decimation + } +} + +bool DecimatorC::decimate2(Complex c, Complex& cd) +{ + float x = c.real(); + float y = c.imag(); + bool done2 = m_decimator2.workDecimateCenter(&x, &y); + + if (done2) + { + cd.real(x); + cd.imag(y); + } + + return done2; +} + +bool DecimatorC::decimate4(Complex c, Complex& cd) +{ + float x = c.real(); + float y = c.imag(); + bool done2 = m_decimator2.workDecimateCenter(&x, &y); + + if (done2) + { + bool done4 = m_decimator4.workDecimateCenter(&x, &y); + + if (done4) + { + cd.real(x); + cd.imag(y); + return true; + } + } + + return false; +} + +bool DecimatorC::decimate8(Complex c, Complex& cd) +{ + float x = c.real(); + float y = c.imag(); + bool done2 = m_decimator2.workDecimateCenter(&x, &y); + + if (done2) + { + bool done4 = m_decimator4.workDecimateCenter(&x, &y); + + if (done4) + { + bool done8 = m_decimator8.workDecimateCenter(&x, &y); + + if (done8) + { + cd.real(x); + cd.imag(y); + return true; + } + } + } + + return false; +} + +bool DecimatorC::decimate16(Complex c, Complex& cd) +{ + float x = c.real(); + float y = c.imag(); + bool done2 = m_decimator2.workDecimateCenter(&x, &y); + + if (done2) + { + bool done4 = m_decimator4.workDecimateCenter(&x, &y); + + if (done4) + { + bool done8 = m_decimator8.workDecimateCenter(&x, &y); + + if (done8) + { + bool done16 = m_decimator16.workDecimateCenter(&x, &y); + + if (done16) + { + cd.real(x); + cd.imag(y); + return true; + } + } + } + } + + return false; +} + + +bool DecimatorC::decimate32(Complex c, Complex& cd) +{ + float x = c.real(); + float y = c.imag(); + bool done2 = m_decimator2.workDecimateCenter(&x, &y); + + if (done2) + { + bool done4 = m_decimator4.workDecimateCenter(&x, &y); + + if (done4) + { + bool done8 = m_decimator8.workDecimateCenter(&x, &y); + + if (done8) + { + bool done16 = m_decimator16.workDecimateCenter(&x, &y); + + if (done16) + { + bool done32 = m_decimator32.workDecimateCenter(&x, &y); + + if (done32) + { + cd.real(x); + cd.imag(y); + return true; + } + } + } + } + } + + return false; +} + +bool DecimatorC::decimate64(Complex c, Complex& cd) +{ + float x = c.real(); + float y = c.imag(); + bool done2 = m_decimator2.workDecimateCenter(&x, &y); + + if (done2) + { + bool done4 = m_decimator4.workDecimateCenter(&x, &y); + + if (done4) + { + bool done8 = m_decimator8.workDecimateCenter(&x, &y); + + if (done8) + { + bool done16 = m_decimator16.workDecimateCenter(&x, &y); + + if (done16) + { + bool done32 = m_decimator32.workDecimateCenter(&x, &y); + + if (done32) + { + bool done64 = m_decimator32.workDecimateCenter(&x, &y); + + if (done64) + { + cd.real(x); + cd.imag(y); + return true; + } + } + } + } + } + } + + return false; +} diff --git a/android/app/src/main/cpp/dsp/decimatorc.h b/android/app/src/main/cpp/dsp/decimatorc.h new file mode 100644 index 0000000..a0c2a2f --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorc.h @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// Half-band centered decimators with Complex (i.e. omplex) input/output +// Decimates by a power of two using centered half-band filters + +#ifndef INCLUDE_DSP_DECIMATORC_H_ +#define INCLUDE_DSP_DECIMATORC_H_ + +#include "dsp/dsptypes.h" +#include "dsp/inthalfbandfiltereof.h" +#include "export.h" + +#define DECIMATORSXS_HB_FILTER_ORDER 64 + +class SDRBASE_API DecimatorC +{ +public: + DecimatorC(); + void setLog2Decim(unsigned int log2Decim); + bool decimate(Complex c, Complex& cd); + unsigned int getDecim() const { return m_decim; } + +private: + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages + unsigned int m_log2Decim; + unsigned int m_decim; + + bool decimate2(Complex c, Complex& cd); + bool decimate4(Complex c, Complex& cd); + bool decimate8(Complex c, Complex& cd); + bool decimate16(Complex c, Complex& cd); + bool decimate32(Complex c, Complex& cd); + bool decimate64(Complex c, Complex& cd); +}; + +#endif // INCLUDE_DSP_DECIMATORC_H_ diff --git a/android/app/src/main/cpp/dsp/decimators.h b/android/app/src/main/cpp/dsp/decimators.h new file mode 100644 index 0000000..a3e944c --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimators.h @@ -0,0 +1,4390 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2016, 2018-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GPL_DSP_DECIMATORS_H_ +#define INCLUDE_GPL_DSP_DECIMATORS_H_ + +#include "dsp/dsptypes.h" +#include "dsp/inthalfbandfiltereo.h" + +#define DECIMATORS_HB_FILTER_ORDER 64 + +template +struct decimation_shifts +{ + static const uint pre1 = 0; + static const uint pre2 = 0; + static const uint post2 = 0; + static const uint pre4 = 0; + static const uint post4 = 0; + static const uint pre8 = 0; + static const uint post8 = 0; + static const uint pre16 = 0; + static const uint post16 = 0; + static const uint pre32 = 0; + static const uint post32 = 0; + static const uint pre64 = 0; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<16, 24> +{ + static const uint pre1 = 0; + static const uint pre2 = 0; + static const uint post2 = 9; + static const uint pre4 = 0; + static const uint post4 = 10; + static const uint pre8 = 0; + static const uint post8 = 11; + static const uint pre16 = 0; + static const uint post16 = 12; + static const uint pre32 = 0; + static const uint post32 = 13; + static const uint pre64 = 0; + static const uint post64 = 14; +}; + +template<> +struct decimation_shifts<24, 24> +{ + static const uint pre1 = 0; + static const uint pre2 = 0; + static const uint post2 = 1; + static const uint pre4 = 0; + static const uint post4 = 2; + static const uint pre8 = 0; + static const uint post8 = 3; + static const uint pre16 = 0; + static const uint post16 = 4; + static const uint pre32 = 0; + static const uint post32 = 5; + static const uint pre64 = 0; + static const uint post64 = 6; +}; + +template<> +struct decimation_shifts<16, 16> +{ + static const uint pre1 = 0; + static const uint pre2 = 0; + static const uint post2 = 1; + static const uint pre4 = 0; + static const uint post4 = 2; + static const uint pre8 = 0; + static const uint post8 = 3; + static const uint pre16 = 0; + static const uint post16 = 4; + static const uint pre32 = 0; + static const uint post32 = 5; + static const uint pre64 = 0; + static const uint post64 = 6; +}; + +template<> +struct decimation_shifts<24, 16> +{ + static const uint pre1 = 8; + static const uint pre2 = 7; + static const uint post2 = 0; + static const uint pre4 = 6; + static const uint post4 = 0; + static const uint pre8 = 5; + static const uint post8 = 0; + static const uint pre16 = 4; + static const uint post16 = 0; + static const uint pre32 = 3; + static const uint post32 = 0; + static const uint pre64 = 2; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<16, 12> +{ + static const uint pre1 = 4; + static const uint pre2 = 3; + static const uint post2 = 0; + static const uint pre4 = 2; + static const uint post4 = 0; + static const uint pre8 = 1; + static const uint post8 = 0; + static const uint pre16 = 0; + static const uint post16 = 0; + static const uint pre32 = 0; + static const uint post32 = 1; + static const uint pre64 = 0; + static const uint post64 = 2; +}; + +template<> +struct decimation_shifts<24, 12> +{ + static const uint pre1 = 12; + static const uint pre2 = 11; + static const uint post2 = 0; + static const uint pre4 = 10; + static const uint post4 = 0; + static const uint pre8 = 9; + static const uint post8 = 0; + static const uint pre16 = 8; + static const uint post16 = 0; + static const uint pre32 = 7; + static const uint post32 = 0; + static const uint pre64 = 6; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<16, 8> +{ + static const uint pre1 = 8; + static const uint pre2 = 7; + static const uint post2 = 0; + static const uint pre4 = 6; + static const uint post4 = 0; + static const uint pre8 = 5; + static const uint post8 = 0; + static const uint pre16 = 4; + static const uint post16 = 0; + static const uint pre32 = 3; + static const uint post32 = 0; + static const uint pre64 = 2; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<24, 8> +{ + static const uint pre1 = 16; + static const uint pre2 = 15; + static const uint post2 = 0; + static const uint pre4 = 14; + static const uint post4 = 0; + static const uint pre8 = 13; + static const uint post8 = 0; + static const uint pre16 = 12; + static const uint post16 = 0; + static const uint pre32 = 11; + static const uint post32 = 0; + static const uint pre64 = 10; + static const uint post64 = 0; +}; + +#ifdef _MSC_VER +#pragma pack(push,1) +template +struct TripleByteLE +{ + uint8_t b0; + uint8_t b1; + uint8_t b2; + + typedef union { +#pragma pack(push,1) + struct { + int32_t i; + }; +#pragma pack(pop) +#pragma pack(push, 1) + struct { + uint8_t i0; + uint8_t i1; + uint8_t i2; + uint8_t i3; + }; +#pragma pack(pop) + } isample; + + operator T() const { + isample s; + s.i0 = 0; + s.i1 = b0; + s.i2 = b1; + s.i3 = b2; + return s.i; + } +}; +#pragma pack(pop) +#else +template +struct TripleByteLE +{ + uint8_t b0; + uint8_t b1; + uint8_t b2; + + typedef union { + struct { + int32_t i; + } __attribute__((__packed__)); + struct { + uint8_t i0; + uint8_t i1; + uint8_t i2; + uint8_t i3; + } __attribute__((__packed__)); + } isample; + + operator T() const { + isample s; + s.i0 = 0; + s.i1 = b0; + s.i2 = b1; + s.i3 = b2; + return s.i; + } +} __attribute__((__packed__)); +#endif + +#ifdef _MSC_VER +#pragma pack(push, 1) +template<> +struct TripleByteLE +{ + uint8_t b0; + uint8_t b1; + uint8_t b2; + + typedef union { +#pragma pack(push, 1) + struct { + qint32 i; + }; +#pragma pack(pop) +#pragma pack(push, 1) + struct { + uint8_t i0; + uint8_t i1; + uint8_t i2; + uint8_t i3; + }; +#pragma pack(pop) + } isample; + + operator qint32() const { + isample s; + s.i0 = 0; + s.i1 = b0; + s.i2 = b1; + s.i3 = b2; + return s.i >> 8; + } +}; +#pragma pack(pop) +#else +template<> +struct TripleByteLE +{ + uint8_t b0; + uint8_t b1; + uint8_t b2; + + typedef union { + struct { + qint32 i; + } __attribute__((__packed__)); + struct { + uint8_t i0; + uint8_t i1; + uint8_t i2; + uint8_t i3; + } __attribute__((__packed__)); + } isample; + + operator qint32() const { + isample s; + s.i0 = 0; + s.i1 = b0; + s.i2 = b1; + s.i3 = b2; + return s.i >> 8; + } +} __attribute__((__packed__)); +#endif + +#ifdef _MSC_VER +#pragma pack(push, 1) +template<> +struct TripleByteLE +{ + uint8_t b0; + uint8_t b1; + uint8_t b2; + + typedef union { +#pragma pack(push, 1) + struct { + qint64 i; + }; +#pragma pack(pop) +#pragma pack(push, 1) + struct { + uint32_t ia; + uint8_t i0; + uint8_t i1; + uint8_t i2; + uint8_t i3; + }; +#pragma pack(pop) + } isample; + + operator qint64() const { + isample s; + s.ia = 0; + s.i0 = 0; + s.i1 = b0; + s.i2 = b1; + s.i3 = b2; + return s.i >> 40; + } +}; +#pragma pack(pop) +#else +template<> +struct TripleByteLE +{ + uint8_t b0; + uint8_t b1; + uint8_t b2; + + typedef union { + struct { + qint64 i; + } __attribute__((__packed__)); + struct { + uint32_t ia; + uint8_t i0; + uint8_t i1; + uint8_t i2; + uint8_t i3; + } __attribute__((__packed__)); + } isample; + + operator qint64() const { + isample s; + s.ia = 0; + s.i0 = 0; + s.i1 = b0; + s.i2 = b1; + s.i3 = b2; + return s.i >> 40; + } +} __attribute__((__packed__)); +#endif + +/** Decimators with integer input and integer output */ +template +class Decimators +{ +public: + // interleaved I/Q input buffer + void decimate1(SampleVector::iterator* it, const T* buf, qint32 len); + + void decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len); + + void decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate4_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate4_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len); + + void decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate8_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate8_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len); + + void decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate16_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate16_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len); + + void decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate32_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate32_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len); + + void decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len); + + // separate I and Q input buffers + void decimate1(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + void decimate2_u(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + void decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + void decimate4_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + void decimate8_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + void decimate16_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + void decimate32_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + void decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len); + +private: +#ifdef SDR_RX_SAMPLE_24BIT + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator2s; // 1st stages - straight + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages +#else + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator2s; // 1st stages - straight + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages +#endif +}; + +template +void Decimators::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) +{ + qint32 xreal, yimag; + + for (int pos = 0; pos < len - 1; pos += 2) + { + xreal = IQOrder ? buf[pos+0] : buf[pos+1]; + yimag = IQOrder ? buf[pos+1] : buf[pos+0]; + (**it).setReal(xreal << decimation_shifts::pre1); // Valgrind optim (2 - comment not repeated) + (**it).setImag(yimag << decimation_shifts::pre1); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template +void Decimators::decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType xreal, yimag; + + for (int pos = 0; pos < len - 7; pos += 8) + { + xreal = IQOrder ? + (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2 : + (buf[pos+1] + buf[pos+2] - 255) << decimation_shifts::pre2; + yimag = IQOrder ? + (buf[pos+1] + buf[pos+2] - 255) << decimation_shifts::pre2 : + (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; + (**it).setReal(xreal >> decimation_shifts::post2); + (**it).setImag(yimag >> decimation_shifts::post2); + ++(*it); + + xreal = IQOrder ? + (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2 : + (255 - buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2; + yimag = IQOrder ? + (255 - buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2 : + (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2; + (**it).setReal(xreal >> decimation_shifts::post2); + (**it).setImag(yimag >> decimation_shifts::post2); + ++(*it); + } +} + +template +void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[4]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[4]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[4]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2s.myDecimateInf( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2s.myDecimateInf( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateSup( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[IQOrder ? 0 : 1] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder ? 1 : 0] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[IQOrder ? 2 : 3] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder ? 3 : 2] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void Decimators::decimate4_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2s.myDecimateInf( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2s.myDecimateInf( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateInf( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[IQOrder? 0 : 1] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder? 1 : 0] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[IQOrder? 2 : 3] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder? 3 : 2] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2s.myDecimateSup( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2s.myDecimateSup( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateInf( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[IQOrder? 0 : 1] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder? 1 : 0] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[IQOrder? 2 : 3] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder? 3 : 2] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void Decimators::decimate4_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2s.myDecimateSup( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2s.myDecimateSup( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateSup( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[IQOrder? 0 : 1] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder? 1 : 0] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[IQOrder? 2 : 3] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder? 3 : 2] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void Decimators::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2.myDecimateCen( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void Decimators::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[16], buf4[8], buf8[4]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2s.myDecimateInf( + buf[pos+0] << decimation_shifts::pre8, + buf[pos+1] << decimation_shifts::pre8, + buf[pos+2] << decimation_shifts::pre8, + buf[pos+3] << decimation_shifts::pre8, + buf[pos+4] << decimation_shifts::pre8, + buf[pos+5] << decimation_shifts::pre8, + buf[pos+6] << decimation_shifts::pre8, + buf[pos+7] << decimation_shifts::pre8, + &buf2[0]); + + m_decimator2s.myDecimateInf( + buf[pos+8] << decimation_shifts::pre8, + buf[pos+9] << decimation_shifts::pre8, + buf[pos+10] << decimation_shifts::pre8, + buf[pos+11] << decimation_shifts::pre8, + buf[pos+12] << decimation_shifts::pre8, + buf[pos+13] << decimation_shifts::pre8, + buf[pos+14] << decimation_shifts::pre8, + buf[pos+15] << decimation_shifts::pre8, + &buf2[4]); + + m_decimator2s.myDecimateInf( + buf[pos+16] << decimation_shifts::pre8, + buf[pos+17] << decimation_shifts::pre8, + buf[pos+18] << decimation_shifts::pre8, + buf[pos+19] << decimation_shifts::pre8, + buf[pos+20] << decimation_shifts::pre8, + buf[pos+21] << decimation_shifts::pre8, + buf[pos+22] << decimation_shifts::pre8, + buf[pos+23] << decimation_shifts::pre8, + &buf2[8]); + + m_decimator2s.myDecimateInf( + buf[pos+24] << decimation_shifts::pre8, + buf[pos+25] << decimation_shifts::pre8, + buf[pos+26] << decimation_shifts::pre8, + buf[pos+27] << decimation_shifts::pre8, + buf[pos+28] << decimation_shifts::pre8, + buf[pos+29] << decimation_shifts::pre8, + buf[pos+30] << decimation_shifts::pre8, + buf[pos+31] << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[IQOrder? 0 : 1] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 1 : 0] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[IQOrder? 2 : 3] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 3 : 2] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void Decimators::decimate8_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[16], buf4[8], buf8[4]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + for (int i = 0; i < 4; i++) + { + m_decimator2s.myDecimateInf( + buf[pos+8*i+0] << decimation_shifts::pre8, + buf[pos+8*i+1] << decimation_shifts::pre8, + buf[pos+8*i+2] << decimation_shifts::pre8, + buf[pos+8*i+3] << decimation_shifts::pre8, + buf[pos+8*i+4] << decimation_shifts::pre8, + buf[pos+8*i+5] << decimation_shifts::pre8, + buf[pos+8*i+6] << decimation_shifts::pre8, + buf[pos+8*i+7] << decimation_shifts::pre8, + &buf2[4*i]); + } + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[IQOrder? 0 : 1] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 1 : 0] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[IQOrder? 2 : 3] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 3 : 2] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void Decimators::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[16], buf4[8], buf8[4]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2s.myDecimateSup( + buf[pos+0] << decimation_shifts::pre8, + buf[pos+1] << decimation_shifts::pre8, + buf[pos+2] << decimation_shifts::pre8, + buf[pos+3] << decimation_shifts::pre8, + buf[pos+4] << decimation_shifts::pre8, + buf[pos+5] << decimation_shifts::pre8, + buf[pos+6] << decimation_shifts::pre8, + buf[pos+7] << decimation_shifts::pre8, + &buf2[0]); + + m_decimator2s.myDecimateSup( + buf[pos+8] << decimation_shifts::pre8, + buf[pos+9] << decimation_shifts::pre8, + buf[pos+10] << decimation_shifts::pre8, + buf[pos+11] << decimation_shifts::pre8, + buf[pos+12] << decimation_shifts::pre8, + buf[pos+13] << decimation_shifts::pre8, + buf[pos+14] << decimation_shifts::pre8, + buf[pos+15] << decimation_shifts::pre8, + &buf2[4]); + + m_decimator2s.myDecimateSup( + buf[pos+16] << decimation_shifts::pre8, + buf[pos+17] << decimation_shifts::pre8, + buf[pos+18] << decimation_shifts::pre8, + buf[pos+19] << decimation_shifts::pre8, + buf[pos+20] << decimation_shifts::pre8, + buf[pos+21] << decimation_shifts::pre8, + buf[pos+22] << decimation_shifts::pre8, + buf[pos+23] << decimation_shifts::pre8, + &buf2[8]); + + m_decimator2s.myDecimateSup( + buf[pos+24] << decimation_shifts::pre8, + buf[pos+25] << decimation_shifts::pre8, + buf[pos+26] << decimation_shifts::pre8, + buf[pos+27] << decimation_shifts::pre8, + buf[pos+28] << decimation_shifts::pre8, + buf[pos+29] << decimation_shifts::pre8, + buf[pos+30] << decimation_shifts::pre8, + buf[pos+31] << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[IQOrder? 0 : 1] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 1 : 0] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[IQOrder? 2 : 3] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 3 : 2] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void Decimators::decimate8_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[16], buf4[8], buf8[4]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + for (int i = 0; i < 4; i++) + { + m_decimator2s.myDecimateSup( + buf[pos+8*i+0] << decimation_shifts::pre8, + buf[pos+8*i+1] << decimation_shifts::pre8, + buf[pos+8*i+2] << decimation_shifts::pre8, + buf[pos+8*i+3] << decimation_shifts::pre8, + buf[pos+8*i+4] << decimation_shifts::pre8, + buf[pos+8*i+5] << decimation_shifts::pre8, + buf[pos+8*i+6] << decimation_shifts::pre8, + buf[pos+8*i+7] << decimation_shifts::pre8, + &buf2[4*i]); + } + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[IQOrder? 0 : 1] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 1 : 0] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[IQOrder? 2 : 3] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder? 3 : 2] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void Decimators::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[8]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + intbuf[0] = buf[pos+2] << decimation_shifts::pre8; + intbuf[1] = buf[pos+3] << decimation_shifts::pre8; + intbuf[2] = buf[pos+6] << decimation_shifts::pre8; + intbuf[3] = buf[pos+7] << decimation_shifts::pre8; + intbuf[4] = buf[pos+10] << decimation_shifts::pre8; + intbuf[5] = buf[pos+11] << decimation_shifts::pre8; + intbuf[6] = buf[pos+14] << decimation_shifts::pre8; + intbuf[7] = buf[pos+15] << decimation_shifts::pre8; + + m_decimator2.myDecimate( + buf[pos+0] << decimation_shifts::pre8, + buf[pos+1] << decimation_shifts::pre8, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4] << decimation_shifts::pre8, + buf[pos+5] << decimation_shifts::pre8, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8] << decimation_shifts::pre8, + buf[pos+9] << decimation_shifts::pre8, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12] << decimation_shifts::pre8, + buf[pos+13] << decimation_shifts::pre8, + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6] >> decimation_shifts::post8); + (**it).setImag(intbuf[7] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void Decimators::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2s.myDecimateInf( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); + + m_decimator2s.myDecimateInf( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); + + m_decimator2s.myDecimateInf( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); + + m_decimator2s.myDecimateInf( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); + + m_decimator2s.myDecimateInf( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2s.myDecimateInf( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2s.myDecimateInf( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2s.myDecimateInf( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[IQOrder? 0 : 1] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 1 : 0] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[IQOrder? 2 : 3] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 3 : 2] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void Decimators::decimate16_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + for (int i = 0; i < 8; i++) + { + m_decimator2s.myDecimateInf( + buf[pos+8*i+0] << decimation_shifts::pre16, + buf[pos+8*i+1] << decimation_shifts::pre16, + buf[pos+8*i+2] << decimation_shifts::pre16, + buf[pos+8*i+3] << decimation_shifts::pre16, + buf[pos+8*i+4] << decimation_shifts::pre16, + buf[pos+8*i+5] << decimation_shifts::pre16, + buf[pos+8*i+6] << decimation_shifts::pre16, + buf[pos+8*i+7] << decimation_shifts::pre16, + &buf2[4*i]); + } + + for (int i = 0; i < 4; i++) + { + m_decimator4.myDecimateInf( + &buf2[8*i], + &buf4[4*i]); + } + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[IQOrder? 0 : 1] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 1 : 0] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[IQOrder? 2 : 3] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 3 : 2] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void Decimators::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2s.myDecimateSup( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); + + m_decimator2s.myDecimateSup( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); + + m_decimator2s.myDecimateSup( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); + + m_decimator2s.myDecimateSup( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); + + m_decimator2s.myDecimateSup( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2s.myDecimateSup( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2s.myDecimateSup( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2s.myDecimateSup( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[IQOrder? 0 : 1] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 1 : 0] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[IQOrder? 2 : 3] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 3 : 2] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void Decimators::decimate16_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + for (int i = 0; i < 8; i++) + { + m_decimator2s.myDecimateSup( + buf[pos+8*i+0] << decimation_shifts::pre16, + buf[pos+8*i+1] << decimation_shifts::pre16, + buf[pos+8*i+2] << decimation_shifts::pre16, + buf[pos+8*i+3] << decimation_shifts::pre16, + buf[pos+8*i+4] << decimation_shifts::pre16, + buf[pos+8*i+5] << decimation_shifts::pre16, + buf[pos+8*i+6] << decimation_shifts::pre16, + buf[pos+8*i+7] << decimation_shifts::pre16, + &buf2[4*i]); + } + + for (int i = 0; i < 4; i++) + { + m_decimator4.myDecimateSup( + &buf2[8*i], + &buf4[4*i]); + } + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[IQOrder? 0 : 1] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 1 : 0] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[IQOrder? 2 : 3] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder? 3 : 2] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void Decimators::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[16]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + intbuf[0] = buf[pos+2] << decimation_shifts::pre16; + intbuf[1] = buf[pos+3] << decimation_shifts::pre16; + intbuf[2] = buf[pos+6] << decimation_shifts::pre16; + intbuf[3] = buf[pos+7] << decimation_shifts::pre16; + intbuf[4] = buf[pos+10] << decimation_shifts::pre16; + intbuf[5] = buf[pos+11] << decimation_shifts::pre16; + intbuf[6] = buf[pos+14] << decimation_shifts::pre16; + intbuf[7] = buf[pos+15] << decimation_shifts::pre16; + intbuf[8] = buf[pos+18] << decimation_shifts::pre16; + intbuf[9] = buf[pos+19] << decimation_shifts::pre16; + intbuf[10] = buf[pos+22] << decimation_shifts::pre16; + intbuf[11] = buf[pos+23] << decimation_shifts::pre16; + intbuf[12] = buf[pos+26] << decimation_shifts::pre16; + intbuf[13] = buf[pos+27] << decimation_shifts::pre16; + intbuf[14] = buf[pos+30] << decimation_shifts::pre16; + intbuf[15] = buf[pos+31] << decimation_shifts::pre16; + + m_decimator2.myDecimate( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14] >> decimation_shifts::post16); + (**it).setImag(intbuf[15] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void Decimators::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2s.myDecimateInf( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); + + m_decimator2s.myDecimateInf( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); + + m_decimator2s.myDecimateInf( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); + + m_decimator2s.myDecimateInf( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); + + m_decimator2s.myDecimateInf( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); + + m_decimator2s.myDecimateInf( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2s.myDecimateInf( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2s.myDecimateInf( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2s.myDecimateInf( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2s.myDecimateInf( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2s.myDecimateInf( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2s.myDecimateInf( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2s.myDecimateInf( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2s.myDecimateInf( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2s.myDecimateInf( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2s.myDecimateInf( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[IQOrder? 0 : 1] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 1 : 0] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[IQOrder? 2 : 3] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 3 : 2] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void Decimators::decimate32_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + for (int i = 0; i < 16; i++) + { + m_decimator2s.myDecimateInf( + buf[pos+8*i+0] << decimation_shifts::pre32, + buf[pos+8*i+1] << decimation_shifts::pre32, + buf[pos+8*i+2] << decimation_shifts::pre32, + buf[pos+8*i+3] << decimation_shifts::pre32, + buf[pos+8*i+4] << decimation_shifts::pre32, + buf[pos+8*i+5] << decimation_shifts::pre32, + buf[pos+8*i+6] << decimation_shifts::pre32, + buf[pos+8*i+7] << decimation_shifts::pre32, + &buf2[4*i]); + } + + for (int i = 0; i < 8; i++) + { + m_decimator4.myDecimateInf( + &buf2[8*i], + &buf4[4*i]); + } + + for (int i = 0; i < 4; i++) + { + m_decimator8.myDecimateSup( + &buf4[8*i], + &buf8[4*i]); + } + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateSup( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[IQOrder? 0 : 1] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 1 : 0] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[IQOrder? 2 : 3] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 3 : 2] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void Decimators::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2s.myDecimateSup( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); + + m_decimator2s.myDecimateSup( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); + + m_decimator2s.myDecimateSup( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); + + m_decimator2s.myDecimateSup( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); + + m_decimator2s.myDecimateSup( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); + + m_decimator2s.myDecimateSup( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2s.myDecimateSup( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2s.myDecimateSup( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2s.myDecimateSup( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2s.myDecimateSup( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2s.myDecimateSup( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2s.myDecimateSup( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2s.myDecimateSup( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2s.myDecimateSup( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2s.myDecimateSup( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2s.myDecimateSup( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[IQOrder? 0 : 1] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 1 : 0] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[IQOrder? 2 : 3] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 3 : 2] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void Decimators::decimate32_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + for (int i = 0; i < 16; i++) + { + m_decimator2s.myDecimateSup( + buf[pos+8*i+0] << decimation_shifts::pre32, + buf[pos+8*i+1] << decimation_shifts::pre32, + buf[pos+8*i+2] << decimation_shifts::pre32, + buf[pos+8*i+3] << decimation_shifts::pre32, + buf[pos+8*i+4] << decimation_shifts::pre32, + buf[pos+8*i+5] << decimation_shifts::pre32, + buf[pos+8*i+6] << decimation_shifts::pre32, + buf[pos+8*i+7] << decimation_shifts::pre32, + &buf2[4*i]); + } + + for (int i = 0; i < 8; i++) + { + m_decimator4.myDecimateSup( + &buf2[8*i], + &buf4[4*i]); + } + + for (int i = 0; i < 4; i++) + { + m_decimator8.myDecimateInf( + &buf4[8*i], + &buf8[4*i]); + } + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateInf( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[IQOrder? 0 : 1] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 1 : 0] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[IQOrder? 2 : 3] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder? 3 : 2] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void Decimators::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[32]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + intbuf[0] = buf[pos+2] << decimation_shifts::pre32; + intbuf[1] = buf[pos+3] << decimation_shifts::pre32; + intbuf[2] = buf[pos+6] << decimation_shifts::pre32; + intbuf[3] = buf[pos+7] << decimation_shifts::pre32; + intbuf[4] = buf[pos+10] << decimation_shifts::pre32; + intbuf[5] = buf[pos+11] << decimation_shifts::pre32; + intbuf[6] = buf[pos+14] << decimation_shifts::pre32; + intbuf[7] = buf[pos+15] << decimation_shifts::pre32; + intbuf[8] = buf[pos+18] << decimation_shifts::pre32; + intbuf[9] = buf[pos+19] << decimation_shifts::pre32; + intbuf[10] = buf[pos+22] << decimation_shifts::pre32; + intbuf[11] = buf[pos+23] << decimation_shifts::pre32; + intbuf[12] = buf[pos+26] << decimation_shifts::pre32; + intbuf[13] = buf[pos+27] << decimation_shifts::pre32; + intbuf[14] = buf[pos+30] << decimation_shifts::pre32; + intbuf[15] = buf[pos+31] << decimation_shifts::pre32; + intbuf[16] = buf[pos+34] << decimation_shifts::pre32; + intbuf[17] = buf[pos+35] << decimation_shifts::pre32; + intbuf[18] = buf[pos+38] << decimation_shifts::pre32; + intbuf[19] = buf[pos+39] << decimation_shifts::pre32; + intbuf[20] = buf[pos+42] << decimation_shifts::pre32; + intbuf[21] = buf[pos+43] << decimation_shifts::pre32; + intbuf[22] = buf[pos+46] << decimation_shifts::pre32; + intbuf[23] = buf[pos+47] << decimation_shifts::pre32; + intbuf[24] = buf[pos+50] << decimation_shifts::pre32; + intbuf[25] = buf[pos+51] << decimation_shifts::pre32; + intbuf[26] = buf[pos+54] << decimation_shifts::pre32; + intbuf[27] = buf[pos+55] << decimation_shifts::pre32; + intbuf[28] = buf[pos+58] << decimation_shifts::pre32; + intbuf[29] = buf[pos+59] << decimation_shifts::pre32; + intbuf[30] = buf[pos+62] << decimation_shifts::pre32; + intbuf[31] = buf[pos+63] << decimation_shifts::pre32; + + m_decimator2.myDecimate( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30] >> decimation_shifts::post32); + (**it).setImag(intbuf[31] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void Decimators::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; + + for (int pos = 0; pos < len - 255; pos += 256) + { + m_decimator2s.myDecimateInf( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + buf[pos+2] << decimation_shifts::pre64, + buf[pos+3] << decimation_shifts::pre64, + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + buf[pos+6] << decimation_shifts::pre64, + buf[pos+7] << decimation_shifts::pre64, + &buf2[0]); + + m_decimator2s.myDecimateInf( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + buf[pos+10] << decimation_shifts::pre64, + buf[pos+11] << decimation_shifts::pre64, + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + buf[pos+14] << decimation_shifts::pre64, + buf[pos+15] << decimation_shifts::pre64, + &buf2[4]); + + m_decimator2s.myDecimateInf( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + buf[pos+18] << decimation_shifts::pre64, + buf[pos+19] << decimation_shifts::pre64, + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + buf[pos+22] << decimation_shifts::pre64, + buf[pos+23] << decimation_shifts::pre64, + &buf2[8]); + + m_decimator2s.myDecimateInf( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + buf[pos+26] << decimation_shifts::pre64, + buf[pos+27] << decimation_shifts::pre64, + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + buf[pos+30] << decimation_shifts::pre64, + buf[pos+31] << decimation_shifts::pre64, + &buf2[12]); + + m_decimator2s.myDecimateInf( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + buf[pos+34] << decimation_shifts::pre64, + buf[pos+35] << decimation_shifts::pre64, + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + buf[pos+38] << decimation_shifts::pre64, + buf[pos+39] << decimation_shifts::pre64, + &buf2[16]); + + m_decimator2s.myDecimateInf( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + buf[pos+42] << decimation_shifts::pre64, + buf[pos+43] << decimation_shifts::pre64, + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + buf[pos+46] << decimation_shifts::pre64, + buf[pos+47] << decimation_shifts::pre64, + &buf2[20]); + + m_decimator2s.myDecimateInf( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + buf[pos+50] << decimation_shifts::pre64, + buf[pos+51] << decimation_shifts::pre64, + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + buf[pos+54] << decimation_shifts::pre64, + buf[pos+55] << decimation_shifts::pre64, + &buf2[24]); + + m_decimator2s.myDecimateInf( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + buf[pos+58] << decimation_shifts::pre64, + buf[pos+59] << decimation_shifts::pre64, + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + buf[pos+62] << decimation_shifts::pre64, + buf[pos+63] << decimation_shifts::pre64, + &buf2[28]); + + m_decimator2s.myDecimateInf( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + buf[pos+66] << decimation_shifts::pre64, + buf[pos+67] << decimation_shifts::pre64, + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + buf[pos+70] << decimation_shifts::pre64, + buf[pos+71] << decimation_shifts::pre64, + &buf2[32]); + + m_decimator2s.myDecimateInf( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + buf[pos+74] << decimation_shifts::pre64, + buf[pos+75] << decimation_shifts::pre64, + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + buf[pos+78] << decimation_shifts::pre64, + buf[pos+79] << decimation_shifts::pre64, + &buf2[36]); + + m_decimator2s.myDecimateInf( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + buf[pos+82] << decimation_shifts::pre64, + buf[pos+83] << decimation_shifts::pre64, + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + buf[pos+86] << decimation_shifts::pre64, + buf[pos+87] << decimation_shifts::pre64, + &buf2[40]); + + m_decimator2s.myDecimateInf( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + buf[pos+90] << decimation_shifts::pre64, + buf[pos+91] << decimation_shifts::pre64, + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + buf[pos+94] << decimation_shifts::pre64, + buf[pos+95] << decimation_shifts::pre64, + &buf2[44]); + + m_decimator2s.myDecimateInf( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + buf[pos+98] << decimation_shifts::pre64, + buf[pos+99] << decimation_shifts::pre64, + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + buf[pos+102] << decimation_shifts::pre64, + buf[pos+103] << decimation_shifts::pre64, + &buf2[48]); + + m_decimator2s.myDecimateInf( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + buf[pos+106] << decimation_shifts::pre64, + buf[pos+107] << decimation_shifts::pre64, + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + buf[pos+110] << decimation_shifts::pre64, + buf[pos+111] << decimation_shifts::pre64, + &buf2[52]); + + m_decimator2s.myDecimateInf( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + buf[pos+114] << decimation_shifts::pre64, + buf[pos+115] << decimation_shifts::pre64, + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + buf[pos+118] << decimation_shifts::pre64, + buf[pos+119] << decimation_shifts::pre64, + &buf2[56]); + + m_decimator2s.myDecimateInf( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + buf[pos+122] << decimation_shifts::pre64, + buf[pos+123] << decimation_shifts::pre64, + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + buf[pos+126] << decimation_shifts::pre64, + buf[pos+127] << decimation_shifts::pre64, + &buf2[60]); + + m_decimator2s.myDecimateInf( + buf[pos+128] << decimation_shifts::pre64, + buf[pos+129] << decimation_shifts::pre64, + buf[pos+130] << decimation_shifts::pre64, + buf[pos+131] << decimation_shifts::pre64, + buf[pos+132] << decimation_shifts::pre64, + buf[pos+133] << decimation_shifts::pre64, + buf[pos+134] << decimation_shifts::pre64, + buf[pos+135] << decimation_shifts::pre64, + &buf2[64]); + + m_decimator2s.myDecimateInf( + buf[pos+136] << decimation_shifts::pre64, + buf[pos+137] << decimation_shifts::pre64, + buf[pos+138] << decimation_shifts::pre64, + buf[pos+139] << decimation_shifts::pre64, + buf[pos+140] << decimation_shifts::pre64, + buf[pos+141] << decimation_shifts::pre64, + buf[pos+142] << decimation_shifts::pre64, + buf[pos+143] << decimation_shifts::pre64, + &buf2[68]); + + m_decimator2s.myDecimateInf( + buf[pos+144] << decimation_shifts::pre64, + buf[pos+145] << decimation_shifts::pre64, + buf[pos+146] << decimation_shifts::pre64, + buf[pos+147] << decimation_shifts::pre64, + buf[pos+148] << decimation_shifts::pre64, + buf[pos+149] << decimation_shifts::pre64, + buf[pos+150] << decimation_shifts::pre64, + buf[pos+151] << decimation_shifts::pre64, + &buf2[72]); + + m_decimator2s.myDecimateInf( + buf[pos+152] << decimation_shifts::pre64, + buf[pos+153] << decimation_shifts::pre64, + buf[pos+154] << decimation_shifts::pre64, + buf[pos+155] << decimation_shifts::pre64, + buf[pos+156] << decimation_shifts::pre64, + buf[pos+157] << decimation_shifts::pre64, + buf[pos+158] << decimation_shifts::pre64, + buf[pos+159] << decimation_shifts::pre64, + &buf2[76]); + + m_decimator2s.myDecimateInf( + buf[pos+160] << decimation_shifts::pre64, + buf[pos+161] << decimation_shifts::pre64, + buf[pos+162] << decimation_shifts::pre64, + buf[pos+163] << decimation_shifts::pre64, + buf[pos+164] << decimation_shifts::pre64, + buf[pos+165] << decimation_shifts::pre64, + buf[pos+166] << decimation_shifts::pre64, + buf[pos+167] << decimation_shifts::pre64, + &buf2[80]); + + m_decimator2s.myDecimateInf( + buf[pos+168] << decimation_shifts::pre64, + buf[pos+169] << decimation_shifts::pre64, + buf[pos+170] << decimation_shifts::pre64, + buf[pos+171] << decimation_shifts::pre64, + buf[pos+172] << decimation_shifts::pre64, + buf[pos+173] << decimation_shifts::pre64, + buf[pos+174] << decimation_shifts::pre64, + buf[pos+175] << decimation_shifts::pre64, + &buf2[84]); + + m_decimator2s.myDecimateInf( + buf[pos+176] << decimation_shifts::pre64, + buf[pos+177] << decimation_shifts::pre64, + buf[pos+178] << decimation_shifts::pre64, + buf[pos+179] << decimation_shifts::pre64, + buf[pos+180] << decimation_shifts::pre64, + buf[pos+181] << decimation_shifts::pre64, + buf[pos+182] << decimation_shifts::pre64, + buf[pos+183] << decimation_shifts::pre64, + &buf2[88]); + + m_decimator2s.myDecimateInf( + buf[pos+184] << decimation_shifts::pre64, + buf[pos+185] << decimation_shifts::pre64, + buf[pos+186] << decimation_shifts::pre64, + buf[pos+187] << decimation_shifts::pre64, + buf[pos+188] << decimation_shifts::pre64, + buf[pos+189] << decimation_shifts::pre64, + buf[pos+190] << decimation_shifts::pre64, + buf[pos+191] << decimation_shifts::pre64, + &buf2[92]); + + m_decimator2s.myDecimateInf( + buf[pos+192] << decimation_shifts::pre64, + buf[pos+193] << decimation_shifts::pre64, + buf[pos+194] << decimation_shifts::pre64, + buf[pos+195] << decimation_shifts::pre64, + buf[pos+196] << decimation_shifts::pre64, + buf[pos+197] << decimation_shifts::pre64, + buf[pos+198] << decimation_shifts::pre64, + buf[pos+199] << decimation_shifts::pre64, + &buf2[96]); + + m_decimator2s.myDecimateInf( + buf[pos+200] << decimation_shifts::pre64, + buf[pos+201] << decimation_shifts::pre64, + buf[pos+202] << decimation_shifts::pre64, + buf[pos+203] << decimation_shifts::pre64, + buf[pos+204] << decimation_shifts::pre64, + buf[pos+205] << decimation_shifts::pre64, + buf[pos+206] << decimation_shifts::pre64, + buf[pos+207] << decimation_shifts::pre64, + &buf2[100]); + + m_decimator2s.myDecimateInf( + buf[pos+208] << decimation_shifts::pre64, + buf[pos+209] << decimation_shifts::pre64, + buf[pos+210] << decimation_shifts::pre64, + buf[pos+211] << decimation_shifts::pre64, + buf[pos+212] << decimation_shifts::pre64, + buf[pos+213] << decimation_shifts::pre64, + buf[pos+214] << decimation_shifts::pre64, + buf[pos+215] << decimation_shifts::pre64, + &buf2[104]); + + m_decimator2s.myDecimateInf( + buf[pos+216] << decimation_shifts::pre64, + buf[pos+217] << decimation_shifts::pre64, + buf[pos+218] << decimation_shifts::pre64, + buf[pos+219] << decimation_shifts::pre64, + buf[pos+220] << decimation_shifts::pre64, + buf[pos+221] << decimation_shifts::pre64, + buf[pos+222] << decimation_shifts::pre64, + buf[pos+223] << decimation_shifts::pre64, + &buf2[108]); + + m_decimator2s.myDecimateInf( + buf[pos+224] << decimation_shifts::pre64, + buf[pos+225] << decimation_shifts::pre64, + buf[pos+226] << decimation_shifts::pre64, + buf[pos+227] << decimation_shifts::pre64, + buf[pos+228] << decimation_shifts::pre64, + buf[pos+229] << decimation_shifts::pre64, + buf[pos+230] << decimation_shifts::pre64, + buf[pos+231] << decimation_shifts::pre64, + &buf2[112]); + + m_decimator2s.myDecimateInf( + buf[pos+232] << decimation_shifts::pre64, + buf[pos+233] << decimation_shifts::pre64, + buf[pos+234] << decimation_shifts::pre64, + buf[pos+235] << decimation_shifts::pre64, + buf[pos+236] << decimation_shifts::pre64, + buf[pos+237] << decimation_shifts::pre64, + buf[pos+238] << decimation_shifts::pre64, + buf[pos+239] << decimation_shifts::pre64, + &buf2[116]); + + m_decimator2s.myDecimateInf( + buf[pos+240] << decimation_shifts::pre64, + buf[pos+241] << decimation_shifts::pre64, + buf[pos+242] << decimation_shifts::pre64, + buf[pos+243] << decimation_shifts::pre64, + buf[pos+244] << decimation_shifts::pre64, + buf[pos+245] << decimation_shifts::pre64, + buf[pos+246] << decimation_shifts::pre64, + buf[pos+247] << decimation_shifts::pre64, + &buf2[120]); + + m_decimator2s.myDecimateInf( + buf[pos+248] << decimation_shifts::pre64, + buf[pos+249] << decimation_shifts::pre64, + buf[pos+250] << decimation_shifts::pre64, + buf[pos+251] << decimation_shifts::pre64, + buf[pos+252] << decimation_shifts::pre64, + buf[pos+253] << decimation_shifts::pre64, + buf[pos+254] << decimation_shifts::pre64, + buf[pos+255] << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateSup( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateSup( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateSup( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateSup( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateSup( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateSup( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateSup( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateSup( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateSup( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateSup( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateSup( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateSup( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateSup( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateSup( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateSup( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateSup( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[IQOrder? 0 : 1] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 1 : 0] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[IQOrder? 2 : 3] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 3 : 2] >> decimation_shifts::post64); + ++(*it); + } +} + +template +void Decimators::decimate64_inf_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; + + for (int pos = 0; pos < len - 255; pos += 256) + { + for (int i = 0; i < 32; i++) + { + m_decimator2s.myDecimateInf( + buf[pos+8*i+0] << decimation_shifts::pre64, + buf[pos+8*i+1] << decimation_shifts::pre64, + buf[pos+8*i+2] << decimation_shifts::pre64, + buf[pos+8*i+3] << decimation_shifts::pre64, + buf[pos+8*i+4] << decimation_shifts::pre64, + buf[pos+8*i+5] << decimation_shifts::pre64, + buf[pos+8*i+6] << decimation_shifts::pre64, + buf[pos+8*i+7] << decimation_shifts::pre64, + &buf2[4*i]); + } + + for (int i = 0; i < 16; i++) + { + m_decimator4.myDecimateSup( + &buf2[8*i], + &buf4[4*i]); + } + + for (int i = 0; i < 8; i++) + { + m_decimator8.myDecimateInf( + &buf4[8*i], + &buf8[4*i]); + } + + for (int i = 0; i < 4; i++) + { + m_decimator16.myDecimateSup( + &buf8[8*i], + &buf16[4*i]); + } + + m_decimator32.myDecimateInf( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateInf( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateSup( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[IQOrder? 0 : 1] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 1 : 0] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[IQOrder? 2 : 3] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 3 : 2] >> decimation_shifts::post64); + ++(*it); + } +} + +template +void Decimators::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; + + for (int pos = 0; pos < len - 255; pos += 256) + { + for (int i = 0; i < 32; i++) + { + m_decimator2s.myDecimateInf( + buf[pos+8*i+0] << decimation_shifts::pre64, + buf[pos+8*i+1] << decimation_shifts::pre64, + buf[pos+8*i+2] << decimation_shifts::pre64, + buf[pos+8*i+3] << decimation_shifts::pre64, + buf[pos+8*i+4] << decimation_shifts::pre64, + buf[pos+8*i+5] << decimation_shifts::pre64, + buf[pos+8*i+6] << decimation_shifts::pre64, + buf[pos+8*i+7] << decimation_shifts::pre64, + &buf2[4*i]); + } + + for (int i = 0; i < 16; i++) + { + m_decimator4.myDecimateSup( + &buf2[8*i], + &buf4[4*i]); + } + + for (int i = 0; i < 8; i++) + { + m_decimator8.myDecimateInf( + &buf4[8*i], + &buf8[4*i]); + } + + for (int i = 0; i < 4; i++) + { + m_decimator16.myDecimateSup( + &buf8[8*i], + &buf16[4*i]); + } + + m_decimator32.myDecimateInf( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateInf( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateSup( + &buf32[0], + &buf64[0]); + + + (**it).setReal(buf64[IQOrder? 0 : 1] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 1 : 0] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[IQOrder? 2 : 3] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 3 : 2] >> decimation_shifts::post64); + ++(*it); + } +} + +template +void Decimators::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[64]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + intbuf[0] = buf[pos+2] << decimation_shifts::pre64; + intbuf[1] = buf[pos+3] << decimation_shifts::pre64; + intbuf[2] = buf[pos+6] << decimation_shifts::pre64; + intbuf[3] = buf[pos+7] << decimation_shifts::pre64; + intbuf[4] = buf[pos+10] << decimation_shifts::pre64; + intbuf[5] = buf[pos+11] << decimation_shifts::pre64; + intbuf[6] = buf[pos+14] << decimation_shifts::pre64; + intbuf[7] = buf[pos+15] << decimation_shifts::pre64; + intbuf[8] = buf[pos+18] << decimation_shifts::pre64; + intbuf[9] = buf[pos+19] << decimation_shifts::pre64; + intbuf[10] = buf[pos+22] << decimation_shifts::pre64; + intbuf[11] = buf[pos+23] << decimation_shifts::pre64; + intbuf[12] = buf[pos+26] << decimation_shifts::pre64; + intbuf[13] = buf[pos+27] << decimation_shifts::pre64; + intbuf[14] = buf[pos+30] << decimation_shifts::pre64; + intbuf[15] = buf[pos+31] << decimation_shifts::pre64; + intbuf[16] = buf[pos+34] << decimation_shifts::pre64; + intbuf[17] = buf[pos+35] << decimation_shifts::pre64; + intbuf[18] = buf[pos+38] << decimation_shifts::pre64; + intbuf[19] = buf[pos+39] << decimation_shifts::pre64; + intbuf[20] = buf[pos+42] << decimation_shifts::pre64; + intbuf[21] = buf[pos+43] << decimation_shifts::pre64; + intbuf[22] = buf[pos+46] << decimation_shifts::pre64; + intbuf[23] = buf[pos+47] << decimation_shifts::pre64; + intbuf[24] = buf[pos+50] << decimation_shifts::pre64; + intbuf[25] = buf[pos+51] << decimation_shifts::pre64; + intbuf[26] = buf[pos+54] << decimation_shifts::pre64; + intbuf[27] = buf[pos+55] << decimation_shifts::pre64; + intbuf[28] = buf[pos+58] << decimation_shifts::pre64; + intbuf[29] = buf[pos+59] << decimation_shifts::pre64; + intbuf[30] = buf[pos+62] << decimation_shifts::pre64; + intbuf[31] = buf[pos+63] << decimation_shifts::pre64; + + intbuf[32] = buf[pos+66] << decimation_shifts::pre64; + intbuf[33] = buf[pos+67] << decimation_shifts::pre64; + intbuf[34] = buf[pos+70] << decimation_shifts::pre64; + intbuf[35] = buf[pos+71] << decimation_shifts::pre64; + intbuf[36] = buf[pos+74] << decimation_shifts::pre64; + intbuf[37] = buf[pos+75] << decimation_shifts::pre64; + intbuf[38] = buf[pos+78] << decimation_shifts::pre64; + intbuf[39] = buf[pos+79] << decimation_shifts::pre64; + intbuf[40] = buf[pos+82] << decimation_shifts::pre64; + intbuf[41] = buf[pos+83] << decimation_shifts::pre64; + intbuf[42] = buf[pos+86] << decimation_shifts::pre64; + intbuf[43] = buf[pos+87] << decimation_shifts::pre64; + intbuf[44] = buf[pos+90] << decimation_shifts::pre64; + intbuf[45] = buf[pos+91] << decimation_shifts::pre64; + intbuf[46] = buf[pos+94] << decimation_shifts::pre64; + intbuf[47] = buf[pos+95] << decimation_shifts::pre64; + intbuf[48] = buf[pos+98] << decimation_shifts::pre64; + intbuf[49] = buf[pos+99] << decimation_shifts::pre64; + intbuf[50] = buf[pos+102] << decimation_shifts::pre64; + intbuf[51] = buf[pos+103] << decimation_shifts::pre64; + intbuf[52] = buf[pos+106] << decimation_shifts::pre64; + intbuf[53] = buf[pos+107] << decimation_shifts::pre64; + intbuf[54] = buf[pos+110] << decimation_shifts::pre64; + intbuf[55] = buf[pos+111] << decimation_shifts::pre64; + intbuf[56] = buf[pos+114] << decimation_shifts::pre64; + intbuf[57] = buf[pos+115] << decimation_shifts::pre64; + intbuf[58] = buf[pos+118] << decimation_shifts::pre64; + intbuf[59] = buf[pos+119] << decimation_shifts::pre64; + intbuf[60] = buf[pos+122] << decimation_shifts::pre64; + intbuf[61] = buf[pos+123] << decimation_shifts::pre64; + intbuf[62] = buf[pos+126] << decimation_shifts::pre64; + intbuf[63] = buf[pos+127] << decimation_shifts::pre64; + + m_decimator2.myDecimate( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62] >> decimation_shifts::post64); + (**it).setImag(intbuf[63] >> decimation_shifts::post64); + ++(*it); + } +} + +template +void Decimators::decimate64_sup_txsync(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; + + for (int pos = 0; pos < len - 255; pos += 256) + { + for (int i = 0; i < 32; i++) + { + m_decimator2s.myDecimateSup( + buf[pos+8*i+0] << decimation_shifts::pre64, + buf[pos+8*i+1] << decimation_shifts::pre64, + buf[pos+8*i+2] << decimation_shifts::pre64, + buf[pos+8*i+3] << decimation_shifts::pre64, + buf[pos+8*i+4] << decimation_shifts::pre64, + buf[pos+8*i+5] << decimation_shifts::pre64, + buf[pos+8*i+6] << decimation_shifts::pre64, + buf[pos+8*i+7] << decimation_shifts::pre64, + &buf2[4*i]); + } + + for (int i = 0; i < 16; i++) + { + m_decimator4.myDecimateInf( + &buf2[8*i], + &buf4[4*i]); + } + + for (int i = 0; i < 8; i++) + { + m_decimator8.myDecimateSup( + &buf4[8*i], + &buf8[4*i]); + } + + for (int i = 0; i < 4; i++) + { + m_decimator16.myDecimateInf( + &buf8[8*i], + &buf16[4*i]); + } + + m_decimator32.myDecimateSup( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateSup( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateInf( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[IQOrder? 0 : 1] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 1 : 0] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[IQOrder? 2 : 3] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder? 3 : 2] >> decimation_shifts::post64); + ++(*it); + } +} + + +// ============================================================================================================== + +template +void Decimators::decimate1(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + qint32 xreal, yimag; + + for (int pos = 0; pos < len; pos += 1) + { + xreal = IQOrder ? bufI[pos] : bufQ[pos]; + yimag = IQOrder ? bufQ[pos] : bufI[pos]; + (**it).setReal(xreal << decimation_shifts::pre1); // Valgrind optim (2 - comment not repeated) + (**it).setImag(yimag << decimation_shifts::pre1); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template +void Decimators::decimate2_u(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + StorageType xreal, yimag; + + for (int pos = 0; pos < len - 3; pos += 4) + { + // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] + xreal = (bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; + yimag = (bufQ[pos] + bufI[pos+1] - 255) << decimation_shifts::pre2; + (**it).setReal((IQOrder ? xreal : yimag) >> decimation_shifts::post2); + (**it).setImag((IQOrder ? yimag : xreal) >> decimation_shifts::post2); + ++(*it); + + // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] + xreal = (bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre2; + yimag = (255 - bufQ[pos+2] - bufI[pos+3]) << decimation_shifts::pre2; + (**it).setReal((IQOrder ? xreal : yimag) >> decimation_shifts::post2); + (**it).setImag((IQOrder ? yimag : xreal) >> decimation_shifts::post2); + ++(*it); + } +} + +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + StorageType intbuf[2]; + + for (int pos = 0; pos < len - 1; pos += 2) + { + intbuf[0] = bufI[pos+1] << decimation_shifts::pre2; + intbuf[1] = bufQ[pos+1] << decimation_shifts::pre2; + + m_decimator2.myDecimate( + bufI[pos+0] << decimation_shifts::pre2, + bufQ[pos+0] << decimation_shifts::pre2, + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0] >> decimation_shifts::post2); + (**it).setImag(intbuf[1] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void Decimators::decimate4_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + StorageType intbuf[4]; + + for (int pos = 0; pos < len - 3; pos += 4) + { + intbuf[0] = bufI[pos+1] << decimation_shifts::pre4; + intbuf[1] = bufQ[pos+1] << decimation_shifts::pre4; + intbuf[2] = bufI[pos+3] << decimation_shifts::pre4; + intbuf[3] = bufQ[pos+3] << decimation_shifts::pre4; + + m_decimator2.myDecimate( + bufI[pos+0] << decimation_shifts::pre4, + bufQ[pos+0] << decimation_shifts::pre4, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + bufI[pos+2] << decimation_shifts::pre4, + bufQ[pos+2] << decimation_shifts::pre4, + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2] >> decimation_shifts::post4); + (**it).setImag(intbuf[3] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void Decimators::decimate8_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + StorageType intbuf[8]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + intbuf[0] = bufI[pos+1] << decimation_shifts::pre8; + intbuf[1] = bufQ[pos+1] << decimation_shifts::pre8; + intbuf[2] = bufI[pos+3] << decimation_shifts::pre8; + intbuf[3] = bufQ[pos+3] << decimation_shifts::pre8; + intbuf[4] = bufI[pos+5] << decimation_shifts::pre8; + intbuf[5] = bufQ[pos+5] << decimation_shifts::pre8; + intbuf[6] = bufI[pos+7] << decimation_shifts::pre8; + intbuf[7] = bufQ[pos+7] << decimation_shifts::pre8; + + m_decimator2.myDecimate( + bufI[pos+0] << decimation_shifts::pre8, + bufQ[pos+0] << decimation_shifts::pre8, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + bufI[pos+2] << decimation_shifts::pre8, + bufQ[pos+2] << decimation_shifts::pre8, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + bufI[pos+4] << decimation_shifts::pre8, + bufQ[pos+4] << decimation_shifts::pre8, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + bufI[pos+6] << decimation_shifts::pre8, + bufQ[pos+6] << decimation_shifts::pre8, + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6] >> decimation_shifts::post8); + (**it).setImag(intbuf[7] >> decimation_shifts::post8); + ++(*it); + } +} + + +template +void Decimators::decimate16_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + StorageType intbuf[16]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + intbuf[0] = bufI[pos+1] << decimation_shifts::pre16; + intbuf[1] = bufQ[pos+1] << decimation_shifts::pre16; + intbuf[2] = bufI[pos+3] << decimation_shifts::pre16; + intbuf[3] = bufQ[pos+3] << decimation_shifts::pre16; + intbuf[4] = bufI[pos+5] << decimation_shifts::pre16; + intbuf[5] = bufQ[pos+5] << decimation_shifts::pre16; + intbuf[6] = bufI[pos+7] << decimation_shifts::pre16; + intbuf[7] = bufQ[pos+7] << decimation_shifts::pre16; + intbuf[8] = bufI[pos+9] << decimation_shifts::pre16; + intbuf[9] = bufQ[pos+9] << decimation_shifts::pre16; + intbuf[10] = bufI[pos+11] << decimation_shifts::pre16; + intbuf[11] = bufQ[pos+11] << decimation_shifts::pre16; + intbuf[12] = bufI[pos+13] << decimation_shifts::pre16; + intbuf[13] = bufQ[pos+13] << decimation_shifts::pre16; + intbuf[14] = bufI[pos+15] << decimation_shifts::pre16; + intbuf[15] = bufQ[pos+15] << decimation_shifts::pre16; + + m_decimator2.myDecimate( + bufI[pos+0] << decimation_shifts::pre16, + bufQ[pos+0] << decimation_shifts::pre16, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + bufI[pos+2] << decimation_shifts::pre16, + bufQ[pos+2] << decimation_shifts::pre16, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + bufI[pos+4] << decimation_shifts::pre16, + bufQ[pos+4] << decimation_shifts::pre16, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + bufI[pos+6] << decimation_shifts::pre16, + bufQ[pos+6] << decimation_shifts::pre16, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + bufI[pos+8] << decimation_shifts::pre16, + bufQ[pos+8] << decimation_shifts::pre16, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + bufI[pos+10] << decimation_shifts::pre16, + bufQ[pos+10] << decimation_shifts::pre16, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + bufI[pos+12] << decimation_shifts::pre16, + bufQ[pos+12] << decimation_shifts::pre16, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + bufI[pos+14] << decimation_shifts::pre16, + bufQ[pos+14] << decimation_shifts::pre16, + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14] >> decimation_shifts::post16); + (**it).setImag(intbuf[15] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void Decimators::decimate32_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + StorageType intbuf[32]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + intbuf[0] = bufI[pos+1] << decimation_shifts::pre32; + intbuf[1] = bufQ[pos+1] << decimation_shifts::pre32; + intbuf[2] = bufI[pos+3] << decimation_shifts::pre32; + intbuf[3] = bufQ[pos+3] << decimation_shifts::pre32; + intbuf[4] = bufI[pos+5] << decimation_shifts::pre32; + intbuf[5] = bufQ[pos+5] << decimation_shifts::pre32; + intbuf[6] = bufI[pos+7] << decimation_shifts::pre32; + intbuf[7] = bufQ[pos+7] << decimation_shifts::pre32; + intbuf[8] = bufI[pos+9] << decimation_shifts::pre32; + intbuf[9] = bufQ[pos+9] << decimation_shifts::pre32; + intbuf[10] = bufI[pos+11] << decimation_shifts::pre32; + intbuf[11] = bufQ[pos+11] << decimation_shifts::pre32; + intbuf[12] = bufI[pos+13] << decimation_shifts::pre32; + intbuf[13] = bufQ[pos+13] << decimation_shifts::pre32; + intbuf[14] = bufI[pos+15] << decimation_shifts::pre32; + intbuf[15] = bufQ[pos+15] << decimation_shifts::pre32; + intbuf[16] = bufI[pos+17] << decimation_shifts::pre32; + intbuf[17] = bufQ[pos+17] << decimation_shifts::pre32; + intbuf[18] = bufI[pos+19] << decimation_shifts::pre32; + intbuf[19] = bufQ[pos+19] << decimation_shifts::pre32; + intbuf[20] = bufI[pos+21] << decimation_shifts::pre32; + intbuf[21] = bufQ[pos+21] << decimation_shifts::pre32; + intbuf[22] = bufI[pos+23] << decimation_shifts::pre32; + intbuf[23] = bufQ[pos+23] << decimation_shifts::pre32; + intbuf[24] = bufI[pos+25] << decimation_shifts::pre32; + intbuf[25] = bufQ[pos+25] << decimation_shifts::pre32; + intbuf[26] = bufI[pos+27] << decimation_shifts::pre32; + intbuf[27] = bufQ[pos+27] << decimation_shifts::pre32; + intbuf[28] = bufI[pos+29] << decimation_shifts::pre32; + intbuf[29] = bufQ[pos+29] << decimation_shifts::pre32; + intbuf[30] = bufI[pos+31] << decimation_shifts::pre32; + intbuf[31] = bufQ[pos+31] << decimation_shifts::pre32; + + m_decimator2.myDecimate( + bufI[pos+0] << decimation_shifts::pre32, + bufQ[pos+0] << decimation_shifts::pre32, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + bufI[pos+2] << decimation_shifts::pre32, + bufQ[pos+2] << decimation_shifts::pre32, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + bufI[pos+4] << decimation_shifts::pre32, + bufQ[pos+4] << decimation_shifts::pre32, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + bufI[pos+6] << decimation_shifts::pre32, + bufQ[pos+6] << decimation_shifts::pre32, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + bufI[pos+8] << decimation_shifts::pre32, + bufQ[pos+8] << decimation_shifts::pre32, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + bufI[pos+10] << decimation_shifts::pre32, + bufQ[pos+10] << decimation_shifts::pre32, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + bufI[pos+12] << decimation_shifts::pre32, + bufQ[pos+12] << decimation_shifts::pre32, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + bufI[pos+14] << decimation_shifts::pre32, + bufQ[pos+14] << decimation_shifts::pre32, + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + bufI[pos+16] << decimation_shifts::pre32, + bufQ[pos+16] << decimation_shifts::pre32, + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + bufI[pos+18] << decimation_shifts::pre32, + bufQ[pos+18] << decimation_shifts::pre32, + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + bufI[pos+20] << decimation_shifts::pre32, + bufQ[pos+20] << decimation_shifts::pre32, + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + bufI[pos+22] << decimation_shifts::pre32, + bufQ[pos+22] << decimation_shifts::pre32, + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + bufI[pos+24] << decimation_shifts::pre32, + bufQ[pos+24] << decimation_shifts::pre32, + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + bufI[pos+26] << decimation_shifts::pre32, + bufQ[pos+26] << decimation_shifts::pre32, + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + bufI[pos+28] << decimation_shifts::pre32, + bufQ[pos+28] << decimation_shifts::pre32, + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + bufI[pos+30] << decimation_shifts::pre32, + bufQ[pos+30] << decimation_shifts::pre32, + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30] >> decimation_shifts::post32); + (**it).setImag(intbuf[31] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void Decimators::decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +{ + StorageType intbuf[64]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + intbuf[0] = bufI[pos+1] << decimation_shifts::pre64; + intbuf[1] = bufQ[pos+1] << decimation_shifts::pre64; + intbuf[2] = bufI[pos+3] << decimation_shifts::pre64; + intbuf[3] = bufQ[pos+3] << decimation_shifts::pre64; + intbuf[4] = bufI[pos+5] << decimation_shifts::pre64; + intbuf[5] = bufQ[pos+5] << decimation_shifts::pre64; + intbuf[6] = bufI[pos+7] << decimation_shifts::pre64; + intbuf[7] = bufQ[pos+7] << decimation_shifts::pre64; + intbuf[8] = bufI[pos+9] << decimation_shifts::pre64; + intbuf[9] = bufQ[pos+9] << decimation_shifts::pre64; + intbuf[10] = bufI[pos+11] << decimation_shifts::pre64; + intbuf[11] = bufQ[pos+11] << decimation_shifts::pre64; + intbuf[12] = bufI[pos+13] << decimation_shifts::pre64; + intbuf[13] = bufQ[pos+13] << decimation_shifts::pre64; + intbuf[14] = bufI[pos+15] << decimation_shifts::pre64; + intbuf[15] = bufQ[pos+15] << decimation_shifts::pre64; + intbuf[16] = bufI[pos+17] << decimation_shifts::pre64; + intbuf[17] = bufQ[pos+17] << decimation_shifts::pre64; + intbuf[18] = bufI[pos+19] << decimation_shifts::pre64; + intbuf[19] = bufQ[pos+19] << decimation_shifts::pre64; + intbuf[20] = bufI[pos+21] << decimation_shifts::pre64; + intbuf[21] = bufQ[pos+21] << decimation_shifts::pre64; + intbuf[22] = bufI[pos+23] << decimation_shifts::pre64; + intbuf[23] = bufQ[pos+23] << decimation_shifts::pre64; + intbuf[24] = bufI[pos+25] << decimation_shifts::pre64; + intbuf[25] = bufQ[pos+25] << decimation_shifts::pre64; + intbuf[26] = bufI[pos+27] << decimation_shifts::pre64; + intbuf[27] = bufQ[pos+27] << decimation_shifts::pre64; + intbuf[28] = bufI[pos+29] << decimation_shifts::pre64; + intbuf[29] = bufQ[pos+29] << decimation_shifts::pre64; + intbuf[30] = bufI[pos+31] << decimation_shifts::pre64; + intbuf[31] = bufQ[pos+31] << decimation_shifts::pre64; + + intbuf[32] = bufI[pos+33] << decimation_shifts::pre64; + intbuf[33] = bufQ[pos+33] << decimation_shifts::pre64; + intbuf[34] = bufI[pos+35] << decimation_shifts::pre64; + intbuf[35] = bufQ[pos+35] << decimation_shifts::pre64; + intbuf[36] = bufI[pos+37] << decimation_shifts::pre64; + intbuf[37] = bufQ[pos+37] << decimation_shifts::pre64; + intbuf[38] = bufI[pos+39] << decimation_shifts::pre64; + intbuf[39] = bufQ[pos+39] << decimation_shifts::pre64; + intbuf[40] = bufI[pos+41] << decimation_shifts::pre64; + intbuf[41] = bufQ[pos+41] << decimation_shifts::pre64; + intbuf[42] = bufI[pos+43] << decimation_shifts::pre64; + intbuf[43] = bufQ[pos+43] << decimation_shifts::pre64; + intbuf[44] = bufI[pos+45] << decimation_shifts::pre64; + intbuf[45] = bufQ[pos+45] << decimation_shifts::pre64; + intbuf[46] = bufI[pos+47] << decimation_shifts::pre64; + intbuf[47] = bufQ[pos+47] << decimation_shifts::pre64; + intbuf[48] = bufI[pos+49] << decimation_shifts::pre64; + intbuf[49] = bufQ[pos+49] << decimation_shifts::pre64; + intbuf[50] = bufI[pos+51] << decimation_shifts::pre64; + intbuf[51] = bufQ[pos+51] << decimation_shifts::pre64; + intbuf[52] = bufI[pos+53] << decimation_shifts::pre64; + intbuf[53] = bufQ[pos+53] << decimation_shifts::pre64; + intbuf[54] = bufI[pos+55] << decimation_shifts::pre64; + intbuf[55] = bufQ[pos+55] << decimation_shifts::pre64; + intbuf[56] = bufI[pos+57] << decimation_shifts::pre64; + intbuf[57] = bufQ[pos+57] << decimation_shifts::pre64; + intbuf[58] = bufI[pos+59] << decimation_shifts::pre64; + intbuf[59] = bufQ[pos+59] << decimation_shifts::pre64; + intbuf[60] = bufI[pos+61] << decimation_shifts::pre64; + intbuf[61] = bufQ[pos+61] << decimation_shifts::pre64; + intbuf[62] = bufI[pos+63] << decimation_shifts::pre64; + intbuf[63] = bufQ[pos+63] << decimation_shifts::pre64; + + m_decimator2.myDecimate( + bufI[pos+0] << decimation_shifts::pre64, + bufQ[pos+0] << decimation_shifts::pre64, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + bufI[pos+2] << decimation_shifts::pre64, + bufQ[pos+2] << decimation_shifts::pre64, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + bufI[pos+4] << decimation_shifts::pre64, + bufQ[pos+4] << decimation_shifts::pre64, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + bufI[pos+6] << decimation_shifts::pre64, + bufQ[pos+6] << decimation_shifts::pre64, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + bufI[pos+8] << decimation_shifts::pre64, + bufQ[pos+8] << decimation_shifts::pre64, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + bufI[pos+10] << decimation_shifts::pre64, + bufQ[pos+10] << decimation_shifts::pre64, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + bufI[pos+12] << decimation_shifts::pre64, + bufQ[pos+12] << decimation_shifts::pre64, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + bufI[pos+14] << decimation_shifts::pre64, + bufQ[pos+14] << decimation_shifts::pre64, + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + bufI[pos+16] << decimation_shifts::pre64, + bufQ[pos+16] << decimation_shifts::pre64, + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + bufI[pos+18] << decimation_shifts::pre64, + bufQ[pos+18] << decimation_shifts::pre64, + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + bufI[pos+20] << decimation_shifts::pre64, + bufQ[pos+20] << decimation_shifts::pre64, + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + bufI[pos+22] << decimation_shifts::pre64, + bufQ[pos+22] << decimation_shifts::pre64, + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + bufI[pos+24] << decimation_shifts::pre64, + bufQ[pos+24] << decimation_shifts::pre64, + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + bufI[pos+26] << decimation_shifts::pre64, + bufQ[pos+26] << decimation_shifts::pre64, + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + bufI[pos+28] << decimation_shifts::pre64, + bufQ[pos+28] << decimation_shifts::pre64, + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + bufI[pos+30] << decimation_shifts::pre64, + bufQ[pos+30] << decimation_shifts::pre64, + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + bufI[pos+32] << decimation_shifts::pre64, + bufQ[pos+32] << decimation_shifts::pre64, + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + bufI[pos+34] << decimation_shifts::pre64, + bufQ[pos+34] << decimation_shifts::pre64, + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + bufI[pos+36] << decimation_shifts::pre64, + bufQ[pos+36] << decimation_shifts::pre64, + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + bufI[pos+38] << decimation_shifts::pre64, + bufQ[pos+38] << decimation_shifts::pre64, + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + bufI[pos+40] << decimation_shifts::pre64, + bufQ[pos+40] << decimation_shifts::pre64, + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + bufI[pos+42] << decimation_shifts::pre64, + bufQ[pos+42] << decimation_shifts::pre64, + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + bufI[pos+44] << decimation_shifts::pre64, + bufQ[pos+44] << decimation_shifts::pre64, + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + bufI[pos+46] << decimation_shifts::pre64, + bufQ[pos+46] << decimation_shifts::pre64, + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + bufI[pos+48] << decimation_shifts::pre64, + bufQ[pos+48] << decimation_shifts::pre64, + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + bufI[pos+50] << decimation_shifts::pre64, + bufQ[pos+50] << decimation_shifts::pre64, + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + bufI[pos+52] << decimation_shifts::pre64, + bufQ[pos+52] << decimation_shifts::pre64, + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + bufI[pos+54] << decimation_shifts::pre64, + bufQ[pos+54] << decimation_shifts::pre64, + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + bufI[pos+56] << decimation_shifts::pre64, + bufQ[pos+56] << decimation_shifts::pre64, + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + bufI[pos+58] << decimation_shifts::pre64, + bufQ[pos+58] << decimation_shifts::pre64, + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + bufI[pos+60] << decimation_shifts::pre64, + bufQ[pos+60] << decimation_shifts::pre64, + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + bufI[pos+62] << decimation_shifts::pre64, + bufQ[pos+62] << decimation_shifts::pre64, + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62] >> decimation_shifts::post64); + (**it).setImag(intbuf[63] >> decimation_shifts::post64); + ++(*it); + } +} + +#endif /* INCLUDE_GPL_DSP_DECIMATORS_H_ */ diff --git a/android/app/src/main/cpp/dsp/decimatorsff.cpp b/android/app/src/main/cpp/dsp/decimatorsff.cpp new file mode 100644 index 0000000..0564558 --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorsff.cpp @@ -0,0 +1,213 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "decimatorsff.h" + +template<> +SDRBASE_API void DecimatorsFF::decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+0]; + yimag = buf[pos+1]; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template<> +SDRBASE_API void DecimatorsFF::decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+1]; + yimag = buf[pos+0]; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template<> +void DecimatorsFF::decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3]); + yimag = (buf[pos+1] + buf[pos+2]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+7] - buf[pos+4]); + yimag = (- buf[pos+5] - buf[pos+6]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template<> +void DecimatorsFF::decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] + buf[pos+2]); + yimag = (buf[pos+0] - buf[pos+3]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (- buf[pos+5] - buf[pos+6]); + yimag = (buf[pos+7] - buf[pos+4]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template<> +void DecimatorsFF::decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2]); + yimag = (- buf[pos+0] - buf[pos+3]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+6] - buf[pos+5]); + yimag = (buf[pos+4] + buf[pos+7]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template<> +void DecimatorsFF::decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (- buf[pos+0] - buf[pos+3]); + yimag = (buf[pos+1] - buf[pos+2]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+4] + buf[pos+7]); + yimag = (buf[pos+6] - buf[pos+5]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template<> +void DecimatorsFF::decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template<> +void DecimatorsFF::decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + yimag = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template<> +void DecimatorsFF::decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Sup (USB): + // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 + // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + // Inf (LSB): + // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 + // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template<> +void DecimatorsFF::decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Sup (USB): + // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 + // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + // Inf (LSB): + // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 + // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + yimag = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} diff --git a/android/app/src/main/cpp/dsp/decimatorsff.h b/android/app/src/main/cpp/dsp/decimatorsff.h new file mode 100644 index 0000000..c6458eb --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorsff.h @@ -0,0 +1,1139 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DECIMATORSFF_H_ +#define SDRBASE_DSP_DECIMATORSFF_H_ + +#include "dsp/inthalfbandfiltereof.h" +#include "export.h" + +#define DECIMATORSFF_HB_FILTER_ORDER 64 + +/** Decimators with float input and float output */ +template +class DecimatorsFF +{ +public: + SDRBASE_API void decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator2s; // 1st stages - straight + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages +}; + + +template +void DecimatorsFF::decimate2_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[2]; + + for (int pos = 0; pos < nbIAndQ - 3; pos += 4) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0]); + (**it).setImag(intbuf[1]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate8_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + + xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(IQOrder ? xreal[1] : yimag[1]); + (**it).setImag(IQOrder ? yimag[1] : xreal[1]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate8_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + pos += 8; + + xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(IQOrder ? xreal[1] : yimag[1]); + (**it).setImag(IQOrder ? yimag[1] : xreal[1]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate16_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(IQOrder ? xreal[3] : yimag[3]); + (**it).setImag(IQOrder ? yimag[3] : xreal[3]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate16_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(IQOrder ? xreal[3] : yimag[3]); + (**it).setImag(IQOrder ? yimag[3] : xreal[3]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate32_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(IQOrder ? xreal[7] : yimag[7]); + (**it).setImag(IQOrder ? yimag[7] : xreal[7]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate32_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(IQOrder ? xreal[7] : yimag[7]); + (**it).setImag(IQOrder ? yimag[7] : xreal[7]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate64_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2s.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2s.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2s.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2s.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(IQOrder ? xreal[15] : yimag[15]); + (**it).setImag(IQOrder ? yimag[15] : xreal[15]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2s.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2s.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2s.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2s.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(IQOrder ? xreal[15] : yimag[15]); + (**it).setImag(IQOrder ? yimag[15] : xreal[15]); + + ++(*it); + } +} + +template +void DecimatorsFF::decimate4_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[4]; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2]); + (**it).setImag(intbuf[3]); + ++(*it); + } +} + +template +void DecimatorsFF::decimate8_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[8]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 16) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6]); + (**it).setImag(intbuf[7]); + ++(*it); + } +} + +template +void DecimatorsFF::decimate16_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[16]; + + for (int pos = 0; pos < nbIAndQ - 31; pos += 32) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14]); + (**it).setImag(intbuf[15]); + ++(*it); + } +} + +template +void DecimatorsFF::decimate32_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[32]; + + for (int pos = 0; pos < nbIAndQ - 63; pos += 64) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30]); + (**it).setImag(intbuf[31]); + ++(*it); + } +} + +template +void DecimatorsFF::decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[64]; + + for (int pos = 0; pos < nbIAndQ - 127; pos += 128) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + intbuf[32] = buf[pos+66]; + intbuf[33] = buf[pos+67]; + intbuf[34] = buf[pos+70]; + intbuf[35] = buf[pos+71]; + intbuf[36] = buf[pos+74]; + intbuf[37] = buf[pos+75]; + intbuf[38] = buf[pos+78]; + intbuf[39] = buf[pos+79]; + intbuf[40] = buf[pos+82]; + intbuf[41] = buf[pos+83]; + intbuf[42] = buf[pos+86]; + intbuf[43] = buf[pos+87]; + intbuf[44] = buf[pos+90]; + intbuf[45] = buf[pos+91]; + intbuf[46] = buf[pos+94]; + intbuf[47] = buf[pos+95]; + intbuf[48] = buf[pos+98]; + intbuf[49] = buf[pos+99]; + intbuf[50] = buf[pos+102]; + intbuf[51] = buf[pos+103]; + intbuf[52] = buf[pos+106]; + intbuf[53] = buf[pos+107]; + intbuf[54] = buf[pos+110]; + intbuf[55] = buf[pos+111]; + intbuf[56] = buf[pos+114]; + intbuf[57] = buf[pos+115]; + intbuf[58] = buf[pos+118]; + intbuf[59] = buf[pos+119]; + intbuf[60] = buf[pos+122]; + intbuf[61] = buf[pos+123]; + intbuf[62] = buf[pos+126]; + intbuf[63] = buf[pos+127]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64], + buf[pos+65], + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68], + buf[pos+69], + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72], + buf[pos+73], + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76], + buf[pos+77], + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80], + buf[pos+81], + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84], + buf[pos+85], + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88], + buf[pos+89], + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92], + buf[pos+93], + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96], + buf[pos+97], + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100], + buf[pos+101], + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104], + buf[pos+105], + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108], + buf[pos+109], + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112], + buf[pos+113], + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116], + buf[pos+117], + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120], + buf[pos+121], + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124], + buf[pos+125], + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62]); + (**it).setImag(intbuf[63]); + ++(*it); + } +} + +#endif /* SDRBASE_DSP_DECIMATORSFF_H_ */ diff --git a/android/app/src/main/cpp/dsp/decimatorsfi.cpp b/android/app/src/main/cpp/dsp/decimatorsfi.cpp new file mode 100644 index 0000000..002e4a8 --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorsfi.cpp @@ -0,0 +1,214 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "decimatorsfi.h" + +template<> +SDRBASE_API void DecimatorsFI::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+0]; + yimag = buf[pos+1]; + (**it).setReal(xreal * SDR_RX_SCALEF); + (**it).setImag(yimag * SDR_RX_SCALEF); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+1]; + yimag = buf[pos+0]; + (**it).setReal(xreal * SDR_RX_SCALEF); + (**it).setImag(yimag * SDR_RX_SCALEF); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3]); + yimag = (buf[pos+1] + buf[pos+2]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + + xreal = (buf[pos+7] - buf[pos+4]); + yimag = (- buf[pos+5] - buf[pos+6]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] + buf[pos+2]); + yimag = (buf[pos+0] - buf[pos+3]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + + xreal = (- buf[pos+5] - buf[pos+6]); + yimag = (buf[pos+7] - buf[pos+4]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2]); + yimag = (- buf[pos+0] - buf[pos+3]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + + xreal = (buf[pos+6] - buf[pos+5]); + yimag = (buf[pos+4] + buf[pos+7]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (- buf[pos+0] - buf[pos+3]); + yimag = (buf[pos+1] - buf[pos+2]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + + xreal = (buf[pos+4] + buf[pos+7]); + yimag = (buf[pos+6] - buf[pos+5]); + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + ++(*it); + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + + ++(*it); + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + yimag = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + + ++(*it); + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Sup (USB): + // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 + // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + // Inf (LSB): + // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 + // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + + ++(*it); + } +} + +template<> +SDRBASE_API void DecimatorsFI::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Sup (USB): + // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 + // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + // Inf (LSB): + // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 + // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + yimag = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + + (**it).setReal(xreal * SDR_RX_SCALED); + (**it).setImag(yimag * SDR_RX_SCALED); + + ++(*it); + } +} + diff --git a/android/app/src/main/cpp/dsp/decimatorsfi.h b/android/app/src/main/cpp/dsp/decimatorsfi.h new file mode 100644 index 0000000..3a329e9 --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorsfi.h @@ -0,0 +1,1984 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DECIMATORSFI_H_ +#define SDRBASE_DSP_DECIMATORSFI_H_ + +#include "dsp/inthalfbandfiltereof.h" +#include "export.h" + +#define DECIMATORSFI_HB_FILTER_ORDER 64 + +/** Decimators with float input and integer output */ +template +class DecimatorsFI +{ +public: + SDRBASE_API void decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + SDRBASE_API void decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + SDRBASE_API void decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + SDRBASE_API void decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + SDRBASE_API void decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate128_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate256_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator2s; // 1st stages - straight + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages + IntHalfbandFilterEOF m_decimator128; // 7th stages + IntHalfbandFilterEOF m_decimator256; // 8th stages +}; + + +template +void DecimatorsFI::decimate2_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[2]; + + for (int pos = 0; pos < nbIAndQ - 3; pos += 4) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0] * SDR_RX_SCALED); + (**it).setImag(intbuf[1] * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate8_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + + xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal((IQOrder ? xreal[1] : yimag[1]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[1] : xreal[1]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate8_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + pos += 8; + + xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal((IQOrder ? xreal[1] : yimag[1]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[1] : xreal[1]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate16_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal((IQOrder ? xreal[3] : yimag[3]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[3] : xreal[3]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate16_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal((IQOrder ? xreal[3] : yimag[3]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[3] : xreal[3]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate32_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal((IQOrder ? xreal[7] : yimag[7]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[7] : xreal[7]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate32_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal((IQOrder ? xreal[7] : yimag[7]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[7] : xreal[7]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate64_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2s.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2s.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2s.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2s.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal((IQOrder ? xreal[15] : yimag[15]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[15] : xreal[15]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2s.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2s.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2s.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2s.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal((IQOrder ? xreal[15] : yimag[15]) * SDR_RX_SCALED); + (**it).setImag((IQOrder ? yimag[15] : xreal[15]) * SDR_RX_SCALED); + + ++(*it); + } +} + +template +void DecimatorsFI::decimate4_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[4]; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2] * SDR_RX_SCALED); + (**it).setImag(intbuf[3] * SDR_RX_SCALED); + ++(*it); + } +} + +template +void DecimatorsFI::decimate8_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[8]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 16) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6] * SDR_RX_SCALED); + (**it).setImag(intbuf[7] * SDR_RX_SCALED); + ++(*it); + } +} + +template +void DecimatorsFI::decimate16_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[16]; + + for (int pos = 0; pos < nbIAndQ - 31; pos += 32) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14] * SDR_RX_SCALED); + (**it).setImag(intbuf[15] * SDR_RX_SCALED); + ++(*it); + } +} + +template +void DecimatorsFI::decimate32_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[32]; + + for (int pos = 0; pos < nbIAndQ - 63; pos += 64) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30] * SDR_RX_SCALED); + (**it).setImag(intbuf[31] * SDR_RX_SCALED); + ++(*it); + } +} + +template +void DecimatorsFI::decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[64]; + + for (int pos = 0; pos < nbIAndQ - 127; pos += 128) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + intbuf[32] = buf[pos+66]; + intbuf[33] = buf[pos+67]; + intbuf[34] = buf[pos+70]; + intbuf[35] = buf[pos+71]; + intbuf[36] = buf[pos+74]; + intbuf[37] = buf[pos+75]; + intbuf[38] = buf[pos+78]; + intbuf[39] = buf[pos+79]; + intbuf[40] = buf[pos+82]; + intbuf[41] = buf[pos+83]; + intbuf[42] = buf[pos+86]; + intbuf[43] = buf[pos+87]; + intbuf[44] = buf[pos+90]; + intbuf[45] = buf[pos+91]; + intbuf[46] = buf[pos+94]; + intbuf[47] = buf[pos+95]; + intbuf[48] = buf[pos+98]; + intbuf[49] = buf[pos+99]; + intbuf[50] = buf[pos+102]; + intbuf[51] = buf[pos+103]; + intbuf[52] = buf[pos+106]; + intbuf[53] = buf[pos+107]; + intbuf[54] = buf[pos+110]; + intbuf[55] = buf[pos+111]; + intbuf[56] = buf[pos+114]; + intbuf[57] = buf[pos+115]; + intbuf[58] = buf[pos+118]; + intbuf[59] = buf[pos+119]; + intbuf[60] = buf[pos+122]; + intbuf[61] = buf[pos+123]; + intbuf[62] = buf[pos+126]; + intbuf[63] = buf[pos+127]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64], + buf[pos+65], + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68], + buf[pos+69], + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72], + buf[pos+73], + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76], + buf[pos+77], + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80], + buf[pos+81], + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84], + buf[pos+85], + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88], + buf[pos+89], + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92], + buf[pos+93], + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96], + buf[pos+97], + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100], + buf[pos+101], + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104], + buf[pos+105], + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108], + buf[pos+109], + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112], + buf[pos+113], + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116], + buf[pos+117], + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120], + buf[pos+121], + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124], + buf[pos+125], + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62] * SDR_RX_SCALED); + (**it).setImag(intbuf[63] * SDR_RX_SCALED); + ++(*it); + } +} + +template +void DecimatorsFI::decimate128_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[128]; + + for (int pos = 0; pos < nbIAndQ - 255; pos += 256) + { + float *intbufp; + int posi; + + intbufp = intbuf; + posi = pos; + for (int i = 0; i < 4; i++) + { + intbufp[0] = buf[posi+2]; + intbufp[1] = buf[posi+3]; + intbufp[2] = buf[posi+6]; + intbufp[3] = buf[posi+7]; + intbufp[4] = buf[posi+10]; + intbufp[5] = buf[posi+11]; + intbufp[6] = buf[posi+14]; + intbufp[7] = buf[posi+15]; + intbufp[8] = buf[posi+18]; + intbufp[9] = buf[posi+19]; + intbufp[10] = buf[posi+22]; + intbufp[11] = buf[posi+23]; + intbufp[12] = buf[posi+26]; + intbufp[13] = buf[posi+27]; + intbufp[14] = buf[posi+30]; + intbufp[15] = buf[posi+31]; + intbufp[16] = buf[posi+34]; + intbufp[17] = buf[posi+35]; + intbufp[18] = buf[posi+38]; + intbufp[19] = buf[posi+39]; + intbufp[20] = buf[posi+42]; + intbufp[21] = buf[posi+43]; + intbufp[22] = buf[posi+46]; + intbufp[23] = buf[posi+47]; + intbufp[24] = buf[posi+50]; + intbufp[25] = buf[posi+51]; + intbufp[26] = buf[posi+54]; + intbufp[27] = buf[posi+55]; + intbufp[28] = buf[posi+58]; + intbufp[29] = buf[posi+59]; + intbufp[30] = buf[posi+62]; + intbufp[31] = buf[posi+63]; + intbufp += 32; + posi += 64; + } + + intbufp = intbuf; + posi = pos; + for (int i = 0; i < 2; i++) + { + m_decimator2.myDecimate( + buf[posi+0], + buf[posi+1], + &intbufp[0], + &intbufp[1]); + m_decimator2.myDecimate( + buf[posi+4], + buf[posi+5], + &intbufp[2], + &intbufp[3]); + m_decimator2.myDecimate( + buf[posi+8], + buf[posi+9], + &intbufp[4], + &intbufp[5]); + m_decimator2.myDecimate( + buf[posi+12], + buf[posi+13], + &intbufp[6], + &intbufp[7]); + m_decimator2.myDecimate( + buf[posi+16], + buf[posi+17], + &intbufp[8], + &intbufp[9]); + m_decimator2.myDecimate( + buf[posi+20], + buf[posi+21], + &intbufp[10], + &intbufp[11]); + m_decimator2.myDecimate( + buf[posi+24], + buf[posi+25], + &intbufp[12], + &intbufp[13]); + m_decimator2.myDecimate( + buf[posi+28], + buf[posi+29], + &intbufp[14], + &intbufp[15]); + m_decimator2.myDecimate( + buf[posi+32], + buf[posi+33], + &intbufp[16], + &intbufp[17]); + m_decimator2.myDecimate( + buf[posi+36], + buf[posi+37], + &intbufp[18], + &intbufp[19]); + m_decimator2.myDecimate( + buf[posi+40], + buf[posi+41], + &intbufp[20], + &intbufp[21]); + m_decimator2.myDecimate( + buf[posi+44], + buf[posi+45], + &intbufp[22], + &intbufp[23]); + m_decimator2.myDecimate( + buf[posi+48], + buf[posi+49], + &intbufp[24], + &intbufp[25]); + m_decimator2.myDecimate( + buf[posi+52], + buf[posi+53], + &intbufp[26], + &intbufp[27]); + m_decimator2.myDecimate( + buf[posi+56], + buf[posi+57], + &intbufp[28], + &intbufp[29]); + m_decimator2.myDecimate( + buf[posi+60], + buf[posi+61], + &intbufp[30], + &intbufp[31]); + m_decimator2.myDecimate( + buf[posi+64], + buf[posi+65], + &intbufp[32], + &intbufp[33]); + m_decimator2.myDecimate( + buf[posi+68], + buf[posi+69], + &intbufp[34], + &intbufp[35]); + m_decimator2.myDecimate( + buf[posi+72], + buf[posi+73], + &intbufp[36], + &intbufp[37]); + m_decimator2.myDecimate( + buf[posi+76], + buf[posi+77], + &intbufp[38], + &intbufp[39]); + m_decimator2.myDecimate( + buf[posi+80], + buf[posi+81], + &intbufp[40], + &intbufp[41]); + m_decimator2.myDecimate( + buf[posi+84], + buf[posi+85], + &intbufp[42], + &intbufp[43]); + m_decimator2.myDecimate( + buf[posi+88], + buf[posi+89], + &intbufp[44], + &intbufp[45]); + m_decimator2.myDecimate( + buf[posi+92], + buf[posi+93], + &intbufp[46], + &intbufp[47]); + m_decimator2.myDecimate( + buf[posi+96], + buf[posi+97], + &intbufp[48], + &intbufp[49]); + m_decimator2.myDecimate( + buf[posi+100], + buf[posi+101], + &intbufp[50], + &intbufp[51]); + m_decimator2.myDecimate( + buf[posi+104], + buf[posi+105], + &intbufp[52], + &intbufp[53]); + m_decimator2.myDecimate( + buf[posi+108], + buf[posi+109], + &intbufp[54], + &intbufp[55]); + m_decimator2.myDecimate( + buf[posi+112], + buf[posi+113], + &intbufp[56], + &intbufp[57]); + m_decimator2.myDecimate( + buf[posi+116], + buf[posi+117], + &intbufp[58], + &intbufp[59]); + m_decimator2.myDecimate( + buf[posi+120], + buf[posi+121], + &intbufp[60], + &intbufp[61]); + m_decimator2.myDecimate( + buf[posi+124], + buf[posi+125], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + posi += 128; + } + + intbufp = intbuf; + for (int i = 0; i < 2; i++) + { + m_decimator4.myDecimate( + intbufp[0], + intbufp[1], + &intbufp[2], + &intbufp[3]); + m_decimator4.myDecimate( + intbufp[4], + intbufp[5], + &intbufp[6], + &intbufp[7]); + m_decimator4.myDecimate( + intbufp[8], + intbufp[9], + &intbufp[10], + &intbufp[11]); + m_decimator4.myDecimate( + intbufp[12], + intbufp[13], + &intbufp[14], + &intbufp[15]); + m_decimator4.myDecimate( + intbufp[16], + intbufp[17], + &intbufp[18], + &intbufp[19]); + m_decimator4.myDecimate( + intbufp[20], + intbufp[21], + &intbufp[22], + &intbufp[23]); + m_decimator4.myDecimate( + intbufp[24], + intbufp[25], + &intbufp[26], + &intbufp[27]); + m_decimator4.myDecimate( + intbufp[28], + intbufp[29], + &intbufp[30], + &intbufp[31]); + m_decimator4.myDecimate( + intbufp[32], + intbufp[33], + &intbufp[34], + &intbufp[35]); + m_decimator4.myDecimate( + intbufp[36], + intbufp[37], + &intbufp[38], + &intbufp[39]); + m_decimator4.myDecimate( + intbufp[40], + intbufp[41], + &intbufp[42], + &intbufp[43]); + m_decimator4.myDecimate( + intbufp[44], + intbufp[45], + &intbufp[46], + &intbufp[47]); + m_decimator4.myDecimate( + intbufp[48], + intbufp[49], + &intbufp[50], + &intbufp[51]); + m_decimator4.myDecimate( + intbufp[52], + intbufp[53], + &intbufp[54], + &intbufp[55]); + m_decimator4.myDecimate( + intbufp[56], + intbufp[57], + &intbufp[58], + &intbufp[59]); + m_decimator4.myDecimate( + intbufp[60], + intbufp[61], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 2; i++) + { + m_decimator8.myDecimate( + intbufp[2], + intbufp[3], + &intbufp[6], + &intbufp[7]); + m_decimator8.myDecimate( + intbufp[10], + intbufp[11], + &intbufp[14], + &intbufp[15]); + m_decimator8.myDecimate( + intbufp[18], + intbufp[19], + &intbufp[22], + &intbufp[23]); + m_decimator8.myDecimate( + intbufp[26], + intbufp[27], + &intbufp[30], + &intbufp[31]); + m_decimator8.myDecimate( + intbufp[34], + intbufp[35], + &intbufp[38], + &intbufp[39]); + m_decimator8.myDecimate( + intbufp[42], + intbufp[43], + &intbufp[46], + &intbufp[47]); + m_decimator8.myDecimate( + intbufp[50], + intbufp[51], + &intbufp[54], + &intbufp[55]); + m_decimator8.myDecimate( + intbufp[58], + intbufp[59], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 2; i++) + { + m_decimator16.myDecimate( + intbufp[6], + intbufp[7], + &intbufp[14], + &intbufp[15]); + m_decimator16.myDecimate( + intbufp[22], + intbufp[23], + &intbufp[30], + &intbufp[31]); + m_decimator16.myDecimate( + intbufp[38], + intbufp[39], + &intbufp[46], + &intbufp[47]); + m_decimator16.myDecimate( + intbufp[54], + intbufp[55], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 2; i++) + { + m_decimator32.myDecimate( + intbufp[14], + intbufp[15], + &intbufp[30], + &intbufp[31]); + m_decimator32.myDecimate( + intbufp[46], + intbufp[47], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 2; i++) + { + m_decimator64.myDecimate( + intbufp[30], + intbufp[31], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + m_decimator128.myDecimate( + intbuf[62], + intbuf[63], + &intbuf[126], + &intbuf[127]); + + (**it).setReal(intbuf[126] * SDR_RX_SCALED); + (**it).setImag(intbuf[127] * SDR_RX_SCALED); + ++(*it); + } +} + +template +void DecimatorsFI::decimate256_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[256]; + + for (int pos = 0; pos < nbIAndQ - 511; pos += 512) + { + float *intbufp; + int posi; + + intbufp = intbuf; + posi = pos; + for (int i = 0; i < 8; i++) + { + intbufp[0] = buf[posi+2]; + intbufp[1] = buf[posi+3]; + intbufp[2] = buf[posi+6]; + intbufp[3] = buf[posi+7]; + intbufp[4] = buf[posi+10]; + intbufp[5] = buf[posi+11]; + intbufp[6] = buf[posi+14]; + intbufp[7] = buf[posi+15]; + intbufp[8] = buf[posi+18]; + intbufp[9] = buf[posi+19]; + intbufp[10] = buf[posi+22]; + intbufp[11] = buf[posi+23]; + intbufp[12] = buf[posi+26]; + intbufp[13] = buf[posi+27]; + intbufp[14] = buf[posi+30]; + intbufp[15] = buf[posi+31]; + intbufp[16] = buf[posi+34]; + intbufp[17] = buf[posi+35]; + intbufp[18] = buf[posi+38]; + intbufp[19] = buf[posi+39]; + intbufp[20] = buf[posi+42]; + intbufp[21] = buf[posi+43]; + intbufp[22] = buf[posi+46]; + intbufp[23] = buf[posi+47]; + intbufp[24] = buf[posi+50]; + intbufp[25] = buf[posi+51]; + intbufp[26] = buf[posi+54]; + intbufp[27] = buf[posi+55]; + intbufp[28] = buf[posi+58]; + intbufp[29] = buf[posi+59]; + intbufp[30] = buf[posi+62]; + intbufp[31] = buf[posi+63]; + intbufp += 32; + posi += 64; + } + + intbufp = intbuf; + posi = pos; + for (int i = 0; i < 4; i++) + { + m_decimator2.myDecimate( + buf[posi+0], + buf[posi+1], + &intbufp[0], + &intbufp[1]); + m_decimator2.myDecimate( + buf[posi+4], + buf[posi+5], + &intbufp[2], + &intbufp[3]); + m_decimator2.myDecimate( + buf[posi+8], + buf[posi+9], + &intbufp[4], + &intbufp[5]); + m_decimator2.myDecimate( + buf[posi+12], + buf[posi+13], + &intbufp[6], + &intbufp[7]); + m_decimator2.myDecimate( + buf[posi+16], + buf[posi+17], + &intbufp[8], + &intbufp[9]); + m_decimator2.myDecimate( + buf[posi+20], + buf[posi+21], + &intbufp[10], + &intbufp[11]); + m_decimator2.myDecimate( + buf[posi+24], + buf[posi+25], + &intbufp[12], + &intbufp[13]); + m_decimator2.myDecimate( + buf[posi+28], + buf[posi+29], + &intbufp[14], + &intbufp[15]); + m_decimator2.myDecimate( + buf[posi+32], + buf[posi+33], + &intbufp[16], + &intbufp[17]); + m_decimator2.myDecimate( + buf[posi+36], + buf[posi+37], + &intbufp[18], + &intbufp[19]); + m_decimator2.myDecimate( + buf[posi+40], + buf[posi+41], + &intbufp[20], + &intbufp[21]); + m_decimator2.myDecimate( + buf[posi+44], + buf[posi+45], + &intbufp[22], + &intbufp[23]); + m_decimator2.myDecimate( + buf[posi+48], + buf[posi+49], + &intbufp[24], + &intbufp[25]); + m_decimator2.myDecimate( + buf[posi+52], + buf[posi+53], + &intbufp[26], + &intbufp[27]); + m_decimator2.myDecimate( + buf[posi+56], + buf[posi+57], + &intbufp[28], + &intbufp[29]); + m_decimator2.myDecimate( + buf[posi+60], + buf[posi+61], + &intbufp[30], + &intbufp[31]); + m_decimator2.myDecimate( + buf[posi+64], + buf[posi+65], + &intbufp[32], + &intbufp[33]); + m_decimator2.myDecimate( + buf[posi+68], + buf[posi+69], + &intbufp[34], + &intbufp[35]); + m_decimator2.myDecimate( + buf[posi+72], + buf[posi+73], + &intbufp[36], + &intbufp[37]); + m_decimator2.myDecimate( + buf[posi+76], + buf[posi+77], + &intbufp[38], + &intbufp[39]); + m_decimator2.myDecimate( + buf[posi+80], + buf[posi+81], + &intbufp[40], + &intbufp[41]); + m_decimator2.myDecimate( + buf[posi+84], + buf[posi+85], + &intbufp[42], + &intbufp[43]); + m_decimator2.myDecimate( + buf[posi+88], + buf[posi+89], + &intbufp[44], + &intbufp[45]); + m_decimator2.myDecimate( + buf[posi+92], + buf[posi+93], + &intbufp[46], + &intbufp[47]); + m_decimator2.myDecimate( + buf[posi+96], + buf[posi+97], + &intbufp[48], + &intbufp[49]); + m_decimator2.myDecimate( + buf[posi+100], + buf[posi+101], + &intbufp[50], + &intbufp[51]); + m_decimator2.myDecimate( + buf[posi+104], + buf[posi+105], + &intbufp[52], + &intbufp[53]); + m_decimator2.myDecimate( + buf[posi+108], + buf[posi+109], + &intbufp[54], + &intbufp[55]); + m_decimator2.myDecimate( + buf[posi+112], + buf[posi+113], + &intbufp[56], + &intbufp[57]); + m_decimator2.myDecimate( + buf[posi+116], + buf[posi+117], + &intbufp[58], + &intbufp[59]); + m_decimator2.myDecimate( + buf[posi+120], + buf[posi+121], + &intbufp[60], + &intbufp[61]); + m_decimator2.myDecimate( + buf[posi+124], + buf[posi+125], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + posi += 128; + } + + intbufp = intbuf; + for (int i = 0; i < 4; i++) + { + m_decimator4.myDecimate( + intbufp[0], + intbufp[1], + &intbufp[2], + &intbufp[3]); + m_decimator4.myDecimate( + intbufp[4], + intbufp[5], + &intbufp[6], + &intbufp[7]); + m_decimator4.myDecimate( + intbufp[8], + intbufp[9], + &intbufp[10], + &intbufp[11]); + m_decimator4.myDecimate( + intbufp[12], + intbufp[13], + &intbufp[14], + &intbufp[15]); + m_decimator4.myDecimate( + intbufp[16], + intbufp[17], + &intbufp[18], + &intbufp[19]); + m_decimator4.myDecimate( + intbufp[20], + intbufp[21], + &intbufp[22], + &intbufp[23]); + m_decimator4.myDecimate( + intbufp[24], + intbufp[25], + &intbufp[26], + &intbufp[27]); + m_decimator4.myDecimate( + intbufp[28], + intbufp[29], + &intbufp[30], + &intbufp[31]); + m_decimator4.myDecimate( + intbufp[32], + intbufp[33], + &intbufp[34], + &intbufp[35]); + m_decimator4.myDecimate( + intbufp[36], + intbufp[37], + &intbufp[38], + &intbufp[39]); + m_decimator4.myDecimate( + intbufp[40], + intbufp[41], + &intbufp[42], + &intbufp[43]); + m_decimator4.myDecimate( + intbufp[44], + intbufp[45], + &intbufp[46], + &intbufp[47]); + m_decimator4.myDecimate( + intbufp[48], + intbufp[49], + &intbufp[50], + &intbufp[51]); + m_decimator4.myDecimate( + intbufp[52], + intbufp[53], + &intbufp[54], + &intbufp[55]); + m_decimator4.myDecimate( + intbufp[56], + intbufp[57], + &intbufp[58], + &intbufp[59]); + m_decimator4.myDecimate( + intbufp[60], + intbufp[61], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 4; i++) + { + m_decimator8.myDecimate( + intbufp[2], + intbufp[3], + &intbufp[6], + &intbufp[7]); + m_decimator8.myDecimate( + intbufp[10], + intbufp[11], + &intbufp[14], + &intbufp[15]); + m_decimator8.myDecimate( + intbufp[18], + intbufp[19], + &intbufp[22], + &intbufp[23]); + m_decimator8.myDecimate( + intbufp[26], + intbufp[27], + &intbufp[30], + &intbufp[31]); + m_decimator8.myDecimate( + intbufp[34], + intbufp[35], + &intbufp[38], + &intbufp[39]); + m_decimator8.myDecimate( + intbufp[42], + intbufp[43], + &intbufp[46], + &intbufp[47]); + m_decimator8.myDecimate( + intbufp[50], + intbufp[51], + &intbufp[54], + &intbufp[55]); + m_decimator8.myDecimate( + intbufp[58], + intbufp[59], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 4; i++) + { + m_decimator16.myDecimate( + intbufp[6], + intbufp[7], + &intbufp[14], + &intbufp[15]); + m_decimator16.myDecimate( + intbufp[22], + intbufp[23], + &intbufp[30], + &intbufp[31]); + m_decimator16.myDecimate( + intbufp[38], + intbufp[39], + &intbufp[46], + &intbufp[47]); + m_decimator16.myDecimate( + intbufp[54], + intbufp[55], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 4; i++) + { + m_decimator32.myDecimate( + intbufp[14], + intbufp[15], + &intbufp[30], + &intbufp[31]); + m_decimator32.myDecimate( + intbufp[46], + intbufp[47], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 4; i++) + { + m_decimator64.myDecimate( + intbufp[30], + intbufp[31], + &intbufp[62], + &intbufp[63]); + intbufp += 64; + } + + intbufp = intbuf; + for (int i = 0; i < 2; i++) + { + m_decimator128.myDecimate( + intbufp[62], + intbufp[63], + &intbufp[126], + &intbufp[127]); + intbufp += 128; + } + + m_decimator256.myDecimate( + intbuf[126], + intbuf[127], + &intbuf[254], + &intbuf[255]); + + (**it).setReal(intbuf[254] * SDR_RX_SCALED); + (**it).setImag(intbuf[255] * SDR_RX_SCALED); + ++(*it); + } +} + +#endif /* SDRBASE_DSP_DECIMATORSFI_H_ */ diff --git a/android/app/src/main/cpp/dsp/decimatorsif.h b/android/app/src/main/cpp/dsp/decimatorsif.h new file mode 100644 index 0000000..1104605 --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorsif.h @@ -0,0 +1,1238 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DECIMATORSIF_H_ +#define SDRBASE_DSP_DECIMATORSIF_H_ + +#include "dsp/dsptypes.h" +#include "dsp/inthalfbandfiltereof.h" + +#define DECIMATORS_IF_FILTER_ORDER 64 + +template +struct decimation_scale +{ + static constexpr float scaleIn = 1.0f / (1 << (InputBits - 1)); +}; + +template +class DecimatorsIF { +public: + // interleaved I/Q input buffer + void decimate1(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator2s; // 1st stages - straight + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages +}; + +template +void DecimatorsIF::decimate1(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = (IQOrder ? buf[pos+0] : buf[pos+1]) * decimation_scale::scaleIn; + yimag = (IQOrder ? buf[pos+1] : buf[pos+0]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template +void DecimatorsIF::decimate2_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (IQOrder ? (buf[pos+0] - buf[pos+3]) : (buf[pos+1] + buf[pos+2])) * decimation_scale::scaleIn; + yimag = (IQOrder ? (buf[pos+1] + buf[pos+2]) : (buf[pos+0] - buf[pos+3])) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (IQOrder ? (buf[pos+7] - buf[pos+4]) : (- buf[pos+5] - buf[pos+6])) * decimation_scale::scaleIn; + yimag = (IQOrder ? (- buf[pos+5] - buf[pos+6]) : (buf[pos+7] - buf[pos+4])) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template +void DecimatorsIF::decimate2_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (IQOrder ? (buf[pos+1] - buf[pos+2]) : (- buf[pos+0] - buf[pos+3])) * decimation_scale::scaleIn; + yimag = (IQOrder ? (- buf[pos+0] - buf[pos+3]) : (buf[pos+1] - buf[pos+2])) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (IQOrder ? (buf[pos+6] - buf[pos+5]) : (buf[pos+4] + buf[pos+7])) * decimation_scale::scaleIn; + yimag = (IQOrder ? (buf[pos+4] + buf[pos+7]) : (buf[pos+6] - buf[pos+5])) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template +void DecimatorsIF::decimate2_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[2]; + + for (int pos = 0; pos < nbIAndQ - 3; pos += 4) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0] * decimation_scale::scaleIn); + (**it).setImag(intbuf[1] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = IQOrder ? + (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) * decimation_scale::scaleIn : + (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) * decimation_scale::scaleIn; + yimag = IQOrder ? + (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) * decimation_scale::scaleIn : + (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) * decimation_scale::scaleIn; + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = IQOrder ? + (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) * decimation_scale::scaleIn : + (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) * decimation_scale::scaleIn; + yimag = IQOrder ? + (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) * decimation_scale::scaleIn : + (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) * decimation_scale::scaleIn; + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[4]; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2] * decimation_scale::scaleIn); + (**it).setImag(intbuf[3] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + + xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal((IQOrder ? xreal[1] : yimag[1]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[1] : xreal[1]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + pos += 8; + + xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal((IQOrder ? xreal[1] : yimag[1]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[1] : xreal[1]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[8]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 16) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6] * decimation_scale::scaleIn); + (**it).setImag(intbuf[7] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal((IQOrder ? xreal[3] : yimag[3]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[3] : xreal[3]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal((IQOrder ? xreal[3] : yimag[3]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[3] : xreal[3]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[16]; + + for (int pos = 0; pos < nbIAndQ - 31; pos += 32) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14] * decimation_scale::scaleIn); + (**it).setImag(intbuf[15] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal((IQOrder ? xreal[7] : yimag[7]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[7] : xreal[7]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal((IQOrder ? xreal[7] : yimag[7]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[7] : xreal[7]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[32]; + + for (int pos = 0; pos < nbIAndQ - 63; pos += 64) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30] * decimation_scale::scaleIn); + (**it).setImag(intbuf[31] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2s.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2s.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2s.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2s.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal((IQOrder ? xreal[15] : yimag[15]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[15] : xreal[15]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2s.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2s.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2s.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2s.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2s.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2s.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2s.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2s.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal((IQOrder ? xreal[15] : yimag[15]) * decimation_scale::scaleIn); + (**it).setImag((IQOrder ? yimag[15] : xreal[15]) * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[64]; + + for (int pos = 0; pos < nbIAndQ - 127; pos += 128) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + intbuf[32] = buf[pos+66]; + intbuf[33] = buf[pos+67]; + intbuf[34] = buf[pos+70]; + intbuf[35] = buf[pos+71]; + intbuf[36] = buf[pos+74]; + intbuf[37] = buf[pos+75]; + intbuf[38] = buf[pos+78]; + intbuf[39] = buf[pos+79]; + intbuf[40] = buf[pos+82]; + intbuf[41] = buf[pos+83]; + intbuf[42] = buf[pos+86]; + intbuf[43] = buf[pos+87]; + intbuf[44] = buf[pos+90]; + intbuf[45] = buf[pos+91]; + intbuf[46] = buf[pos+94]; + intbuf[47] = buf[pos+95]; + intbuf[48] = buf[pos+98]; + intbuf[49] = buf[pos+99]; + intbuf[50] = buf[pos+102]; + intbuf[51] = buf[pos+103]; + intbuf[52] = buf[pos+106]; + intbuf[53] = buf[pos+107]; + intbuf[54] = buf[pos+110]; + intbuf[55] = buf[pos+111]; + intbuf[56] = buf[pos+114]; + intbuf[57] = buf[pos+115]; + intbuf[58] = buf[pos+118]; + intbuf[59] = buf[pos+119]; + intbuf[60] = buf[pos+122]; + intbuf[61] = buf[pos+123]; + intbuf[62] = buf[pos+126]; + intbuf[63] = buf[pos+127]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64], + buf[pos+65], + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68], + buf[pos+69], + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72], + buf[pos+73], + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76], + buf[pos+77], + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80], + buf[pos+81], + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84], + buf[pos+85], + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88], + buf[pos+89], + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92], + buf[pos+93], + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96], + buf[pos+97], + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100], + buf[pos+101], + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104], + buf[pos+105], + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108], + buf[pos+109], + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112], + buf[pos+113], + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116], + buf[pos+117], + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120], + buf[pos+121], + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124], + buf[pos+125], + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62] * decimation_scale::scaleIn); + (**it).setImag(intbuf[63] * decimation_scale::scaleIn); + ++(*it); + } +} + +#endif /* SDRBASE_DSP_DECIMATORSIF_H_ */ diff --git a/android/app/src/main/cpp/dsp/decimatorsu.h b/android/app/src/main/cpp/dsp/decimatorsu.h new file mode 100644 index 0000000..e0b8db3 --- /dev/null +++ b/android/app/src/main/cpp/dsp/decimatorsu.h @@ -0,0 +1,3254 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +// // +// This decimation class includes unsigned to signed with shifting. Typically // +// for 8 bit samples issued from RTL-SDR the conversion is: // +// signed = unsigned - 127. Here the "Shift" value is 127. // +// The shift value is given as a template parameter // +// // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GPL_DSP_DECIMATORSU_H_ +#define INCLUDE_GPL_DSP_DECIMATORSU_H_ + +#include "dsp/dsptypes.h" +#include "dsp/inthalfbandfiltereo.h" + +#define DECIMATORS_HB_FILTER_ORDER 64 + +template +struct decimation_shifts +{ + static const uint pre1 = 0; + static const uint pre2 = 0; + static const uint post2 = 0; + static const uint pre4 = 0; + static const uint post4 = 0; + static const uint pre8 = 0; + static const uint post8 = 0; + static const uint pre16 = 0; + static const uint post16 = 0; + static const uint pre32 = 0; + static const uint post32 = 0; + static const uint pre64 = 0; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<24, 24> +{ + static const uint pre1 = 0; + static const uint pre2 = 0; + static const uint post2 = 1; + static const uint pre4 = 0; + static const uint post4 = 2; + static const uint pre8 = 0; + static const uint post8 = 3; + static const uint pre16 = 0; + static const uint post16 = 4; + static const uint pre32 = 0; + static const uint post32 = 5; + static const uint pre64 = 0; + static const uint post64 = 6; +}; + +template<> +struct decimation_shifts<16, 16> +{ + static const uint pre1 = 0; + static const uint pre2 = 0; + static const uint post2 = 1; + static const uint pre4 = 0; + static const uint post4 = 2; + static const uint pre8 = 0; + static const uint post8 = 3; + static const uint pre16 = 0; + static const uint post16 = 4; + static const uint pre32 = 0; + static const uint post32 = 5; + static const uint pre64 = 0; + static const uint post64 = 6; +}; + +template<> +struct decimation_shifts<24, 16> +{ + static const uint pre1 = 8; + static const uint pre2 = 7; + static const uint post2 = 0; + static const uint pre4 = 6; + static const uint post4 = 0; + static const uint pre8 = 5; + static const uint post8 = 0; + static const uint pre16 = 4; + static const uint post16 = 0; + static const uint pre32 = 3; + static const uint post32 = 0; + static const uint pre64 = 2; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<16, 12> +{ + static const uint pre1 = 4; + static const uint pre2 = 3; + static const uint post2 = 0; + static const uint pre4 = 2; + static const uint post4 = 0; + static const uint pre8 = 1; + static const uint post8 = 0; + static const uint pre16 = 0; + static const uint post16 = 0; + static const uint pre32 = 0; + static const uint post32 = 1; + static const uint pre64 = 0; + static const uint post64 = 2; +}; + +template<> +struct decimation_shifts<24, 12> +{ + static const uint pre1 = 12; + static const uint pre2 = 11; + static const uint post2 = 0; + static const uint pre4 = 10; + static const uint post4 = 0; + static const uint pre8 = 9; + static const uint post8 = 0; + static const uint pre16 = 8; + static const uint post16 = 0; + static const uint pre32 = 7; + static const uint post32 = 0; + static const uint pre64 = 6; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<16, 8> +{ + static const uint pre1 = 8; + static const uint pre2 = 7; + static const uint post2 = 0; + static const uint pre4 = 6; + static const uint post4 = 0; + static const uint pre8 = 5; + static const uint post8 = 0; + static const uint pre16 = 4; + static const uint post16 = 0; + static const uint pre32 = 3; + static const uint post32 = 0; + static const uint pre64 = 2; + static const uint post64 = 0; +}; + +template<> +struct decimation_shifts<24, 8> +{ + static const uint pre1 = 16; + static const uint pre2 = 15; + static const uint post2 = 0; + static const uint pre4 = 14; + static const uint post4 = 0; + static const uint pre8 = 13; + static const uint post8 = 0; + static const uint pre16 = 12; + static const uint post16 = 0; + static const uint pre32 = 11; + static const uint post32 = 0; + static const uint pre64 = 10; + static const uint post64 = 0; +}; + +template +class DecimatorsU +{ +public: + // interleaved I/Q input buffer + void decimate1(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len); + +private: +#ifdef SDR_RX_SAMPLE_24BIT + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator2s; // 1st stages - straight + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages +#else + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator2s; // 1st stages - straight + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages +#endif +}; + +template +void DecimatorsU::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) +{ + qint32 xreal, yimag; + + for (int pos = 0; pos < len - 1; pos += 2) + { + xreal = IQOrder ? buf[pos+0] - Shift : buf[pos+1] - Shift; + yimag = IQOrder ? buf[pos+1] - Shift : buf[pos+0] - Shift; + (**it).setReal(xreal << decimation_shifts::pre1); // Valgrind optim (2 - comment not repeated) + (**it).setImag(yimag << decimation_shifts::pre1); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template +void DecimatorsU::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[4]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void DecimatorsU::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[4]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void DecimatorsU::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2s.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2s.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateSup( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[IQOrder ? 0 : 1] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder ? 1 : 0] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[IQOrder ? 2 : 3] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder ? 3 : 2] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void DecimatorsU::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2s.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2s.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateInf( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[IQOrder ? 0 : 1] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder ? 1 : 0] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[IQOrder ? 2 : 3] >> decimation_shifts::post4); + (**it).setImag(buf4[IQOrder ? 3 : 2] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[16], buf4[8], buf8[4]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2s.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre8, + (buf[pos+1] - Shift) << decimation_shifts::pre8, + (buf[pos+2] - Shift) << decimation_shifts::pre8, + (buf[pos+3] - Shift) << decimation_shifts::pre8, + (buf[pos+4] - Shift) << decimation_shifts::pre8, + (buf[pos+5] - Shift) << decimation_shifts::pre8, + (buf[pos+6] - Shift) << decimation_shifts::pre8, + (buf[pos+7] - Shift) << decimation_shifts::pre8, + &buf2[0]); + + + m_decimator2s.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre8, + (buf[pos+9] - Shift) << decimation_shifts::pre8, + (buf[pos+10] - Shift) << decimation_shifts::pre8, + (buf[pos+11] - Shift) << decimation_shifts::pre8, + (buf[pos+12] - Shift) << decimation_shifts::pre8, + (buf[pos+13] - Shift) << decimation_shifts::pre8, + (buf[pos+14] - Shift) << decimation_shifts::pre8, + (buf[pos+15] - Shift) << decimation_shifts::pre8, + &buf2[4]); + + + m_decimator2s.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre8, + (buf[pos+17] - Shift) << decimation_shifts::pre8, + (buf[pos+18] - Shift) << decimation_shifts::pre8, + (buf[pos+19] - Shift) << decimation_shifts::pre8, + (buf[pos+20] - Shift) << decimation_shifts::pre8, + (buf[pos+21] - Shift) << decimation_shifts::pre8, + (buf[pos+22] - Shift) << decimation_shifts::pre8, + (buf[pos+23] - Shift) << decimation_shifts::pre8, + &buf2[8]); + + + m_decimator2s.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre8, + (buf[pos+25] - Shift) << decimation_shifts::pre8, + (buf[pos+26] - Shift) << decimation_shifts::pre8, + (buf[pos+27] - Shift) << decimation_shifts::pre8, + (buf[pos+28] - Shift) << decimation_shifts::pre8, + (buf[pos+29] - Shift) << decimation_shifts::pre8, + (buf[pos+30] - Shift) << decimation_shifts::pre8, + (buf[pos+31] - Shift) << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[IQOrder ? 0 : 1] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder ? 1 : 0] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[IQOrder ? 2 : 3] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder ? 3 : 2] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[16], buf4[8], buf8[4]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2s.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre8, + (buf[pos+1] - Shift) << decimation_shifts::pre8, + (buf[pos+2] - Shift) << decimation_shifts::pre8, + (buf[pos+3] - Shift) << decimation_shifts::pre8, + (buf[pos+4] - Shift) << decimation_shifts::pre8, + (buf[pos+5] - Shift) << decimation_shifts::pre8, + (buf[pos+6] - Shift) << decimation_shifts::pre8, + (buf[pos+7] - Shift) << decimation_shifts::pre8, + &buf2[0]); + + + m_decimator2s.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre8, + (buf[pos+9] - Shift) << decimation_shifts::pre8, + (buf[pos+10] - Shift) << decimation_shifts::pre8, + (buf[pos+11] - Shift) << decimation_shifts::pre8, + (buf[pos+12] - Shift) << decimation_shifts::pre8, + (buf[pos+13] - Shift) << decimation_shifts::pre8, + (buf[pos+14] - Shift) << decimation_shifts::pre8, + (buf[pos+15] - Shift) << decimation_shifts::pre8, + &buf2[4]); + + + m_decimator2s.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre8, + (buf[pos+17] - Shift) << decimation_shifts::pre8, + (buf[pos+18] - Shift) << decimation_shifts::pre8, + (buf[pos+19] - Shift) << decimation_shifts::pre8, + (buf[pos+20] - Shift) << decimation_shifts::pre8, + (buf[pos+21] - Shift) << decimation_shifts::pre8, + (buf[pos+22] - Shift) << decimation_shifts::pre8, + (buf[pos+23] - Shift) << decimation_shifts::pre8, + &buf2[8]); + + + m_decimator2s.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre8, + (buf[pos+25] - Shift) << decimation_shifts::pre8, + (buf[pos+26] - Shift) << decimation_shifts::pre8, + (buf[pos+27] - Shift) << decimation_shifts::pre8, + (buf[pos+28] - Shift) << decimation_shifts::pre8, + (buf[pos+29] - Shift) << decimation_shifts::pre8, + (buf[pos+30] - Shift) << decimation_shifts::pre8, + (buf[pos+31] - Shift) << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[IQOrder ? 0 : 1] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder ? 1 : 0] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[IQOrder ? 2 : 3] >> decimation_shifts::post8); + (**it).setImag(buf8[IQOrder ? 3 : 2] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void DecimatorsU::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2s.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre16, + (buf[pos+1] - Shift) << decimation_shifts::pre16, + (buf[pos+2] - Shift) << decimation_shifts::pre16, + (buf[pos+3] - Shift) << decimation_shifts::pre16, + (buf[pos+4] - Shift) << decimation_shifts::pre16, + (buf[pos+5] - Shift) << decimation_shifts::pre16, + (buf[pos+6] - Shift) << decimation_shifts::pre16, + (buf[pos+7] - Shift) << decimation_shifts::pre16, + &buf2[0]); + + + m_decimator2s.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre16, + (buf[pos+9] - Shift) << decimation_shifts::pre16, + (buf[pos+10] - Shift) << decimation_shifts::pre16, + (buf[pos+11] - Shift) << decimation_shifts::pre16, + (buf[pos+12] - Shift) << decimation_shifts::pre16, + (buf[pos+13] - Shift) << decimation_shifts::pre16, + (buf[pos+14] - Shift) << decimation_shifts::pre16, + (buf[pos+15] - Shift) << decimation_shifts::pre16, + &buf2[4]); + + + m_decimator2s.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre16, + (buf[pos+17] - Shift) << decimation_shifts::pre16, + (buf[pos+18] - Shift) << decimation_shifts::pre16, + (buf[pos+19] - Shift) << decimation_shifts::pre16, + (buf[pos+20] - Shift) << decimation_shifts::pre16, + (buf[pos+21] - Shift) << decimation_shifts::pre16, + (buf[pos+22] - Shift) << decimation_shifts::pre16, + (buf[pos+23] - Shift) << decimation_shifts::pre16, + &buf2[8]); + + + m_decimator2s.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre16, + (buf[pos+25] - Shift) << decimation_shifts::pre16, + (buf[pos+26] - Shift) << decimation_shifts::pre16, + (buf[pos+27] - Shift) << decimation_shifts::pre16, + (buf[pos+28] - Shift) << decimation_shifts::pre16, + (buf[pos+29] - Shift) << decimation_shifts::pre16, + (buf[pos+30] - Shift) << decimation_shifts::pre16, + (buf[pos+31] - Shift) << decimation_shifts::pre16, + &buf2[12]); + + + m_decimator2s.myDecimateInf( + (buf[pos+32] - Shift) << decimation_shifts::pre16, + (buf[pos+33] - Shift) << decimation_shifts::pre16, + (buf[pos+34] - Shift) << decimation_shifts::pre16, + (buf[pos+35] - Shift) << decimation_shifts::pre16, + (buf[pos+36] - Shift) << decimation_shifts::pre16, + (buf[pos+37] - Shift) << decimation_shifts::pre16, + (buf[pos+38] - Shift) << decimation_shifts::pre16, + (buf[pos+39] - Shift) << decimation_shifts::pre16, + &buf2[16]); + + + m_decimator2s.myDecimateInf( + (buf[pos+40] - Shift) << decimation_shifts::pre16, + (buf[pos+41] - Shift) << decimation_shifts::pre16, + (buf[pos+42] - Shift) << decimation_shifts::pre16, + (buf[pos+43] - Shift) << decimation_shifts::pre16, + (buf[pos+44] - Shift) << decimation_shifts::pre16, + (buf[pos+45] - Shift) << decimation_shifts::pre16, + (buf[pos+46] - Shift) << decimation_shifts::pre16, + (buf[pos+47] - Shift) << decimation_shifts::pre16, + &buf2[20]); + + + m_decimator2s.myDecimateInf( + (buf[pos+48] - Shift) << decimation_shifts::pre16, + (buf[pos+49] - Shift) << decimation_shifts::pre16, + (buf[pos+50] - Shift) << decimation_shifts::pre16, + (buf[pos+51] - Shift) << decimation_shifts::pre16, + (buf[pos+52] - Shift) << decimation_shifts::pre16, + (buf[pos+53] - Shift) << decimation_shifts::pre16, + (buf[pos+54] - Shift) << decimation_shifts::pre16, + (buf[pos+55] - Shift) << decimation_shifts::pre16, + &buf2[24]); + + + m_decimator2s.myDecimateInf( + (buf[pos+56] - Shift) << decimation_shifts::pre16, + (buf[pos+57] - Shift) << decimation_shifts::pre16, + (buf[pos+58] - Shift) << decimation_shifts::pre16, + (buf[pos+59] - Shift) << decimation_shifts::pre16, + (buf[pos+60] - Shift) << decimation_shifts::pre16, + (buf[pos+61] - Shift) << decimation_shifts::pre16, + (buf[pos+62] - Shift) << decimation_shifts::pre16, + (buf[pos+63] - Shift) << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[IQOrder ? 0 : 1] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder ? 1 : 0] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[IQOrder ? 2 : 3] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder ? 3 : 2] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void DecimatorsU::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2s.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre16, + (buf[pos+1] - Shift) << decimation_shifts::pre16, + (buf[pos+2] - Shift) << decimation_shifts::pre16, + (buf[pos+3] - Shift) << decimation_shifts::pre16, + (buf[pos+4] - Shift) << decimation_shifts::pre16, + (buf[pos+5] - Shift) << decimation_shifts::pre16, + (buf[pos+6] - Shift) << decimation_shifts::pre16, + (buf[pos+7] - Shift) << decimation_shifts::pre16, + &buf2[0]); + + + m_decimator2s.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre16, + (buf[pos+9] - Shift) << decimation_shifts::pre16, + (buf[pos+10] - Shift) << decimation_shifts::pre16, + (buf[pos+11] - Shift) << decimation_shifts::pre16, + (buf[pos+12] - Shift) << decimation_shifts::pre16, + (buf[pos+13] - Shift) << decimation_shifts::pre16, + (buf[pos+14] - Shift) << decimation_shifts::pre16, + (buf[pos+15] - Shift) << decimation_shifts::pre16, + &buf2[4]); + + + m_decimator2s.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre16, + (buf[pos+17] - Shift) << decimation_shifts::pre16, + (buf[pos+18] - Shift) << decimation_shifts::pre16, + (buf[pos+19] - Shift) << decimation_shifts::pre16, + (buf[pos+20] - Shift) << decimation_shifts::pre16, + (buf[pos+21] - Shift) << decimation_shifts::pre16, + (buf[pos+22] - Shift) << decimation_shifts::pre16, + (buf[pos+23] - Shift) << decimation_shifts::pre16, + &buf2[8]); + + + m_decimator2s.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre16, + (buf[pos+25] - Shift) << decimation_shifts::pre16, + (buf[pos+26] - Shift) << decimation_shifts::pre16, + (buf[pos+27] - Shift) << decimation_shifts::pre16, + (buf[pos+28] - Shift) << decimation_shifts::pre16, + (buf[pos+29] - Shift) << decimation_shifts::pre16, + (buf[pos+30] - Shift) << decimation_shifts::pre16, + (buf[pos+31] - Shift) << decimation_shifts::pre16, + &buf2[12]); + + + m_decimator2s.myDecimateSup( + (buf[pos+32] - Shift) << decimation_shifts::pre16, + (buf[pos+33] - Shift) << decimation_shifts::pre16, + (buf[pos+34] - Shift) << decimation_shifts::pre16, + (buf[pos+35] - Shift) << decimation_shifts::pre16, + (buf[pos+36] - Shift) << decimation_shifts::pre16, + (buf[pos+37] - Shift) << decimation_shifts::pre16, + (buf[pos+38] - Shift) << decimation_shifts::pre16, + (buf[pos+39] - Shift) << decimation_shifts::pre16, + &buf2[16]); + + + m_decimator2s.myDecimateSup( + (buf[pos+40] - Shift) << decimation_shifts::pre16, + (buf[pos+41] - Shift) << decimation_shifts::pre16, + (buf[pos+42] - Shift) << decimation_shifts::pre16, + (buf[pos+43] - Shift) << decimation_shifts::pre16, + (buf[pos+44] - Shift) << decimation_shifts::pre16, + (buf[pos+45] - Shift) << decimation_shifts::pre16, + (buf[pos+46] - Shift) << decimation_shifts::pre16, + (buf[pos+47] - Shift) << decimation_shifts::pre16, + &buf2[20]); + + + m_decimator2s.myDecimateSup( + (buf[pos+48] - Shift) << decimation_shifts::pre16, + (buf[pos+49] - Shift) << decimation_shifts::pre16, + (buf[pos+50] - Shift) << decimation_shifts::pre16, + (buf[pos+51] - Shift) << decimation_shifts::pre16, + (buf[pos+52] - Shift) << decimation_shifts::pre16, + (buf[pos+53] - Shift) << decimation_shifts::pre16, + (buf[pos+54] - Shift) << decimation_shifts::pre16, + (buf[pos+55] - Shift) << decimation_shifts::pre16, + &buf2[24]); + + + m_decimator2s.myDecimateSup( + (buf[pos+56] - Shift) << decimation_shifts::pre16, + (buf[pos+57] - Shift) << decimation_shifts::pre16, + (buf[pos+58] - Shift) << decimation_shifts::pre16, + (buf[pos+59] - Shift) << decimation_shifts::pre16, + (buf[pos+60] - Shift) << decimation_shifts::pre16, + (buf[pos+61] - Shift) << decimation_shifts::pre16, + (buf[pos+62] - Shift) << decimation_shifts::pre16, + (buf[pos+63] - Shift) << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[IQOrder ? 0 : 1] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder ? 1 : 0] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[IQOrder ? 2 : 3] >> decimation_shifts::post16); + (**it).setImag(buf16[IQOrder ? 3 : 2] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void DecimatorsU::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2s.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre32, + (buf[pos+1] - Shift) << decimation_shifts::pre32, + (buf[pos+2] - Shift) << decimation_shifts::pre32, + (buf[pos+3] - Shift) << decimation_shifts::pre32, + (buf[pos+4] - Shift) << decimation_shifts::pre32, + (buf[pos+5] - Shift) << decimation_shifts::pre32, + (buf[pos+6] - Shift) << decimation_shifts::pre32, + (buf[pos+7] - Shift) << decimation_shifts::pre32, + &buf2[0]); + + + m_decimator2s.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre32, + (buf[pos+9] - Shift) << decimation_shifts::pre32, + (buf[pos+10] - Shift) << decimation_shifts::pre32, + (buf[pos+11] - Shift) << decimation_shifts::pre32, + (buf[pos+12] - Shift) << decimation_shifts::pre32, + (buf[pos+13] - Shift) << decimation_shifts::pre32, + (buf[pos+14] - Shift) << decimation_shifts::pre32, + (buf[pos+15] - Shift) << decimation_shifts::pre32, + &buf2[4]); + + + m_decimator2s.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre32, + (buf[pos+17] - Shift) << decimation_shifts::pre32, + (buf[pos+18] - Shift) << decimation_shifts::pre32, + (buf[pos+19] - Shift) << decimation_shifts::pre32, + (buf[pos+20] - Shift) << decimation_shifts::pre32, + (buf[pos+21] - Shift) << decimation_shifts::pre32, + (buf[pos+22] - Shift) << decimation_shifts::pre32, + (buf[pos+23] - Shift) << decimation_shifts::pre32, + &buf2[8]); + + + m_decimator2s.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre32, + (buf[pos+25] - Shift) << decimation_shifts::pre32, + (buf[pos+26] - Shift) << decimation_shifts::pre32, + (buf[pos+27] - Shift) << decimation_shifts::pre32, + (buf[pos+28] - Shift) << decimation_shifts::pre32, + (buf[pos+29] - Shift) << decimation_shifts::pre32, + (buf[pos+30] - Shift) << decimation_shifts::pre32, + (buf[pos+31] - Shift) << decimation_shifts::pre32, + &buf2[12]); + + + m_decimator2s.myDecimateInf( + (buf[pos+32] - Shift) << decimation_shifts::pre32, + (buf[pos+33] - Shift) << decimation_shifts::pre32, + (buf[pos+34] - Shift) << decimation_shifts::pre32, + (buf[pos+35] - Shift) << decimation_shifts::pre32, + (buf[pos+36] - Shift) << decimation_shifts::pre32, + (buf[pos+37] - Shift) << decimation_shifts::pre32, + (buf[pos+38] - Shift) << decimation_shifts::pre32, + (buf[pos+39] - Shift) << decimation_shifts::pre32, + &buf2[16]); + + + m_decimator2s.myDecimateInf( + (buf[pos+40] - Shift) << decimation_shifts::pre32, + (buf[pos+41] - Shift) << decimation_shifts::pre32, + (buf[pos+42] - Shift) << decimation_shifts::pre32, + (buf[pos+43] - Shift) << decimation_shifts::pre32, + (buf[pos+44] - Shift) << decimation_shifts::pre32, + (buf[pos+45] - Shift) << decimation_shifts::pre32, + (buf[pos+46] - Shift) << decimation_shifts::pre32, + (buf[pos+47] - Shift) << decimation_shifts::pre32, + &buf2[20]); + + + m_decimator2s.myDecimateInf( + (buf[pos+48] - Shift) << decimation_shifts::pre32, + (buf[pos+49] - Shift) << decimation_shifts::pre32, + (buf[pos+50] - Shift) << decimation_shifts::pre32, + (buf[pos+51] - Shift) << decimation_shifts::pre32, + (buf[pos+52] - Shift) << decimation_shifts::pre32, + (buf[pos+53] - Shift) << decimation_shifts::pre32, + (buf[pos+54] - Shift) << decimation_shifts::pre32, + (buf[pos+55] - Shift) << decimation_shifts::pre32, + &buf2[24]); + + + m_decimator2s.myDecimateInf( + (buf[pos+56] - Shift) << decimation_shifts::pre32, + (buf[pos+57] - Shift) << decimation_shifts::pre32, + (buf[pos+58] - Shift) << decimation_shifts::pre32, + (buf[pos+59] - Shift) << decimation_shifts::pre32, + (buf[pos+60] - Shift) << decimation_shifts::pre32, + (buf[pos+61] - Shift) << decimation_shifts::pre32, + (buf[pos+62] - Shift) << decimation_shifts::pre32, + (buf[pos+63] - Shift) << decimation_shifts::pre32, + &buf2[28]); + + + m_decimator2s.myDecimateInf( + (buf[pos+64] - Shift) << decimation_shifts::pre32, + (buf[pos+65] - Shift) << decimation_shifts::pre32, + (buf[pos+66] - Shift) << decimation_shifts::pre32, + (buf[pos+67] - Shift) << decimation_shifts::pre32, + (buf[pos+68] - Shift) << decimation_shifts::pre32, + (buf[pos+69] - Shift) << decimation_shifts::pre32, + (buf[pos+70] - Shift) << decimation_shifts::pre32, + (buf[pos+71] - Shift) << decimation_shifts::pre32, + &buf2[32]); + + + m_decimator2s.myDecimateInf( + (buf[pos+72] - Shift) << decimation_shifts::pre32, + (buf[pos+73] - Shift) << decimation_shifts::pre32, + (buf[pos+74] - Shift) << decimation_shifts::pre32, + (buf[pos+75] - Shift) << decimation_shifts::pre32, + (buf[pos+76] - Shift) << decimation_shifts::pre32, + (buf[pos+77] - Shift) << decimation_shifts::pre32, + (buf[pos+78] - Shift) << decimation_shifts::pre32, + (buf[pos+79] - Shift) << decimation_shifts::pre32, + &buf2[36]); + + + m_decimator2s.myDecimateInf( + (buf[pos+80] - Shift) << decimation_shifts::pre32, + (buf[pos+81] - Shift) << decimation_shifts::pre32, + (buf[pos+82] - Shift) << decimation_shifts::pre32, + (buf[pos+83] - Shift) << decimation_shifts::pre32, + (buf[pos+84] - Shift) << decimation_shifts::pre32, + (buf[pos+85] - Shift) << decimation_shifts::pre32, + (buf[pos+86] - Shift) << decimation_shifts::pre32, + (buf[pos+87] - Shift) << decimation_shifts::pre32, + &buf2[40]); + + + m_decimator2s.myDecimateInf( + (buf[pos+88] - Shift) << decimation_shifts::pre32, + (buf[pos+89] - Shift) << decimation_shifts::pre32, + (buf[pos+90] - Shift) << decimation_shifts::pre32, + (buf[pos+91] - Shift) << decimation_shifts::pre32, + (buf[pos+92] - Shift) << decimation_shifts::pre32, + (buf[pos+93] - Shift) << decimation_shifts::pre32, + (buf[pos+94] - Shift) << decimation_shifts::pre32, + (buf[pos+95] - Shift) << decimation_shifts::pre32, + &buf2[44]); + + + m_decimator2s.myDecimateInf( + (buf[pos+96] - Shift) << decimation_shifts::pre32, + (buf[pos+97] - Shift) << decimation_shifts::pre32, + (buf[pos+98] - Shift) << decimation_shifts::pre32, + (buf[pos+99] - Shift) << decimation_shifts::pre32, + (buf[pos+100] - Shift) << decimation_shifts::pre32, + (buf[pos+101] - Shift) << decimation_shifts::pre32, + (buf[pos+102] - Shift) << decimation_shifts::pre32, + (buf[pos+103] - Shift) << decimation_shifts::pre32, + &buf2[48]); + + + m_decimator2s.myDecimateInf( + (buf[pos+104] - Shift) << decimation_shifts::pre32, + (buf[pos+105] - Shift) << decimation_shifts::pre32, + (buf[pos+106] - Shift) << decimation_shifts::pre32, + (buf[pos+107] - Shift) << decimation_shifts::pre32, + (buf[pos+108] - Shift) << decimation_shifts::pre32, + (buf[pos+109] - Shift) << decimation_shifts::pre32, + (buf[pos+110] - Shift) << decimation_shifts::pre32, + (buf[pos+111] - Shift) << decimation_shifts::pre32, + &buf2[52]); + + + m_decimator2s.myDecimateInf( + (buf[pos+112] - Shift) << decimation_shifts::pre32, + (buf[pos+113] - Shift) << decimation_shifts::pre32, + (buf[pos+114] - Shift) << decimation_shifts::pre32, + (buf[pos+115] - Shift) << decimation_shifts::pre32, + (buf[pos+116] - Shift) << decimation_shifts::pre32, + (buf[pos+117] - Shift) << decimation_shifts::pre32, + (buf[pos+118] - Shift) << decimation_shifts::pre32, + (buf[pos+119] - Shift) << decimation_shifts::pre32, + &buf2[56]); + + + m_decimator2s.myDecimateInf( + (buf[pos+120] - Shift) << decimation_shifts::pre32, + (buf[pos+121] - Shift) << decimation_shifts::pre32, + (buf[pos+122] - Shift) << decimation_shifts::pre32, + (buf[pos+123] - Shift) << decimation_shifts::pre32, + (buf[pos+124] - Shift) << decimation_shifts::pre32, + (buf[pos+125] - Shift) << decimation_shifts::pre32, + (buf[pos+126] - Shift) << decimation_shifts::pre32, + (buf[pos+127] - Shift) << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[IQOrder ? 0 : 1] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder ? 1 : 0] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[IQOrder ? 2 : 3] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder ? 3 : 2] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void DecimatorsU::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2s.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre32, + (buf[pos+1] - Shift) << decimation_shifts::pre32, + (buf[pos+2] - Shift) << decimation_shifts::pre32, + (buf[pos+3] - Shift) << decimation_shifts::pre32, + (buf[pos+4] - Shift) << decimation_shifts::pre32, + (buf[pos+5] - Shift) << decimation_shifts::pre32, + (buf[pos+6] - Shift) << decimation_shifts::pre32, + (buf[pos+7] - Shift) << decimation_shifts::pre32, + &buf2[0]); + + + m_decimator2s.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre32, + (buf[pos+9] - Shift) << decimation_shifts::pre32, + (buf[pos+10] - Shift) << decimation_shifts::pre32, + (buf[pos+11] - Shift) << decimation_shifts::pre32, + (buf[pos+12] - Shift) << decimation_shifts::pre32, + (buf[pos+13] - Shift) << decimation_shifts::pre32, + (buf[pos+14] - Shift) << decimation_shifts::pre32, + (buf[pos+15] - Shift) << decimation_shifts::pre32, + &buf2[4]); + + + m_decimator2s.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre32, + (buf[pos+17] - Shift) << decimation_shifts::pre32, + (buf[pos+18] - Shift) << decimation_shifts::pre32, + (buf[pos+19] - Shift) << decimation_shifts::pre32, + (buf[pos+20] - Shift) << decimation_shifts::pre32, + (buf[pos+21] - Shift) << decimation_shifts::pre32, + (buf[pos+22] - Shift) << decimation_shifts::pre32, + (buf[pos+23] - Shift) << decimation_shifts::pre32, + &buf2[8]); + + + m_decimator2s.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre32, + (buf[pos+25] - Shift) << decimation_shifts::pre32, + (buf[pos+26] - Shift) << decimation_shifts::pre32, + (buf[pos+27] - Shift) << decimation_shifts::pre32, + (buf[pos+28] - Shift) << decimation_shifts::pre32, + (buf[pos+29] - Shift) << decimation_shifts::pre32, + (buf[pos+30] - Shift) << decimation_shifts::pre32, + (buf[pos+31] - Shift) << decimation_shifts::pre32, + &buf2[12]); + + + m_decimator2s.myDecimateSup( + (buf[pos+32] - Shift) << decimation_shifts::pre32, + (buf[pos+33] - Shift) << decimation_shifts::pre32, + (buf[pos+34] - Shift) << decimation_shifts::pre32, + (buf[pos+35] - Shift) << decimation_shifts::pre32, + (buf[pos+36] - Shift) << decimation_shifts::pre32, + (buf[pos+37] - Shift) << decimation_shifts::pre32, + (buf[pos+38] - Shift) << decimation_shifts::pre32, + (buf[pos+39] - Shift) << decimation_shifts::pre32, + &buf2[16]); + + + m_decimator2s.myDecimateSup( + (buf[pos+40] - Shift) << decimation_shifts::pre32, + (buf[pos+41] - Shift) << decimation_shifts::pre32, + (buf[pos+42] - Shift) << decimation_shifts::pre32, + (buf[pos+43] - Shift) << decimation_shifts::pre32, + (buf[pos+44] - Shift) << decimation_shifts::pre32, + (buf[pos+45] - Shift) << decimation_shifts::pre32, + (buf[pos+46] - Shift) << decimation_shifts::pre32, + (buf[pos+47] - Shift) << decimation_shifts::pre32, + &buf2[20]); + + + m_decimator2s.myDecimateSup( + (buf[pos+48] - Shift) << decimation_shifts::pre32, + (buf[pos+49] - Shift) << decimation_shifts::pre32, + (buf[pos+50] - Shift) << decimation_shifts::pre32, + (buf[pos+51] - Shift) << decimation_shifts::pre32, + (buf[pos+52] - Shift) << decimation_shifts::pre32, + (buf[pos+53] - Shift) << decimation_shifts::pre32, + (buf[pos+54] - Shift) << decimation_shifts::pre32, + (buf[pos+55] - Shift) << decimation_shifts::pre32, + &buf2[24]); + + + m_decimator2s.myDecimateSup( + (buf[pos+56] - Shift) << decimation_shifts::pre32, + (buf[pos+57] - Shift) << decimation_shifts::pre32, + (buf[pos+58] - Shift) << decimation_shifts::pre32, + (buf[pos+59] - Shift) << decimation_shifts::pre32, + (buf[pos+60] - Shift) << decimation_shifts::pre32, + (buf[pos+61] - Shift) << decimation_shifts::pre32, + (buf[pos+62] - Shift) << decimation_shifts::pre32, + (buf[pos+63] - Shift) << decimation_shifts::pre32, + &buf2[28]); + + + m_decimator2s.myDecimateSup( + (buf[pos+64] - Shift) << decimation_shifts::pre32, + (buf[pos+65] - Shift) << decimation_shifts::pre32, + (buf[pos+66] - Shift) << decimation_shifts::pre32, + (buf[pos+67] - Shift) << decimation_shifts::pre32, + (buf[pos+68] - Shift) << decimation_shifts::pre32, + (buf[pos+69] - Shift) << decimation_shifts::pre32, + (buf[pos+70] - Shift) << decimation_shifts::pre32, + (buf[pos+71] - Shift) << decimation_shifts::pre32, + &buf2[32]); + + + m_decimator2s.myDecimateSup( + (buf[pos+72] - Shift) << decimation_shifts::pre32, + (buf[pos+73] - Shift) << decimation_shifts::pre32, + (buf[pos+74] - Shift) << decimation_shifts::pre32, + (buf[pos+75] - Shift) << decimation_shifts::pre32, + (buf[pos+76] - Shift) << decimation_shifts::pre32, + (buf[pos+77] - Shift) << decimation_shifts::pre32, + (buf[pos+78] - Shift) << decimation_shifts::pre32, + (buf[pos+79] - Shift) << decimation_shifts::pre32, + &buf2[36]); + + + m_decimator2s.myDecimateSup( + (buf[pos+80] - Shift) << decimation_shifts::pre32, + (buf[pos+81] - Shift) << decimation_shifts::pre32, + (buf[pos+82] - Shift) << decimation_shifts::pre32, + (buf[pos+83] - Shift) << decimation_shifts::pre32, + (buf[pos+84] - Shift) << decimation_shifts::pre32, + (buf[pos+85] - Shift) << decimation_shifts::pre32, + (buf[pos+86] - Shift) << decimation_shifts::pre32, + (buf[pos+87] - Shift) << decimation_shifts::pre32, + &buf2[40]); + + + m_decimator2s.myDecimateSup( + (buf[pos+88] - Shift) << decimation_shifts::pre32, + (buf[pos+89] - Shift) << decimation_shifts::pre32, + (buf[pos+90] - Shift) << decimation_shifts::pre32, + (buf[pos+91] - Shift) << decimation_shifts::pre32, + (buf[pos+92] - Shift) << decimation_shifts::pre32, + (buf[pos+93] - Shift) << decimation_shifts::pre32, + (buf[pos+94] - Shift) << decimation_shifts::pre32, + (buf[pos+95] - Shift) << decimation_shifts::pre32, + &buf2[44]); + + + m_decimator2s.myDecimateSup( + (buf[pos+96] - Shift) << decimation_shifts::pre32, + (buf[pos+97] - Shift) << decimation_shifts::pre32, + (buf[pos+98] - Shift) << decimation_shifts::pre32, + (buf[pos+99] - Shift) << decimation_shifts::pre32, + (buf[pos+100] - Shift) << decimation_shifts::pre32, + (buf[pos+101] - Shift) << decimation_shifts::pre32, + (buf[pos+102] - Shift) << decimation_shifts::pre32, + (buf[pos+103] - Shift) << decimation_shifts::pre32, + &buf2[48]); + + + m_decimator2s.myDecimateSup( + (buf[pos+104] - Shift) << decimation_shifts::pre32, + (buf[pos+105] - Shift) << decimation_shifts::pre32, + (buf[pos+106] - Shift) << decimation_shifts::pre32, + (buf[pos+107] - Shift) << decimation_shifts::pre32, + (buf[pos+108] - Shift) << decimation_shifts::pre32, + (buf[pos+109] - Shift) << decimation_shifts::pre32, + (buf[pos+110] - Shift) << decimation_shifts::pre32, + (buf[pos+111] - Shift) << decimation_shifts::pre32, + &buf2[52]); + + + m_decimator2s.myDecimateSup( + (buf[pos+112] - Shift) << decimation_shifts::pre32, + (buf[pos+113] - Shift) << decimation_shifts::pre32, + (buf[pos+114] - Shift) << decimation_shifts::pre32, + (buf[pos+115] - Shift) << decimation_shifts::pre32, + (buf[pos+116] - Shift) << decimation_shifts::pre32, + (buf[pos+117] - Shift) << decimation_shifts::pre32, + (buf[pos+118] - Shift) << decimation_shifts::pre32, + (buf[pos+119] - Shift) << decimation_shifts::pre32, + &buf2[56]); + + + m_decimator2s.myDecimateSup( + (buf[pos+120] - Shift) << decimation_shifts::pre32, + (buf[pos+121] - Shift) << decimation_shifts::pre32, + (buf[pos+122] - Shift) << decimation_shifts::pre32, + (buf[pos+123] - Shift) << decimation_shifts::pre32, + (buf[pos+124] - Shift) << decimation_shifts::pre32, + (buf[pos+125] - Shift) << decimation_shifts::pre32, + (buf[pos+126] - Shift) << decimation_shifts::pre32, + (buf[pos+127] - Shift) << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[IQOrder ? 0 : 1] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder ? 1 : 0] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[IQOrder ? 2 : 3] >> decimation_shifts::post32); + (**it).setImag(buf32[IQOrder ? 3 : 2] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void DecimatorsU::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; + + for (int pos = 0; pos < len - 255; pos += 256) + { + m_decimator2s.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre64, + (buf[pos+1] - Shift) << decimation_shifts::pre64, + (buf[pos+2] - Shift) << decimation_shifts::pre64, + (buf[pos+3] - Shift) << decimation_shifts::pre64, + (buf[pos+4] - Shift) << decimation_shifts::pre64, + (buf[pos+5] - Shift) << decimation_shifts::pre64, + (buf[pos+6] - Shift) << decimation_shifts::pre64, + (buf[pos+7] - Shift) << decimation_shifts::pre64, + &buf2[0]); + + + m_decimator2s.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre64, + (buf[pos+9] - Shift) << decimation_shifts::pre64, + (buf[pos+10] - Shift) << decimation_shifts::pre64, + (buf[pos+11] - Shift) << decimation_shifts::pre64, + (buf[pos+12] - Shift) << decimation_shifts::pre64, + (buf[pos+13] - Shift) << decimation_shifts::pre64, + (buf[pos+14] - Shift) << decimation_shifts::pre64, + (buf[pos+15] - Shift) << decimation_shifts::pre64, + &buf2[4]); + + + m_decimator2s.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre64, + (buf[pos+17] - Shift) << decimation_shifts::pre64, + (buf[pos+18] - Shift) << decimation_shifts::pre64, + (buf[pos+19] - Shift) << decimation_shifts::pre64, + (buf[pos+20] - Shift) << decimation_shifts::pre64, + (buf[pos+21] - Shift) << decimation_shifts::pre64, + (buf[pos+22] - Shift) << decimation_shifts::pre64, + (buf[pos+23] - Shift) << decimation_shifts::pre64, + &buf2[8]); + + + m_decimator2s.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre64, + (buf[pos+25] - Shift) << decimation_shifts::pre64, + (buf[pos+26] - Shift) << decimation_shifts::pre64, + (buf[pos+27] - Shift) << decimation_shifts::pre64, + (buf[pos+28] - Shift) << decimation_shifts::pre64, + (buf[pos+29] - Shift) << decimation_shifts::pre64, + (buf[pos+30] - Shift) << decimation_shifts::pre64, + (buf[pos+31] - Shift) << decimation_shifts::pre64, + &buf2[12]); + + + m_decimator2s.myDecimateInf( + (buf[pos+32] - Shift) << decimation_shifts::pre64, + (buf[pos+33] - Shift) << decimation_shifts::pre64, + (buf[pos+34] - Shift) << decimation_shifts::pre64, + (buf[pos+35] - Shift) << decimation_shifts::pre64, + (buf[pos+36] - Shift) << decimation_shifts::pre64, + (buf[pos+37] - Shift) << decimation_shifts::pre64, + (buf[pos+38] - Shift) << decimation_shifts::pre64, + (buf[pos+39] - Shift) << decimation_shifts::pre64, + &buf2[16]); + + + m_decimator2s.myDecimateInf( + (buf[pos+40] - Shift) << decimation_shifts::pre64, + (buf[pos+41] - Shift) << decimation_shifts::pre64, + (buf[pos+42] - Shift) << decimation_shifts::pre64, + (buf[pos+43] - Shift) << decimation_shifts::pre64, + (buf[pos+44] - Shift) << decimation_shifts::pre64, + (buf[pos+45] - Shift) << decimation_shifts::pre64, + (buf[pos+46] - Shift) << decimation_shifts::pre64, + (buf[pos+47] - Shift) << decimation_shifts::pre64, + &buf2[20]); + + + m_decimator2s.myDecimateInf( + (buf[pos+48] - Shift) << decimation_shifts::pre64, + (buf[pos+49] - Shift) << decimation_shifts::pre64, + (buf[pos+50] - Shift) << decimation_shifts::pre64, + (buf[pos+51] - Shift) << decimation_shifts::pre64, + (buf[pos+52] - Shift) << decimation_shifts::pre64, + (buf[pos+53] - Shift) << decimation_shifts::pre64, + (buf[pos+54] - Shift) << decimation_shifts::pre64, + (buf[pos+55] - Shift) << decimation_shifts::pre64, + &buf2[24]); + + + m_decimator2s.myDecimateInf( + (buf[pos+56] - Shift) << decimation_shifts::pre64, + (buf[pos+57] - Shift) << decimation_shifts::pre64, + (buf[pos+58] - Shift) << decimation_shifts::pre64, + (buf[pos+59] - Shift) << decimation_shifts::pre64, + (buf[pos+60] - Shift) << decimation_shifts::pre64, + (buf[pos+61] - Shift) << decimation_shifts::pre64, + (buf[pos+62] - Shift) << decimation_shifts::pre64, + (buf[pos+63] - Shift) << decimation_shifts::pre64, + &buf2[28]); + + + m_decimator2s.myDecimateInf( + (buf[pos+64] - Shift) << decimation_shifts::pre64, + (buf[pos+65] - Shift) << decimation_shifts::pre64, + (buf[pos+66] - Shift) << decimation_shifts::pre64, + (buf[pos+67] - Shift) << decimation_shifts::pre64, + (buf[pos+68] - Shift) << decimation_shifts::pre64, + (buf[pos+69] - Shift) << decimation_shifts::pre64, + (buf[pos+70] - Shift) << decimation_shifts::pre64, + (buf[pos+71] - Shift) << decimation_shifts::pre64, + &buf2[32]); + + + m_decimator2s.myDecimateInf( + (buf[pos+72] - Shift) << decimation_shifts::pre64, + (buf[pos+73] - Shift) << decimation_shifts::pre64, + (buf[pos+74] - Shift) << decimation_shifts::pre64, + (buf[pos+75] - Shift) << decimation_shifts::pre64, + (buf[pos+76] - Shift) << decimation_shifts::pre64, + (buf[pos+77] - Shift) << decimation_shifts::pre64, + (buf[pos+78] - Shift) << decimation_shifts::pre64, + (buf[pos+79] - Shift) << decimation_shifts::pre64, + &buf2[36]); + + + m_decimator2s.myDecimateInf( + (buf[pos+80] - Shift) << decimation_shifts::pre64, + (buf[pos+81] - Shift) << decimation_shifts::pre64, + (buf[pos+82] - Shift) << decimation_shifts::pre64, + (buf[pos+83] - Shift) << decimation_shifts::pre64, + (buf[pos+84] - Shift) << decimation_shifts::pre64, + (buf[pos+85] - Shift) << decimation_shifts::pre64, + (buf[pos+86] - Shift) << decimation_shifts::pre64, + (buf[pos+87] - Shift) << decimation_shifts::pre64, + &buf2[40]); + + + m_decimator2s.myDecimateInf( + (buf[pos+88] - Shift) << decimation_shifts::pre64, + (buf[pos+89] - Shift) << decimation_shifts::pre64, + (buf[pos+90] - Shift) << decimation_shifts::pre64, + (buf[pos+91] - Shift) << decimation_shifts::pre64, + (buf[pos+92] - Shift) << decimation_shifts::pre64, + (buf[pos+93] - Shift) << decimation_shifts::pre64, + (buf[pos+94] - Shift) << decimation_shifts::pre64, + (buf[pos+95] - Shift) << decimation_shifts::pre64, + &buf2[44]); + + + m_decimator2s.myDecimateInf( + (buf[pos+96] - Shift) << decimation_shifts::pre64, + (buf[pos+97] - Shift) << decimation_shifts::pre64, + (buf[pos+98] - Shift) << decimation_shifts::pre64, + (buf[pos+99] - Shift) << decimation_shifts::pre64, + (buf[pos+100] - Shift) << decimation_shifts::pre64, + (buf[pos+101] - Shift) << decimation_shifts::pre64, + (buf[pos+102] - Shift) << decimation_shifts::pre64, + (buf[pos+103] - Shift) << decimation_shifts::pre64, + &buf2[48]); + + + m_decimator2s.myDecimateInf( + (buf[pos+104] - Shift) << decimation_shifts::pre64, + (buf[pos+105] - Shift) << decimation_shifts::pre64, + (buf[pos+106] - Shift) << decimation_shifts::pre64, + (buf[pos+107] - Shift) << decimation_shifts::pre64, + (buf[pos+108] - Shift) << decimation_shifts::pre64, + (buf[pos+109] - Shift) << decimation_shifts::pre64, + (buf[pos+110] - Shift) << decimation_shifts::pre64, + (buf[pos+111] - Shift) << decimation_shifts::pre64, + &buf2[52]); + + + m_decimator2s.myDecimateInf( + (buf[pos+112] - Shift) << decimation_shifts::pre64, + (buf[pos+113] - Shift) << decimation_shifts::pre64, + (buf[pos+114] - Shift) << decimation_shifts::pre64, + (buf[pos+115] - Shift) << decimation_shifts::pre64, + (buf[pos+116] - Shift) << decimation_shifts::pre64, + (buf[pos+117] - Shift) << decimation_shifts::pre64, + (buf[pos+118] - Shift) << decimation_shifts::pre64, + (buf[pos+119] - Shift) << decimation_shifts::pre64, + &buf2[56]); + + + m_decimator2s.myDecimateInf( + (buf[pos+120] - Shift) << decimation_shifts::pre64, + (buf[pos+121] - Shift) << decimation_shifts::pre64, + (buf[pos+122] - Shift) << decimation_shifts::pre64, + (buf[pos+123] - Shift) << decimation_shifts::pre64, + (buf[pos+124] - Shift) << decimation_shifts::pre64, + (buf[pos+125] - Shift) << decimation_shifts::pre64, + (buf[pos+126] - Shift) << decimation_shifts::pre64, + (buf[pos+127] - Shift) << decimation_shifts::pre64, + &buf2[60]); + + + m_decimator2s.myDecimateInf( + (buf[pos+128] - Shift) << decimation_shifts::pre64, + (buf[pos+129] - Shift) << decimation_shifts::pre64, + (buf[pos+130] - Shift) << decimation_shifts::pre64, + (buf[pos+131] - Shift) << decimation_shifts::pre64, + (buf[pos+132] - Shift) << decimation_shifts::pre64, + (buf[pos+133] - Shift) << decimation_shifts::pre64, + (buf[pos+134] - Shift) << decimation_shifts::pre64, + (buf[pos+135] - Shift) << decimation_shifts::pre64, + &buf2[64]); + + + m_decimator2s.myDecimateInf( + (buf[pos+136] - Shift) << decimation_shifts::pre64, + (buf[pos+137] - Shift) << decimation_shifts::pre64, + (buf[pos+138] - Shift) << decimation_shifts::pre64, + (buf[pos+139] - Shift) << decimation_shifts::pre64, + (buf[pos+140] - Shift) << decimation_shifts::pre64, + (buf[pos+141] - Shift) << decimation_shifts::pre64, + (buf[pos+142] - Shift) << decimation_shifts::pre64, + (buf[pos+143] - Shift) << decimation_shifts::pre64, + &buf2[68]); + + + m_decimator2s.myDecimateInf( + (buf[pos+144] - Shift) << decimation_shifts::pre64, + (buf[pos+145] - Shift) << decimation_shifts::pre64, + (buf[pos+146] - Shift) << decimation_shifts::pre64, + (buf[pos+147] - Shift) << decimation_shifts::pre64, + (buf[pos+148] - Shift) << decimation_shifts::pre64, + (buf[pos+149] - Shift) << decimation_shifts::pre64, + (buf[pos+150] - Shift) << decimation_shifts::pre64, + (buf[pos+151] - Shift) << decimation_shifts::pre64, + &buf2[72]); + + + m_decimator2s.myDecimateInf( + (buf[pos+152] - Shift) << decimation_shifts::pre64, + (buf[pos+153] - Shift) << decimation_shifts::pre64, + (buf[pos+154] - Shift) << decimation_shifts::pre64, + (buf[pos+155] - Shift) << decimation_shifts::pre64, + (buf[pos+156] - Shift) << decimation_shifts::pre64, + (buf[pos+157] - Shift) << decimation_shifts::pre64, + (buf[pos+158] - Shift) << decimation_shifts::pre64, + (buf[pos+159] - Shift) << decimation_shifts::pre64, + &buf2[76]); + + + m_decimator2s.myDecimateInf( + (buf[pos+160] - Shift) << decimation_shifts::pre64, + (buf[pos+161] - Shift) << decimation_shifts::pre64, + (buf[pos+162] - Shift) << decimation_shifts::pre64, + (buf[pos+163] - Shift) << decimation_shifts::pre64, + (buf[pos+164] - Shift) << decimation_shifts::pre64, + (buf[pos+165] - Shift) << decimation_shifts::pre64, + (buf[pos+166] - Shift) << decimation_shifts::pre64, + (buf[pos+167] - Shift) << decimation_shifts::pre64, + &buf2[80]); + + + m_decimator2s.myDecimateInf( + (buf[pos+168] - Shift) << decimation_shifts::pre64, + (buf[pos+169] - Shift) << decimation_shifts::pre64, + (buf[pos+170] - Shift) << decimation_shifts::pre64, + (buf[pos+171] - Shift) << decimation_shifts::pre64, + (buf[pos+172] - Shift) << decimation_shifts::pre64, + (buf[pos+173] - Shift) << decimation_shifts::pre64, + (buf[pos+174] - Shift) << decimation_shifts::pre64, + (buf[pos+175] - Shift) << decimation_shifts::pre64, + &buf2[84]); + + + m_decimator2s.myDecimateInf( + (buf[pos+176] - Shift) << decimation_shifts::pre64, + (buf[pos+177] - Shift) << decimation_shifts::pre64, + (buf[pos+178] - Shift) << decimation_shifts::pre64, + (buf[pos+179] - Shift) << decimation_shifts::pre64, + (buf[pos+180] - Shift) << decimation_shifts::pre64, + (buf[pos+181] - Shift) << decimation_shifts::pre64, + (buf[pos+182] - Shift) << decimation_shifts::pre64, + (buf[pos+183] - Shift) << decimation_shifts::pre64, + &buf2[88]); + + + m_decimator2s.myDecimateInf( + (buf[pos+184] - Shift) << decimation_shifts::pre64, + (buf[pos+185] - Shift) << decimation_shifts::pre64, + (buf[pos+186] - Shift) << decimation_shifts::pre64, + (buf[pos+187] - Shift) << decimation_shifts::pre64, + (buf[pos+188] - Shift) << decimation_shifts::pre64, + (buf[pos+189] - Shift) << decimation_shifts::pre64, + (buf[pos+190] - Shift) << decimation_shifts::pre64, + (buf[pos+191] - Shift) << decimation_shifts::pre64, + &buf2[92]); + + + m_decimator2s.myDecimateInf( + (buf[pos+192] - Shift) << decimation_shifts::pre64, + (buf[pos+193] - Shift) << decimation_shifts::pre64, + (buf[pos+194] - Shift) << decimation_shifts::pre64, + (buf[pos+195] - Shift) << decimation_shifts::pre64, + (buf[pos+196] - Shift) << decimation_shifts::pre64, + (buf[pos+197] - Shift) << decimation_shifts::pre64, + (buf[pos+198] - Shift) << decimation_shifts::pre64, + (buf[pos+199] - Shift) << decimation_shifts::pre64, + &buf2[96]); + + + m_decimator2s.myDecimateInf( + (buf[pos+200] - Shift) << decimation_shifts::pre64, + (buf[pos+201] - Shift) << decimation_shifts::pre64, + (buf[pos+202] - Shift) << decimation_shifts::pre64, + (buf[pos+203] - Shift) << decimation_shifts::pre64, + (buf[pos+204] - Shift) << decimation_shifts::pre64, + (buf[pos+205] - Shift) << decimation_shifts::pre64, + (buf[pos+206] - Shift) << decimation_shifts::pre64, + (buf[pos+207] - Shift) << decimation_shifts::pre64, + &buf2[100]); + + + m_decimator2s.myDecimateInf( + (buf[pos+208] - Shift) << decimation_shifts::pre64, + (buf[pos+209] - Shift) << decimation_shifts::pre64, + (buf[pos+210] - Shift) << decimation_shifts::pre64, + (buf[pos+211] - Shift) << decimation_shifts::pre64, + (buf[pos+212] - Shift) << decimation_shifts::pre64, + (buf[pos+213] - Shift) << decimation_shifts::pre64, + (buf[pos+214] - Shift) << decimation_shifts::pre64, + (buf[pos+215] - Shift) << decimation_shifts::pre64, + &buf2[104]); + + + m_decimator2s.myDecimateInf( + (buf[pos+216] - Shift) << decimation_shifts::pre64, + (buf[pos+217] - Shift) << decimation_shifts::pre64, + (buf[pos+218] - Shift) << decimation_shifts::pre64, + (buf[pos+219] - Shift) << decimation_shifts::pre64, + (buf[pos+220] - Shift) << decimation_shifts::pre64, + (buf[pos+221] - Shift) << decimation_shifts::pre64, + (buf[pos+222] - Shift) << decimation_shifts::pre64, + (buf[pos+223] - Shift) << decimation_shifts::pre64, + &buf2[108]); + + + m_decimator2s.myDecimateInf( + (buf[pos+224] - Shift) << decimation_shifts::pre64, + (buf[pos+225] - Shift) << decimation_shifts::pre64, + (buf[pos+226] - Shift) << decimation_shifts::pre64, + (buf[pos+227] - Shift) << decimation_shifts::pre64, + (buf[pos+228] - Shift) << decimation_shifts::pre64, + (buf[pos+229] - Shift) << decimation_shifts::pre64, + (buf[pos+230] - Shift) << decimation_shifts::pre64, + (buf[pos+231] - Shift) << decimation_shifts::pre64, + &buf2[112]); + + + m_decimator2s.myDecimateInf( + (buf[pos+232] - Shift) << decimation_shifts::pre64, + (buf[pos+233] - Shift) << decimation_shifts::pre64, + (buf[pos+234] - Shift) << decimation_shifts::pre64, + (buf[pos+235] - Shift) << decimation_shifts::pre64, + (buf[pos+236] - Shift) << decimation_shifts::pre64, + (buf[pos+237] - Shift) << decimation_shifts::pre64, + (buf[pos+238] - Shift) << decimation_shifts::pre64, + (buf[pos+239] - Shift) << decimation_shifts::pre64, + &buf2[116]); + + + m_decimator2s.myDecimateInf( + (buf[pos+240] - Shift) << decimation_shifts::pre64, + (buf[pos+241] - Shift) << decimation_shifts::pre64, + (buf[pos+242] - Shift) << decimation_shifts::pre64, + (buf[pos+243] - Shift) << decimation_shifts::pre64, + (buf[pos+244] - Shift) << decimation_shifts::pre64, + (buf[pos+245] - Shift) << decimation_shifts::pre64, + (buf[pos+246] - Shift) << decimation_shifts::pre64, + (buf[pos+247] - Shift) << decimation_shifts::pre64, + &buf2[120]); + + + m_decimator2s.myDecimateInf( + (buf[pos+248] - Shift) << decimation_shifts::pre64, + (buf[pos+249] - Shift) << decimation_shifts::pre64, + (buf[pos+250] - Shift) << decimation_shifts::pre64, + (buf[pos+251] - Shift) << decimation_shifts::pre64, + (buf[pos+252] - Shift) << decimation_shifts::pre64, + (buf[pos+253] - Shift) << decimation_shifts::pre64, + (buf[pos+254] - Shift) << decimation_shifts::pre64, + (buf[pos+255] - Shift) << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateSup( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateSup( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateSup( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateSup( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateSup( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateSup( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateSup( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateSup( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateSup( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateSup( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateSup( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateSup( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateSup( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateSup( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateSup( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateSup( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[IQOrder ? 0 : 1] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder ? 1 : 0] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[IQOrder ? 2 : 3] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder ? 3 : 2] >> decimation_shifts::post64); + ++(*it); + } +} + +template +void DecimatorsU::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; + + for (int pos = 0; pos < len - 255; pos += 256) + { + m_decimator2s.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre64, + (buf[pos+1] - Shift) << decimation_shifts::pre64, + (buf[pos+2] - Shift) << decimation_shifts::pre64, + (buf[pos+3] - Shift) << decimation_shifts::pre64, + (buf[pos+4] - Shift) << decimation_shifts::pre64, + (buf[pos+5] - Shift) << decimation_shifts::pre64, + (buf[pos+6] - Shift) << decimation_shifts::pre64, + (buf[pos+7] - Shift) << decimation_shifts::pre64, + &buf2[0]); + + + m_decimator2s.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre64, + (buf[pos+9] - Shift) << decimation_shifts::pre64, + (buf[pos+10] - Shift) << decimation_shifts::pre64, + (buf[pos+11] - Shift) << decimation_shifts::pre64, + (buf[pos+12] - Shift) << decimation_shifts::pre64, + (buf[pos+13] - Shift) << decimation_shifts::pre64, + (buf[pos+14] - Shift) << decimation_shifts::pre64, + (buf[pos+15] - Shift) << decimation_shifts::pre64, + &buf2[4]); + + + m_decimator2s.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre64, + (buf[pos+17] - Shift) << decimation_shifts::pre64, + (buf[pos+18] - Shift) << decimation_shifts::pre64, + (buf[pos+19] - Shift) << decimation_shifts::pre64, + (buf[pos+20] - Shift) << decimation_shifts::pre64, + (buf[pos+21] - Shift) << decimation_shifts::pre64, + (buf[pos+22] - Shift) << decimation_shifts::pre64, + (buf[pos+23] - Shift) << decimation_shifts::pre64, + &buf2[8]); + + + m_decimator2s.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre64, + (buf[pos+25] - Shift) << decimation_shifts::pre64, + (buf[pos+26] - Shift) << decimation_shifts::pre64, + (buf[pos+27] - Shift) << decimation_shifts::pre64, + (buf[pos+28] - Shift) << decimation_shifts::pre64, + (buf[pos+29] - Shift) << decimation_shifts::pre64, + (buf[pos+30] - Shift) << decimation_shifts::pre64, + (buf[pos+31] - Shift) << decimation_shifts::pre64, + &buf2[12]); + + + m_decimator2s.myDecimateSup( + (buf[pos+32] - Shift) << decimation_shifts::pre64, + (buf[pos+33] - Shift) << decimation_shifts::pre64, + (buf[pos+34] - Shift) << decimation_shifts::pre64, + (buf[pos+35] - Shift) << decimation_shifts::pre64, + (buf[pos+36] - Shift) << decimation_shifts::pre64, + (buf[pos+37] - Shift) << decimation_shifts::pre64, + (buf[pos+38] - Shift) << decimation_shifts::pre64, + (buf[pos+39] - Shift) << decimation_shifts::pre64, + &buf2[16]); + + + m_decimator2s.myDecimateSup( + (buf[pos+40] - Shift) << decimation_shifts::pre64, + (buf[pos+41] - Shift) << decimation_shifts::pre64, + (buf[pos+42] - Shift) << decimation_shifts::pre64, + (buf[pos+43] - Shift) << decimation_shifts::pre64, + (buf[pos+44] - Shift) << decimation_shifts::pre64, + (buf[pos+45] - Shift) << decimation_shifts::pre64, + (buf[pos+46] - Shift) << decimation_shifts::pre64, + (buf[pos+47] - Shift) << decimation_shifts::pre64, + &buf2[20]); + + + m_decimator2s.myDecimateSup( + (buf[pos+48] - Shift) << decimation_shifts::pre64, + (buf[pos+49] - Shift) << decimation_shifts::pre64, + (buf[pos+50] - Shift) << decimation_shifts::pre64, + (buf[pos+51] - Shift) << decimation_shifts::pre64, + (buf[pos+52] - Shift) << decimation_shifts::pre64, + (buf[pos+53] - Shift) << decimation_shifts::pre64, + (buf[pos+54] - Shift) << decimation_shifts::pre64, + (buf[pos+55] - Shift) << decimation_shifts::pre64, + &buf2[24]); + + + m_decimator2s.myDecimateSup( + (buf[pos+56] - Shift) << decimation_shifts::pre64, + (buf[pos+57] - Shift) << decimation_shifts::pre64, + (buf[pos+58] - Shift) << decimation_shifts::pre64, + (buf[pos+59] - Shift) << decimation_shifts::pre64, + (buf[pos+60] - Shift) << decimation_shifts::pre64, + (buf[pos+61] - Shift) << decimation_shifts::pre64, + (buf[pos+62] - Shift) << decimation_shifts::pre64, + (buf[pos+63] - Shift) << decimation_shifts::pre64, + &buf2[28]); + + + m_decimator2s.myDecimateSup( + (buf[pos+64] - Shift) << decimation_shifts::pre64, + (buf[pos+65] - Shift) << decimation_shifts::pre64, + (buf[pos+66] - Shift) << decimation_shifts::pre64, + (buf[pos+67] - Shift) << decimation_shifts::pre64, + (buf[pos+68] - Shift) << decimation_shifts::pre64, + (buf[pos+69] - Shift) << decimation_shifts::pre64, + (buf[pos+70] - Shift) << decimation_shifts::pre64, + (buf[pos+71] - Shift) << decimation_shifts::pre64, + &buf2[32]); + + + m_decimator2s.myDecimateSup( + (buf[pos+72] - Shift) << decimation_shifts::pre64, + (buf[pos+73] - Shift) << decimation_shifts::pre64, + (buf[pos+74] - Shift) << decimation_shifts::pre64, + (buf[pos+75] - Shift) << decimation_shifts::pre64, + (buf[pos+76] - Shift) << decimation_shifts::pre64, + (buf[pos+77] - Shift) << decimation_shifts::pre64, + (buf[pos+78] - Shift) << decimation_shifts::pre64, + (buf[pos+79] - Shift) << decimation_shifts::pre64, + &buf2[36]); + + + m_decimator2s.myDecimateSup( + (buf[pos+80] - Shift) << decimation_shifts::pre64, + (buf[pos+81] - Shift) << decimation_shifts::pre64, + (buf[pos+82] - Shift) << decimation_shifts::pre64, + (buf[pos+83] - Shift) << decimation_shifts::pre64, + (buf[pos+84] - Shift) << decimation_shifts::pre64, + (buf[pos+85] - Shift) << decimation_shifts::pre64, + (buf[pos+86] - Shift) << decimation_shifts::pre64, + (buf[pos+87] - Shift) << decimation_shifts::pre64, + &buf2[40]); + + + m_decimator2s.myDecimateSup( + (buf[pos+88] - Shift) << decimation_shifts::pre64, + (buf[pos+89] - Shift) << decimation_shifts::pre64, + (buf[pos+90] - Shift) << decimation_shifts::pre64, + (buf[pos+91] - Shift) << decimation_shifts::pre64, + (buf[pos+92] - Shift) << decimation_shifts::pre64, + (buf[pos+93] - Shift) << decimation_shifts::pre64, + (buf[pos+94] - Shift) << decimation_shifts::pre64, + (buf[pos+95] - Shift) << decimation_shifts::pre64, + &buf2[44]); + + + m_decimator2s.myDecimateSup( + (buf[pos+96] - Shift) << decimation_shifts::pre64, + (buf[pos+97] - Shift) << decimation_shifts::pre64, + (buf[pos+98] - Shift) << decimation_shifts::pre64, + (buf[pos+99] - Shift) << decimation_shifts::pre64, + (buf[pos+100] - Shift) << decimation_shifts::pre64, + (buf[pos+101] - Shift) << decimation_shifts::pre64, + (buf[pos+102] - Shift) << decimation_shifts::pre64, + (buf[pos+103] - Shift) << decimation_shifts::pre64, + &buf2[48]); + + + m_decimator2s.myDecimateSup( + (buf[pos+104] - Shift) << decimation_shifts::pre64, + (buf[pos+105] - Shift) << decimation_shifts::pre64, + (buf[pos+106] - Shift) << decimation_shifts::pre64, + (buf[pos+107] - Shift) << decimation_shifts::pre64, + (buf[pos+108] - Shift) << decimation_shifts::pre64, + (buf[pos+109] - Shift) << decimation_shifts::pre64, + (buf[pos+110] - Shift) << decimation_shifts::pre64, + (buf[pos+111] - Shift) << decimation_shifts::pre64, + &buf2[52]); + + + m_decimator2s.myDecimateSup( + (buf[pos+112] - Shift) << decimation_shifts::pre64, + (buf[pos+113] - Shift) << decimation_shifts::pre64, + (buf[pos+114] - Shift) << decimation_shifts::pre64, + (buf[pos+115] - Shift) << decimation_shifts::pre64, + (buf[pos+116] - Shift) << decimation_shifts::pre64, + (buf[pos+117] - Shift) << decimation_shifts::pre64, + (buf[pos+118] - Shift) << decimation_shifts::pre64, + (buf[pos+119] - Shift) << decimation_shifts::pre64, + &buf2[56]); + + + m_decimator2s.myDecimateSup( + (buf[pos+120] - Shift) << decimation_shifts::pre64, + (buf[pos+121] - Shift) << decimation_shifts::pre64, + (buf[pos+122] - Shift) << decimation_shifts::pre64, + (buf[pos+123] - Shift) << decimation_shifts::pre64, + (buf[pos+124] - Shift) << decimation_shifts::pre64, + (buf[pos+125] - Shift) << decimation_shifts::pre64, + (buf[pos+126] - Shift) << decimation_shifts::pre64, + (buf[pos+127] - Shift) << decimation_shifts::pre64, + &buf2[60]); + + + m_decimator2s.myDecimateSup( + (buf[pos+128] - Shift) << decimation_shifts::pre64, + (buf[pos+129] - Shift) << decimation_shifts::pre64, + (buf[pos+130] - Shift) << decimation_shifts::pre64, + (buf[pos+131] - Shift) << decimation_shifts::pre64, + (buf[pos+132] - Shift) << decimation_shifts::pre64, + (buf[pos+133] - Shift) << decimation_shifts::pre64, + (buf[pos+134] - Shift) << decimation_shifts::pre64, + (buf[pos+135] - Shift) << decimation_shifts::pre64, + &buf2[64]); + + + m_decimator2s.myDecimateSup( + (buf[pos+136] - Shift) << decimation_shifts::pre64, + (buf[pos+137] - Shift) << decimation_shifts::pre64, + (buf[pos+138] - Shift) << decimation_shifts::pre64, + (buf[pos+139] - Shift) << decimation_shifts::pre64, + (buf[pos+140] - Shift) << decimation_shifts::pre64, + (buf[pos+141] - Shift) << decimation_shifts::pre64, + (buf[pos+142] - Shift) << decimation_shifts::pre64, + (buf[pos+143] - Shift) << decimation_shifts::pre64, + &buf2[68]); + + + m_decimator2s.myDecimateSup( + (buf[pos+144] - Shift) << decimation_shifts::pre64, + (buf[pos+145] - Shift) << decimation_shifts::pre64, + (buf[pos+146] - Shift) << decimation_shifts::pre64, + (buf[pos+147] - Shift) << decimation_shifts::pre64, + (buf[pos+148] - Shift) << decimation_shifts::pre64, + (buf[pos+149] - Shift) << decimation_shifts::pre64, + (buf[pos+150] - Shift) << decimation_shifts::pre64, + (buf[pos+151] - Shift) << decimation_shifts::pre64, + &buf2[72]); + + + m_decimator2s.myDecimateSup( + (buf[pos+152] - Shift) << decimation_shifts::pre64, + (buf[pos+153] - Shift) << decimation_shifts::pre64, + (buf[pos+154] - Shift) << decimation_shifts::pre64, + (buf[pos+155] - Shift) << decimation_shifts::pre64, + (buf[pos+156] - Shift) << decimation_shifts::pre64, + (buf[pos+157] - Shift) << decimation_shifts::pre64, + (buf[pos+158] - Shift) << decimation_shifts::pre64, + (buf[pos+159] - Shift) << decimation_shifts::pre64, + &buf2[76]); + + + m_decimator2s.myDecimateSup( + (buf[pos+160] - Shift) << decimation_shifts::pre64, + (buf[pos+161] - Shift) << decimation_shifts::pre64, + (buf[pos+162] - Shift) << decimation_shifts::pre64, + (buf[pos+163] - Shift) << decimation_shifts::pre64, + (buf[pos+164] - Shift) << decimation_shifts::pre64, + (buf[pos+165] - Shift) << decimation_shifts::pre64, + (buf[pos+166] - Shift) << decimation_shifts::pre64, + (buf[pos+167] - Shift) << decimation_shifts::pre64, + &buf2[80]); + + + m_decimator2s.myDecimateSup( + (buf[pos+168] - Shift) << decimation_shifts::pre64, + (buf[pos+169] - Shift) << decimation_shifts::pre64, + (buf[pos+170] - Shift) << decimation_shifts::pre64, + (buf[pos+171] - Shift) << decimation_shifts::pre64, + (buf[pos+172] - Shift) << decimation_shifts::pre64, + (buf[pos+173] - Shift) << decimation_shifts::pre64, + (buf[pos+174] - Shift) << decimation_shifts::pre64, + (buf[pos+175] - Shift) << decimation_shifts::pre64, + &buf2[84]); + + + m_decimator2s.myDecimateSup( + (buf[pos+176] - Shift) << decimation_shifts::pre64, + (buf[pos+177] - Shift) << decimation_shifts::pre64, + (buf[pos+178] - Shift) << decimation_shifts::pre64, + (buf[pos+179] - Shift) << decimation_shifts::pre64, + (buf[pos+180] - Shift) << decimation_shifts::pre64, + (buf[pos+181] - Shift) << decimation_shifts::pre64, + (buf[pos+182] - Shift) << decimation_shifts::pre64, + (buf[pos+183] - Shift) << decimation_shifts::pre64, + &buf2[88]); + + + m_decimator2s.myDecimateSup( + (buf[pos+184] - Shift) << decimation_shifts::pre64, + (buf[pos+185] - Shift) << decimation_shifts::pre64, + (buf[pos+186] - Shift) << decimation_shifts::pre64, + (buf[pos+187] - Shift) << decimation_shifts::pre64, + (buf[pos+188] - Shift) << decimation_shifts::pre64, + (buf[pos+189] - Shift) << decimation_shifts::pre64, + (buf[pos+190] - Shift) << decimation_shifts::pre64, + (buf[pos+191] - Shift) << decimation_shifts::pre64, + &buf2[92]); + + + m_decimator2s.myDecimateSup( + (buf[pos+192] - Shift) << decimation_shifts::pre64, + (buf[pos+193] - Shift) << decimation_shifts::pre64, + (buf[pos+194] - Shift) << decimation_shifts::pre64, + (buf[pos+195] - Shift) << decimation_shifts::pre64, + (buf[pos+196] - Shift) << decimation_shifts::pre64, + (buf[pos+197] - Shift) << decimation_shifts::pre64, + (buf[pos+198] - Shift) << decimation_shifts::pre64, + (buf[pos+199] - Shift) << decimation_shifts::pre64, + &buf2[96]); + + + m_decimator2s.myDecimateSup( + (buf[pos+200] - Shift) << decimation_shifts::pre64, + (buf[pos+201] - Shift) << decimation_shifts::pre64, + (buf[pos+202] - Shift) << decimation_shifts::pre64, + (buf[pos+203] - Shift) << decimation_shifts::pre64, + (buf[pos+204] - Shift) << decimation_shifts::pre64, + (buf[pos+205] - Shift) << decimation_shifts::pre64, + (buf[pos+206] - Shift) << decimation_shifts::pre64, + (buf[pos+207] - Shift) << decimation_shifts::pre64, + &buf2[100]); + + + m_decimator2s.myDecimateSup( + (buf[pos+208] - Shift) << decimation_shifts::pre64, + (buf[pos+209] - Shift) << decimation_shifts::pre64, + (buf[pos+210] - Shift) << decimation_shifts::pre64, + (buf[pos+211] - Shift) << decimation_shifts::pre64, + (buf[pos+212] - Shift) << decimation_shifts::pre64, + (buf[pos+213] - Shift) << decimation_shifts::pre64, + (buf[pos+214] - Shift) << decimation_shifts::pre64, + (buf[pos+215] - Shift) << decimation_shifts::pre64, + &buf2[104]); + + + m_decimator2s.myDecimateSup( + (buf[pos+216] - Shift) << decimation_shifts::pre64, + (buf[pos+217] - Shift) << decimation_shifts::pre64, + (buf[pos+218] - Shift) << decimation_shifts::pre64, + (buf[pos+219] - Shift) << decimation_shifts::pre64, + (buf[pos+220] - Shift) << decimation_shifts::pre64, + (buf[pos+221] - Shift) << decimation_shifts::pre64, + (buf[pos+222] - Shift) << decimation_shifts::pre64, + (buf[pos+223] - Shift) << decimation_shifts::pre64, + &buf2[108]); + + + m_decimator2s.myDecimateSup( + (buf[pos+224] - Shift) << decimation_shifts::pre64, + (buf[pos+225] - Shift) << decimation_shifts::pre64, + (buf[pos+226] - Shift) << decimation_shifts::pre64, + (buf[pos+227] - Shift) << decimation_shifts::pre64, + (buf[pos+228] - Shift) << decimation_shifts::pre64, + (buf[pos+229] - Shift) << decimation_shifts::pre64, + (buf[pos+230] - Shift) << decimation_shifts::pre64, + (buf[pos+231] - Shift) << decimation_shifts::pre64, + &buf2[112]); + + + m_decimator2s.myDecimateSup( + (buf[pos+232] - Shift) << decimation_shifts::pre64, + (buf[pos+233] - Shift) << decimation_shifts::pre64, + (buf[pos+234] - Shift) << decimation_shifts::pre64, + (buf[pos+235] - Shift) << decimation_shifts::pre64, + (buf[pos+236] - Shift) << decimation_shifts::pre64, + (buf[pos+237] - Shift) << decimation_shifts::pre64, + (buf[pos+238] - Shift) << decimation_shifts::pre64, + (buf[pos+239] - Shift) << decimation_shifts::pre64, + &buf2[116]); + + + m_decimator2s.myDecimateSup( + (buf[pos+240] - Shift) << decimation_shifts::pre64, + (buf[pos+241] - Shift) << decimation_shifts::pre64, + (buf[pos+242] - Shift) << decimation_shifts::pre64, + (buf[pos+243] - Shift) << decimation_shifts::pre64, + (buf[pos+244] - Shift) << decimation_shifts::pre64, + (buf[pos+245] - Shift) << decimation_shifts::pre64, + (buf[pos+246] - Shift) << decimation_shifts::pre64, + (buf[pos+247] - Shift) << decimation_shifts::pre64, + &buf2[120]); + + + m_decimator2s.myDecimateSup( + (buf[pos+248] - Shift) << decimation_shifts::pre64, + (buf[pos+249] - Shift) << decimation_shifts::pre64, + (buf[pos+250] - Shift) << decimation_shifts::pre64, + (buf[pos+251] - Shift) << decimation_shifts::pre64, + (buf[pos+252] - Shift) << decimation_shifts::pre64, + (buf[pos+253] - Shift) << decimation_shifts::pre64, + (buf[pos+254] - Shift) << decimation_shifts::pre64, + (buf[pos+255] - Shift) << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateInf( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateInf( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateInf( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateInf( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateInf( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateInf( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateInf( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateInf( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateInf( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateInf( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateInf( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateInf( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateInf( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateInf( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateInf( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateInf( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[IQOrder ? 0 : 1] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder ? 1 : 0] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[IQOrder ? 2 : 3] >> decimation_shifts::post64); + (**it).setImag(buf64[IQOrder ? 3 : 2] >> decimation_shifts::post64); + ++(*it); + } +} + +template +void DecimatorsU::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[4]; + + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateCen( + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } +} + +template +void DecimatorsU::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType buf2[8], buf4[4]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateCen( + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, + &buf2[0]); + + m_decimator2.myDecimateCen( + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, + &buf2[4]); + + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } +} + +template +void DecimatorsU::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[8]; + + for (int pos = 0; pos < len - 15; pos += 16) + { + intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre8; + intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre8; + intbuf[2] = (buf[pos+6] - Shift) << decimation_shifts::pre8; + intbuf[3] = (buf[pos+7] - Shift) << decimation_shifts::pre8; + intbuf[4] = (buf[pos+10] - Shift) << decimation_shifts::pre8; + intbuf[5] = (buf[pos+11] - Shift) << decimation_shifts::pre8; + intbuf[6] = (buf[pos+14] - Shift) << decimation_shifts::pre8; + intbuf[7] = (buf[pos+15] - Shift) << decimation_shifts::pre8; + + m_decimator2.myDecimate( + (buf[pos+0] - Shift) << decimation_shifts::pre8, + (buf[pos+1] - Shift) << decimation_shifts::pre8, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + (buf[pos+4] - Shift) << decimation_shifts::pre8, + (buf[pos+5] - Shift) << decimation_shifts::pre8, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + (buf[pos+8] - Shift) << decimation_shifts::pre8, + (buf[pos+9] - Shift) << decimation_shifts::pre8, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + (buf[pos+12] - Shift) << decimation_shifts::pre8, + (buf[pos+13] - Shift) << decimation_shifts::pre8, + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6] >> decimation_shifts::post8); + (**it).setImag(intbuf[7] >> decimation_shifts::post8); + ++(*it); + } +} + +template +void DecimatorsU::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[16]; + + for (int pos = 0; pos < len - 31; pos += 32) + { + intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre16; + intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre16; + intbuf[2] = (buf[pos+6] - Shift) << decimation_shifts::pre16; + intbuf[3] = (buf[pos+7] - Shift) << decimation_shifts::pre16; + intbuf[4] = (buf[pos+10] - Shift) << decimation_shifts::pre16; + intbuf[5] = (buf[pos+11] - Shift) << decimation_shifts::pre16; + intbuf[6] = (buf[pos+14] - Shift) << decimation_shifts::pre16; + intbuf[7] = (buf[pos+15] - Shift) << decimation_shifts::pre16; + intbuf[8] = (buf[pos+18] - Shift) << decimation_shifts::pre16; + intbuf[9] = (buf[pos+19] - Shift) << decimation_shifts::pre16; + intbuf[10] = (buf[pos+22] - Shift) << decimation_shifts::pre16; + intbuf[11] = (buf[pos+23] - Shift) << decimation_shifts::pre16; + intbuf[12] = (buf[pos+26] - Shift) << decimation_shifts::pre16; + intbuf[13] = (buf[pos+27] - Shift) << decimation_shifts::pre16; + intbuf[14] = (buf[pos+30] - Shift) << decimation_shifts::pre16; + intbuf[15] = (buf[pos+31] - Shift) << decimation_shifts::pre16; + + m_decimator2.myDecimate( + (buf[pos+0] - Shift) << decimation_shifts::pre16, + (buf[pos+1] - Shift) << decimation_shifts::pre16, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + (buf[pos+4] - Shift) << decimation_shifts::pre16, + (buf[pos+5] - Shift) << decimation_shifts::pre16, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + (buf[pos+8] - Shift) << decimation_shifts::pre16, + (buf[pos+9] - Shift) << decimation_shifts::pre16, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + (buf[pos+12] - Shift) << decimation_shifts::pre16, + (buf[pos+13] - Shift) << decimation_shifts::pre16, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + (buf[pos+16] - Shift) << decimation_shifts::pre16, + (buf[pos+17] - Shift) << decimation_shifts::pre16, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + (buf[pos+20] - Shift) << decimation_shifts::pre16, + (buf[pos+21] - Shift) << decimation_shifts::pre16, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + (buf[pos+24] - Shift) << decimation_shifts::pre16, + (buf[pos+25] - Shift) << decimation_shifts::pre16, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + (buf[pos+28] - Shift) << decimation_shifts::pre16, + (buf[pos+29] - Shift) << decimation_shifts::pre16, + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14] >> decimation_shifts::post16); + (**it).setImag(intbuf[15] >> decimation_shifts::post16); + ++(*it); + } +} + +template +void DecimatorsU::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[32]; + + for (int pos = 0; pos < len - 63; pos += 64) + { + intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre32; + intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre32; + intbuf[2] = (buf[pos+6] - Shift) << decimation_shifts::pre32; + intbuf[3] = (buf[pos+7] - Shift) << decimation_shifts::pre32; + intbuf[4] = (buf[pos+10] - Shift) << decimation_shifts::pre32; + intbuf[5] = (buf[pos+11] - Shift) << decimation_shifts::pre32; + intbuf[6] = (buf[pos+14] - Shift) << decimation_shifts::pre32; + intbuf[7] = (buf[pos+15] - Shift) << decimation_shifts::pre32; + intbuf[8] = (buf[pos+18] - Shift) << decimation_shifts::pre32; + intbuf[9] = (buf[pos+19] - Shift) << decimation_shifts::pre32; + intbuf[10] = (buf[pos+22] - Shift) << decimation_shifts::pre32; + intbuf[11] = (buf[pos+23] - Shift) << decimation_shifts::pre32; + intbuf[12] = (buf[pos+26] - Shift) << decimation_shifts::pre32; + intbuf[13] = (buf[pos+27] - Shift) << decimation_shifts::pre32; + intbuf[14] = (buf[pos+30] - Shift) << decimation_shifts::pre32; + intbuf[15] = (buf[pos+31] - Shift) << decimation_shifts::pre32; + intbuf[16] = (buf[pos+34] - Shift) << decimation_shifts::pre32; + intbuf[17] = (buf[pos+35] - Shift) << decimation_shifts::pre32; + intbuf[18] = (buf[pos+38] - Shift) << decimation_shifts::pre32; + intbuf[19] = (buf[pos+39] - Shift) << decimation_shifts::pre32; + intbuf[20] = (buf[pos+42] - Shift) << decimation_shifts::pre32; + intbuf[21] = (buf[pos+43] - Shift) << decimation_shifts::pre32; + intbuf[22] = (buf[pos+46] - Shift) << decimation_shifts::pre32; + intbuf[23] = (buf[pos+47] - Shift) << decimation_shifts::pre32; + intbuf[24] = (buf[pos+50] - Shift) << decimation_shifts::pre32; + intbuf[25] = (buf[pos+51] - Shift) << decimation_shifts::pre32; + intbuf[26] = (buf[pos+54] - Shift) << decimation_shifts::pre32; + intbuf[27] = (buf[pos+55] - Shift) << decimation_shifts::pre32; + intbuf[28] = (buf[pos+58] - Shift) << decimation_shifts::pre32; + intbuf[29] = (buf[pos+59] - Shift) << decimation_shifts::pre32; + intbuf[30] = (buf[pos+62] - Shift) << decimation_shifts::pre32; + intbuf[31] = (buf[pos+63] - Shift) << decimation_shifts::pre32; + + m_decimator2.myDecimate( + (buf[pos+0] - Shift) << decimation_shifts::pre32, + (buf[pos+1] - Shift) << decimation_shifts::pre32, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + (buf[pos+4] - Shift) << decimation_shifts::pre32, + (buf[pos+5] - Shift) << decimation_shifts::pre32, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + (buf[pos+8] - Shift) << decimation_shifts::pre32, + (buf[pos+9] - Shift) << decimation_shifts::pre32, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + (buf[pos+12] - Shift) << decimation_shifts::pre32, + (buf[pos+13] - Shift) << decimation_shifts::pre32, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + (buf[pos+16] - Shift) << decimation_shifts::pre32, + (buf[pos+17] - Shift) << decimation_shifts::pre32, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + (buf[pos+20] - Shift) << decimation_shifts::pre32, + (buf[pos+21] - Shift) << decimation_shifts::pre32, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + (buf[pos+24] - Shift) << decimation_shifts::pre32, + (buf[pos+25] - Shift) << decimation_shifts::pre32, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + (buf[pos+28] - Shift) << decimation_shifts::pre32, + (buf[pos+29] - Shift) << decimation_shifts::pre32, + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + (buf[pos+32] - Shift) << decimation_shifts::pre32, + (buf[pos+33] - Shift) << decimation_shifts::pre32, + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + (buf[pos+36] - Shift) << decimation_shifts::pre32, + (buf[pos+37] - Shift) << decimation_shifts::pre32, + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + (buf[pos+40] - Shift) << decimation_shifts::pre32, + (buf[pos+41] - Shift) << decimation_shifts::pre32, + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + (buf[pos+44] - Shift) << decimation_shifts::pre32, + (buf[pos+45] - Shift) << decimation_shifts::pre32, + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + (buf[pos+48] - Shift) << decimation_shifts::pre32, + (buf[pos+49] - Shift) << decimation_shifts::pre32, + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + (buf[pos+52] - Shift) << decimation_shifts::pre32, + (buf[pos+53] - Shift) << decimation_shifts::pre32, + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + (buf[pos+56] - Shift) << decimation_shifts::pre32, + (buf[pos+57] - Shift) << decimation_shifts::pre32, + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + (buf[pos+60] - Shift) << decimation_shifts::pre32, + (buf[pos+61] - Shift) << decimation_shifts::pre32, + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30] >> decimation_shifts::post32); + (**it).setImag(intbuf[31] >> decimation_shifts::post32); + ++(*it); + } +} + +template +void DecimatorsU::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + StorageType intbuf[64]; + + for (int pos = 0; pos < len - 127; pos += 128) + { + intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre64; + intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre64; + intbuf[2] = (buf[pos+6] - Shift) << decimation_shifts::pre64; + intbuf[3] = (buf[pos+7] - Shift) << decimation_shifts::pre64; + intbuf[4] = (buf[pos+10] - Shift) << decimation_shifts::pre64; + intbuf[5] = (buf[pos+11] - Shift) << decimation_shifts::pre64; + intbuf[6] = (buf[pos+14] - Shift) << decimation_shifts::pre64; + intbuf[7] = (buf[pos+15] - Shift) << decimation_shifts::pre64; + intbuf[8] = (buf[pos+18] - Shift) << decimation_shifts::pre64; + intbuf[9] = (buf[pos+19] - Shift) << decimation_shifts::pre64; + intbuf[10] = (buf[pos+22] - Shift) << decimation_shifts::pre64; + intbuf[11] = (buf[pos+23] - Shift) << decimation_shifts::pre64; + intbuf[12] = (buf[pos+26] - Shift) << decimation_shifts::pre64; + intbuf[13] = (buf[pos+27] - Shift) << decimation_shifts::pre64; + intbuf[14] = (buf[pos+30] - Shift) << decimation_shifts::pre64; + intbuf[15] = (buf[pos+31] - Shift) << decimation_shifts::pre64; + intbuf[16] = (buf[pos+34] - Shift) << decimation_shifts::pre64; + intbuf[17] = (buf[pos+35] - Shift) << decimation_shifts::pre64; + intbuf[18] = (buf[pos+38] - Shift) << decimation_shifts::pre64; + intbuf[19] = (buf[pos+39] - Shift) << decimation_shifts::pre64; + intbuf[20] = (buf[pos+42] - Shift) << decimation_shifts::pre64; + intbuf[21] = (buf[pos+43] - Shift) << decimation_shifts::pre64; + intbuf[22] = (buf[pos+46] - Shift) << decimation_shifts::pre64; + intbuf[23] = (buf[pos+47] - Shift) << decimation_shifts::pre64; + intbuf[24] = (buf[pos+50] - Shift) << decimation_shifts::pre64; + intbuf[25] = (buf[pos+51] - Shift) << decimation_shifts::pre64; + intbuf[26] = (buf[pos+54] - Shift) << decimation_shifts::pre64; + intbuf[27] = (buf[pos+55] - Shift) << decimation_shifts::pre64; + intbuf[28] = (buf[pos+58] - Shift) << decimation_shifts::pre64; + intbuf[29] = (buf[pos+59] - Shift) << decimation_shifts::pre64; + intbuf[30] = (buf[pos+62] - Shift) << decimation_shifts::pre64; + intbuf[31] = (buf[pos+63] - Shift) << decimation_shifts::pre64; + + intbuf[32] = (buf[pos+66] - Shift) << decimation_shifts::pre64; + intbuf[33] = (buf[pos+67] - Shift) << decimation_shifts::pre64; + intbuf[34] = (buf[pos+70] - Shift) << decimation_shifts::pre64; + intbuf[35] = (buf[pos+71] - Shift) << decimation_shifts::pre64; + intbuf[36] = (buf[pos+74] - Shift) << decimation_shifts::pre64; + intbuf[37] = (buf[pos+75] - Shift) << decimation_shifts::pre64; + intbuf[38] = (buf[pos+78] - Shift) << decimation_shifts::pre64; + intbuf[39] = (buf[pos+79] - Shift) << decimation_shifts::pre64; + intbuf[40] = (buf[pos+82] - Shift) << decimation_shifts::pre64; + intbuf[41] = (buf[pos+83] - Shift) << decimation_shifts::pre64; + intbuf[42] = (buf[pos+86] - Shift) << decimation_shifts::pre64; + intbuf[43] = (buf[pos+87] - Shift) << decimation_shifts::pre64; + intbuf[44] = (buf[pos+90] - Shift) << decimation_shifts::pre64; + intbuf[45] = (buf[pos+91] - Shift) << decimation_shifts::pre64; + intbuf[46] = (buf[pos+94] - Shift) << decimation_shifts::pre64; + intbuf[47] = (buf[pos+95] - Shift) << decimation_shifts::pre64; + intbuf[48] = (buf[pos+98] - Shift) << decimation_shifts::pre64; + intbuf[49] = (buf[pos+99] - Shift) << decimation_shifts::pre64; + intbuf[50] = (buf[pos+102] - Shift) << decimation_shifts::pre64; + intbuf[51] = (buf[pos+103] - Shift) << decimation_shifts::pre64; + intbuf[52] = (buf[pos+106] - Shift) << decimation_shifts::pre64; + intbuf[53] = (buf[pos+107] - Shift) << decimation_shifts::pre64; + intbuf[54] = (buf[pos+110] - Shift) << decimation_shifts::pre64; + intbuf[55] = (buf[pos+111] - Shift) << decimation_shifts::pre64; + intbuf[56] = (buf[pos+114] - Shift) << decimation_shifts::pre64; + intbuf[57] = (buf[pos+115] - Shift) << decimation_shifts::pre64; + intbuf[58] = (buf[pos+118] - Shift) << decimation_shifts::pre64; + intbuf[59] = (buf[pos+119] - Shift) << decimation_shifts::pre64; + intbuf[60] = (buf[pos+122] - Shift) << decimation_shifts::pre64; + intbuf[61] = (buf[pos+123] - Shift) << decimation_shifts::pre64; + intbuf[62] = (buf[pos+126] - Shift) << decimation_shifts::pre64; + intbuf[63] = (buf[pos+127] - Shift) << decimation_shifts::pre64; + + m_decimator2.myDecimate( + (buf[pos+0] - Shift) << decimation_shifts::pre64, + (buf[pos+1] - Shift) << decimation_shifts::pre64, + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + (buf[pos+4] - Shift) << decimation_shifts::pre64, + (buf[pos+5] - Shift) << decimation_shifts::pre64, + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + (buf[pos+8] - Shift) << decimation_shifts::pre64, + (buf[pos+9] - Shift) << decimation_shifts::pre64, + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + (buf[pos+12] - Shift) << decimation_shifts::pre64, + (buf[pos+13] - Shift) << decimation_shifts::pre64, + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + (buf[pos+16] - Shift) << decimation_shifts::pre64, + (buf[pos+17] - Shift) << decimation_shifts::pre64, + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + (buf[pos+20] - Shift) << decimation_shifts::pre64, + (buf[pos+21] - Shift) << decimation_shifts::pre64, + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + (buf[pos+24] - Shift) << decimation_shifts::pre64, + (buf[pos+25] - Shift) << decimation_shifts::pre64, + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + (buf[pos+28] - Shift) << decimation_shifts::pre64, + (buf[pos+29] - Shift) << decimation_shifts::pre64, + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + (buf[pos+32] - Shift) << decimation_shifts::pre64, + (buf[pos+33] - Shift) << decimation_shifts::pre64, + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + (buf[pos+36] - Shift) << decimation_shifts::pre64, + (buf[pos+37] - Shift) << decimation_shifts::pre64, + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + (buf[pos+40] - Shift) << decimation_shifts::pre64, + (buf[pos+41] - Shift) << decimation_shifts::pre64, + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + (buf[pos+44] - Shift) << decimation_shifts::pre64, + (buf[pos+45] - Shift) << decimation_shifts::pre64, + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + (buf[pos+48] - Shift) << decimation_shifts::pre64, + (buf[pos+49] - Shift) << decimation_shifts::pre64, + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + (buf[pos+52] - Shift) << decimation_shifts::pre64, + (buf[pos+53] - Shift) << decimation_shifts::pre64, + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + (buf[pos+56] - Shift) << decimation_shifts::pre64, + (buf[pos+57] - Shift) << decimation_shifts::pre64, + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + (buf[pos+60] - Shift) << decimation_shifts::pre64, + (buf[pos+61] - Shift) << decimation_shifts::pre64, + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + (buf[pos+64] - Shift) << decimation_shifts::pre64, + (buf[pos+65] - Shift) << decimation_shifts::pre64, + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + (buf[pos+68] - Shift) << decimation_shifts::pre64, + (buf[pos+69] - Shift) << decimation_shifts::pre64, + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + (buf[pos+72] - Shift) << decimation_shifts::pre64, + (buf[pos+73] - Shift) << decimation_shifts::pre64, + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + (buf[pos+76] - Shift) << decimation_shifts::pre64, + (buf[pos+77] - Shift) << decimation_shifts::pre64, + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + (buf[pos+80] - Shift) << decimation_shifts::pre64, + (buf[pos+81] - Shift) << decimation_shifts::pre64, + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + (buf[pos+84] - Shift) << decimation_shifts::pre64, + (buf[pos+85] - Shift) << decimation_shifts::pre64, + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + (buf[pos+88] - Shift) << decimation_shifts::pre64, + (buf[pos+89] - Shift) << decimation_shifts::pre64, + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + (buf[pos+92] - Shift) << decimation_shifts::pre64, + (buf[pos+93] - Shift) << decimation_shifts::pre64, + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + (buf[pos+96] - Shift) << decimation_shifts::pre64, + (buf[pos+97] - Shift) << decimation_shifts::pre64, + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + (buf[pos+100] - Shift) << decimation_shifts::pre64, + (buf[pos+101] - Shift) << decimation_shifts::pre64, + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + (buf[pos+104] - Shift) << decimation_shifts::pre64, + (buf[pos+105] - Shift) << decimation_shifts::pre64, + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + (buf[pos+108] - Shift) << decimation_shifts::pre64, + (buf[pos+109] - Shift) << decimation_shifts::pre64, + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + (buf[pos+112] - Shift) << decimation_shifts::pre64, + (buf[pos+113] - Shift) << decimation_shifts::pre64, + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + (buf[pos+116] - Shift) << decimation_shifts::pre64, + (buf[pos+117] - Shift) << decimation_shifts::pre64, + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + (buf[pos+120] - Shift) << decimation_shifts::pre64, + (buf[pos+121] - Shift) << decimation_shifts::pre64, + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + (buf[pos+124] - Shift) << decimation_shifts::pre64, + (buf[pos+125] - Shift) << decimation_shifts::pre64, + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62] >> decimation_shifts::post64); + (**it).setImag(intbuf[63] >> decimation_shifts::post64); + ++(*it); + } +} + +#endif /* INCLUDE_GPL_DSP_DECIMATORSU_H_ */ diff --git a/android/app/src/main/cpp/dsp/devicesamplemimo.cpp b/android/app/src/main/cpp/dsp/devicesamplemimo.cpp new file mode 100644 index 0000000..c3de816 --- /dev/null +++ b/android/app/src/main/cpp/dsp/devicesamplemimo.cpp @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "devicesamplemimo.h" + +DeviceSampleMIMO::DeviceSampleMIMO() : + m_guiMessageQueue(nullptr) +{ + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +DeviceSampleMIMO::~DeviceSampleMIMO() = default; + +void DeviceSampleMIMO::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) + { + delete message; + } + } +} diff --git a/android/app/src/main/cpp/dsp/devicesamplemimo.h b/android/app/src/main/cpp/dsp/devicesamplemimo.h new file mode 100644 index 0000000..2c3f460 --- /dev/null +++ b/android/app/src/main/cpp/dsp/devicesamplemimo.h @@ -0,0 +1,176 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DEVICESAMPLEMIMO_H_ +#define SDRBASE_DSP_DEVICESAMPLEMIMO_H_ + +#include + +#include "samplemififo.h" +#include "samplemofifo.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "export.h" + +namespace SWGSDRangel +{ + class SWGDeviceSettings; + class SWGDeviceState; + class SWGDeviceReport; + class SWGDeviceActions; +} + +class SDRBASE_API DeviceSampleMIMO : public QObject { + Q_OBJECT +public: + enum MIMOType //!< Type of MIMO + { + MIMOAsynchronous, //!< All streams are asynchronous (false MIMO) + MIMOHalfSynchronous, //!< MI + MO (synchronous inputs on one side and synchronous outputs on the other side) + MIMOFullSynchronous, //!< True MIMO (all streams synchronous) + }; + + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + DeviceSampleMIMO(); + virtual ~DeviceSampleMIMO(); + virtual void destroy() = 0; + + virtual void init() = 0; //!< initializations to be done when all collaborating objects are created and possibly connected + virtual bool startRx() = 0; + virtual void stopRx() = 0; + virtual bool startTx() = 0; + virtual void stopTx() = 0; + + virtual QByteArray serialize() const = 0; + virtual bool deserialize(const QByteArray& data) = 0; + + virtual const QString& getDeviceDescription() const = 0; + + virtual int getSinkSampleRate(int index) const = 0; //!< Sample rate exposed by the sink at index + virtual void setSinkSampleRate(int sampleRate, int index) = 0; //!< For when the sink sample rate is set externally + virtual quint64 getSinkCenterFrequency(int index) const = 0; //!< Center frequency exposed by the sink at index + virtual void setSinkCenterFrequency(qint64 centerFrequency, int index) = 0; + + virtual int getSourceSampleRate(int index) const = 0; //!< Sample rate exposed by the source at index + virtual void setSourceSampleRate(int sampleRate, int index) = 0; //!< For when the source sample rate is set externally + virtual quint64 getSourceCenterFrequency(int index) const = 0; //!< Center frequency exposed by the source at index + virtual void setSourceCenterFrequency(qint64 centerFrequency, int index) = 0; + + virtual quint64 getMIMOCenterFrequency() const = 0; //!< Unique center frequency for preset identification or any unique reference + virtual unsigned int getMIMOSampleRate() const = 0; //!< Unique sample rate for any unique reference + + virtual bool handleMessage(const Message& message) = 0; + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiSettingsPutPatch( + bool force, //!< true to force settings = put + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) + { + (void) force; + (void) deviceSettingsKeys; + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiRunGet( + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) + { + (void) response; + (void) subsystemIndex; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiRun(bool run, + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) + { + (void) run; + (void) subsystemIndex; + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiActionsPost( + const QStringList& deviceActionsKeys, + SWGSDRangel::SWGDeviceActions& actions, + QString& errorMessage) + { + (void) deviceActionsKeys; + (void) actions; + errorMessage = "Not implemented"; + return 501; + } + + MIMOType getMIMOType() const { return m_mimoType; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this + MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } + + unsigned int getNbSourceFifos() const { return m_sampleMOFifo.getNbStreams(); } //!< Get the number of Tx FIFOs + unsigned int getNbSinkFifos() const { return m_sampleMIFifo.getNbStreams(); } //!< Get the number of Rx FIFOs + SampleMIFifo* getSampleMIFifo() { return &m_sampleMIFifo; } + SampleMOFifo* getSampleMOFifo() { return &m_sampleMOFifo; } + // Streams and FIFOs are in opposed source/sink type whick makes it confusing when stream direction is involved: + // Rx: source stream -> sink FIFO -> channel sinks + // Tx: sink stream <- source FIFO <- channel sources + unsigned int getNbSourceStreams() const { return m_sampleMIFifo.getNbStreams(); } //!< Commodity function same as getNbSinkFifos (Rx or source streams) + unsigned int getNbSinkStreams() const { return m_sampleMOFifo.getNbStreams(); } //!< Commodity function same as getNbSourceFifos (Tx or sink streams) + +protected slots: + void handleInputMessages(); + +protected: + MIMOType m_mimoType; + SampleMIFifo m_sampleMIFifo; //!< Multiple Input FIFO + SampleMOFifo m_sampleMOFifo; //!< Multiple Output FIFO + MessageQueue m_inputMessageQueue; //!< Input queue to the sink + MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI +}; + +#endif // SDRBASE_DSP_DEVICESAMPLEMIMO_H_ diff --git a/android/app/src/main/cpp/dsp/devicesamplesink.cpp b/android/app/src/main/cpp/dsp/devicesamplesink.cpp new file mode 100644 index 0000000..640e447 --- /dev/null +++ b/android/app/src/main/cpp/dsp/devicesamplesink.cpp @@ -0,0 +1,113 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2017, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/devicesamplestatic.h" +#include "dsp/devicesamplesink.h" + +DeviceSampleSink::DeviceSampleSink() : + m_sampleSourceFifo(1<<19), + m_guiMessageQueue(0) +{ + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +DeviceSampleSink::~DeviceSampleSink() +{ +} + +void DeviceSampleSink::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +qint64 DeviceSampleSink::calculateDeviceCenterFrequency( + quint64 centerFrequency, + qint64 transverterDeltaFrequency, + int log2Interp, + fcPos_t fcPos, + quint32 devSampleRate, + bool transverterMode) +{ + return DeviceSampleStatic::calculateSinkDeviceCenterFrequency( + centerFrequency, + transverterDeltaFrequency, + log2Interp, + (DeviceSampleStatic::fcPos_t) fcPos, + devSampleRate, + transverterMode + ); +} + +qint64 DeviceSampleSink::calculateCenterFrequency( + quint64 deviceCenterFrequency, + qint64 transverterDeltaFrequency, + int log2Interp, + fcPos_t fcPos, + quint32 devSampleRate, + bool transverterMode) +{ + return DeviceSampleStatic::calculateSinkCenterFrequency( + deviceCenterFrequency, + transverterDeltaFrequency, + log2Interp, + (DeviceSampleStatic::fcPos_t) fcPos, + devSampleRate, + transverterMode + ); +} + +/** + * log2Interp = 0: no shift + * + * log2Interp = 1: middle of side band (inf or sup: 1/2) + * ^ | ^ + * | inf | inf | sup | sup | + * + * log2Interp = 2: middle of far side half side band (inf, inf or sup, sup: 1/2 + 1/4) + * ^ | ^ + * | inf | inf | sup | sup | + * + * log2Interp = 3: inf, inf, sup or sup, sup, inf: 1/2 + 1/4 - 1/8 = 5/8 + * log2Interp = 4: inf, inf, sup, inf or sup, sup, inf, sup: 1/2 + 1/4 - 1/8 + 1/16 = 11/16 + * log2Interp = 5: inf, inf, sup, inf, sup or sup, sup, inf, sup, inf: 1/2 + 1/4 - 1/8 + 1/16 - 1/32 = 21/32 + * log2Interp = 6: inf, sup, inf, sup, inf, sup or sup, inf, sup, inf, sup, inf: 1/2 - 1/4 + 1/8 -1/16 + 1/32 - 1/64 = 21/64 + * + */ +qint32 DeviceSampleSink::calculateFrequencyShift( + int log2Interp, + fcPos_t fcPos, + quint32 devSampleRate) +{ + return DeviceSampleStatic::calculateSinkFrequencyShift( + log2Interp, + (DeviceSampleStatic::fcPos_t) fcPos, + devSampleRate + ); +} + diff --git a/android/app/src/main/cpp/dsp/devicesamplesink.h b/android/app/src/main/cpp/dsp/devicesamplesink.h new file mode 100644 index 0000000..bfc54b1 --- /dev/null +++ b/android/app/src/main/cpp/dsp/devicesamplesink.h @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DEVICESAMPLESINK_H_ +#define SDRBASE_DSP_DEVICESAMPLESINK_H_ + +#include + +#include "samplesourcefifo.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "export.h" + +namespace SWGSDRangel +{ + class SWGDeviceSettings; + class SWGDeviceState; + class SWGDeviceReport; + class SWGDeviceActions; +} + +class SDRBASE_API DeviceSampleSink : public QObject { + Q_OBJECT +public: + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + DeviceSampleSink(); + virtual ~DeviceSampleSink(); + virtual void destroy() = 0; + + virtual void init() = 0; //!< initializations to be done when all collaborating objects are created and possibly connected + virtual bool start() = 0; + virtual void stop() = 0; + + virtual QByteArray serialize() const = 0; + virtual bool deserialize(const QByteArray& data) = 0; + + virtual const QString& getDeviceDescription() const = 0; + virtual int getSampleRate() const = 0; //!< Sample rate exposed by the sink + virtual void setSampleRate(int sampleRate) = 0; //!< For when the sink sample rate is set externally + virtual quint64 getCenterFrequency() const = 0; //!< Center frequency exposed by the sink + virtual void setCenterFrequency(qint64 centerFrequency) = 0; + + virtual bool handleMessage(const Message& message) = 0; + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiSettingsPutPatch( + bool force, //!< true to force settings = put + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) + { + (void) force; + (void) deviceSettingsKeys; + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) + { + (void) run; + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiActionsPost( + const QStringList& deviceActionsKeys, + SWGSDRangel::SWGDeviceActions& actions, + QString& errorMessage) + { + (void) deviceActionsKeys; + (void) actions; + errorMessage = "Not implemented"; + return 501; + } + + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this + MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } + SampleSourceFifo* getSampleFifo() { return &m_sampleSourceFifo; } + + static qint64 calculateDeviceCenterFrequency( + quint64 centerFrequency, + qint64 transverterDeltaFrequency, + int log2Interp, + fcPos_t fcPos, + quint32 devSampleRate, + bool transverterMode = false); + + static qint64 calculateCenterFrequency( + quint64 deviceCenterFrequency, + qint64 transverterDeltaFrequency, + int log2Interp, + fcPos_t fcPos, + quint32 devSampleRate, + bool transverterMode = false); + + static qint32 calculateFrequencyShift( + int log2Interp, + fcPos_t fcPos, + quint32 devSampleRate); + +protected slots: + void handleInputMessages(); + +protected: + SampleSourceFifo m_sampleSourceFifo; + MessageQueue m_inputMessageQueue; //!< Input queue to the sink + MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI +}; + +#endif /* SDRBASE_DSP_DEVICESAMPLESINK_H_ */ diff --git a/android/app/src/main/cpp/dsp/devicesamplesource.cpp b/android/app/src/main/cpp/dsp/devicesamplesource.cpp new file mode 100644 index 0000000..0a3a516 --- /dev/null +++ b/android/app/src/main/cpp/dsp/devicesamplesource.cpp @@ -0,0 +1,115 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/devicesamplestatic.h" +#include "dsp/devicesamplesource.h" + +DeviceSampleSource::DeviceSampleSource() : + m_guiMessageQueue(0) +{ + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +DeviceSampleSource::~DeviceSampleSource() +{ +} + +void DeviceSampleSource::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +qint64 DeviceSampleSource::calculateDeviceCenterFrequency( + quint64 centerFrequency, + qint64 transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + FrequencyShiftScheme frequencyShiftScheme, + bool transverterMode) +{ + return DeviceSampleStatic::calculateSourceDeviceCenterFrequency( + centerFrequency, + transverterDeltaFrequency, + log2Decim, + (DeviceSampleStatic::fcPos_t) fcPos, + devSampleRate, + (DeviceSampleStatic::FrequencyShiftScheme) frequencyShiftScheme, + transverterMode + ); +} + +qint64 DeviceSampleSource::calculateCenterFrequency( + quint64 deviceCenterFrequency, + qint64 transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + FrequencyShiftScheme frequencyShiftScheme, + bool transverterMode) +{ + return DeviceSampleStatic::calculateSourceCenterFrequency( + deviceCenterFrequency, + transverterDeltaFrequency, + log2Decim, + (DeviceSampleStatic::fcPos_t) fcPos, + devSampleRate, + (DeviceSampleStatic::FrequencyShiftScheme) frequencyShiftScheme, + transverterMode + ); +} + +/** + * log2Decim = 0: no shift + * + * n = log2Decim <= 2: fc = +/- 1/2^(n-1) + * center + * | ^ | + * | inf | sup | + * ^ ^ + * + * n = log2Decim > 2: fc = +/- 1/2^n + * center + * | ^ | + * | |inf| | |sup| | + * ^ ^ + */ +qint32 DeviceSampleSource::calculateFrequencyShift( + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + FrequencyShiftScheme frequencyShiftScheme) +{ + return DeviceSampleStatic::calculateSourceFrequencyShift( + log2Decim, + (DeviceSampleStatic::fcPos_t) fcPos, + devSampleRate, + (DeviceSampleStatic::FrequencyShiftScheme) frequencyShiftScheme + ); +} diff --git a/android/app/src/main/cpp/dsp/devicesamplesource.h b/android/app/src/main/cpp/dsp/devicesamplesource.h new file mode 100644 index 0000000..6b93dad --- /dev/null +++ b/android/app/src/main/cpp/dsp/devicesamplesource.h @@ -0,0 +1,178 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SAMPLESOURCE_H +#define INCLUDE_SAMPLESOURCE_H + +#include +#include + +#include "samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "export.h" + +namespace SWGSDRangel +{ + class SWGDeviceSettings; + class SWGDeviceState; + class SWGDeviceReport; + class SWGDeviceActions; +} + +class SDRBASE_API DeviceSampleSource : public QObject { + Q_OBJECT +public: + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + typedef enum { + FSHIFT_STD = 0, // Standard Rx independent + FSHIFT_TXSYNC // Follows same scheme as Tx + } FrequencyShiftScheme; + + DeviceSampleSource(); + virtual ~DeviceSampleSource(); + virtual void destroy() = 0; + + virtual void init() = 0; //!< initializations to be done when all collaborating objects are created and possibly connected + virtual bool start() = 0; + virtual void stop() = 0; + + virtual QByteArray serialize() const = 0; + virtual bool deserialize(const QByteArray& data) = 0; + + virtual const QString& getDeviceDescription() const = 0; + virtual int getSampleRate() const = 0; //!< Sample rate exposed by the source + virtual void setSampleRate(int sampleRate) = 0; //!< For when the source sample rate is set externally + virtual quint64 getCenterFrequency() const = 0; //!< Center frequency exposed by the source + virtual void setCenterFrequency(qint64 centerFrequency) = 0; + + virtual bool handleMessage(const Message& message) = 0; + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiSettingsPutPatch( + bool force, //!< true to force settings = put + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) + { + (void) force; + (void) deviceSettingsKeys; + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) + { + (void) run; + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage) + { + (void) response; + errorMessage = "Not implemented"; + return 501; + } + + virtual int webapiActionsPost( + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceActions& actions, + QString& errorMessage) + { + (void) deviceSettingsKeys; + (void) actions; + errorMessage = "Not implemented"; + return 501; + } + + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this + MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } + SampleSinkFifo* getSampleFifo() { return &m_sampleFifo; } + + static qint64 calculateDeviceCenterFrequency( + quint64 centerFrequency, + qint64 transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + FrequencyShiftScheme frequencyShiftScheme, + bool transverterMode = false + ); + + static qint64 calculateCenterFrequency( + quint64 deviceCenterFrequency, + qint64 transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + FrequencyShiftScheme frequencyShiftScheme, + bool transverterMode = false + ); + + static qint32 calculateFrequencyShift( + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + FrequencyShiftScheme frequencyShiftScheme + ); + +protected slots: + void handleInputMessages(); + +signals: + void positionChanged(float latitude, float longitude, float altitude); + void directionChanged(bool isotropic, float azimuth, float elevation); + +protected: + SampleSinkFifo m_sampleFifo; + MessageQueue m_inputMessageQueue; //!< Input queue to the source + MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI +}; + +#endif // INCLUDE_SAMPLESOURCE_H diff --git a/android/app/src/main/cpp/dsp/devicesamplestatic.cpp b/android/app/src/main/cpp/dsp/devicesamplestatic.cpp new file mode 100644 index 0000000..e33ab7b --- /dev/null +++ b/android/app/src/main/cpp/dsp/devicesamplestatic.cpp @@ -0,0 +1,247 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "devicesamplestatic.h" + +int64_t DeviceSampleStatic::calculateSourceDeviceCenterFrequency( + uint64_t centerFrequency, + int64_t transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + uint32_t devSampleRate, + FrequencyShiftScheme frequencyShiftScheme, + bool transverterMode) +{ + int64_t deviceCenterFrequency = centerFrequency; + deviceCenterFrequency -= transverterMode ? transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + int64_t f_img = deviceCenterFrequency; + + deviceCenterFrequency -= calculateSourceFrequencyShift(log2Decim, fcPos, devSampleRate, frequencyShiftScheme); + f_img -= 2*calculateSourceFrequencyShift(log2Decim, fcPos, devSampleRate, frequencyShiftScheme); + + qDebug() << "DeviceSampleStatic::calculateSourceDeviceCenterFrequency:" + << " frequencyShiftScheme: " << frequencyShiftScheme + << " desired center freq: " << centerFrequency << " Hz" + << " device center freq: " << deviceCenterFrequency << " Hz" + << " device sample rate: " << devSampleRate << "S/s" + << " Actual sample rate: " << devSampleRate/(1< 2: fc = +/- 1/2^n + * center + * | ^ | + * | |inf| | |sup| | + * ^ ^ + */ +int DeviceSampleStatic::calculateSourceFrequencyShift( + int log2Decim, + fcPos_t fcPos, + uint32_t devSampleRate, + FrequencyShiftScheme frequencyShiftScheme) +{ + if (frequencyShiftScheme == FSHIFT_STD) + { + if (log2Decim == 0) { // no shift at all + return 0; + } else if (log2Decim < 3) { + if (fcPos == FC_POS_INFRA) { // shift in the square next to center frequency + return -(devSampleRate / (1<<(log2Decim+1))); + } else if (fcPos == FC_POS_SUPRA) { + return devSampleRate / (1<<(log2Decim+1)); + } else { + return 0; + } + } else { + if (fcPos == FC_POS_INFRA) { // shift centered in the square next to center frequency + return -(devSampleRate / (1<<(log2Decim))); + } else if (fcPos == FC_POS_SUPRA) { + return devSampleRate / (1<<(log2Decim)); + } else { + return 0; + } + } + } + else // frequencyShiftScheme == FSHIFT_TXSYNC + { + if (fcPos == FC_POS_CENTER) { + return 0; + } + + int sign = fcPos == FC_POS_INFRA ? -1 : 1; + int halfSampleRate = devSampleRate / 2; // fractions are relative to sideband thus based on half the sample rate + + if (log2Decim == 0) { + return 0; + } else if (log2Decim == 1) { + return sign * (halfSampleRate / 2); // inf or sup: 1/2 + } else if (log2Decim == 2) { + return sign * ((halfSampleRate * 3) / 4); // inf, inf or sup, sup: 1/2 + 1/4 + } else if (log2Decim == 3) { + return sign * ((halfSampleRate * 5) / 8); // inf, inf, sup or sup, sup, inf: 1/2 + 1/4 - 1/8 = 5/8 + } else if (log2Decim == 4) { + return sign * ((halfSampleRate * 11) / 16); // inf, inf, sup, inf or sup, sup, inf, sup: 1/2 + 1/4 - 1/8 + 1/16 = 11/16 + } else if (log2Decim == 5) { + return sign * ((halfSampleRate * 21) / 32); // inf, inf, sup, inf, sup or sup, sup, inf, sup, inf: 1/2 + 1/4 - 1/8 + 1/16 - 1/32 = 21/32 + } else if (log2Decim == 6) { + return sign * ((halfSampleRate * 21) / 64); // inf, sup, inf, sup, inf, sup or sup, inf, sup, inf, sup, inf: 1/2 - 1/4 + 1/8 -1/16 + 1/32 - 1/64 = 21/64 + } else { + return 0; + } + } +} + +int64_t DeviceSampleStatic::calculateSinkDeviceCenterFrequency( + uint64_t centerFrequency, + int64_t transverterDeltaFrequency, + int log2Interp, + fcPos_t fcPos, + uint32_t devSampleRate, + bool transverterMode) +{ + int64_t deviceCenterFrequency = centerFrequency; + deviceCenterFrequency -= transverterMode ? transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + int64_t f_img = deviceCenterFrequency; + + deviceCenterFrequency -= calculateSinkFrequencyShift(log2Interp, fcPos, devSampleRate); + f_img -= 2*calculateSinkFrequencyShift(log2Interp, fcPos, devSampleRate); + + qDebug() << "DeviceSampleStatic::calculateSinkDeviceCenterFrequency:" + << " desired center freq: " << centerFrequency << " Hz" + << " device center freq: " << deviceCenterFrequency << " Hz" + << " device sample rate: " << devSampleRate << "S/s" + << " Actual sample rate: " << devSampleRate/(1< // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "export.h" + +class SDRBASE_API DeviceSampleStatic +{ +public: + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + typedef enum { + FSHIFT_STD = 0, // Standard Rx independent + FSHIFT_TXSYNC // Follows same scheme as Tx + } FrequencyShiftScheme; + + static int64_t calculateSourceDeviceCenterFrequency( + uint64_t centerFrequency, + int64_t transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + uint32_t devSampleRate, + FrequencyShiftScheme frequencyShiftScheme, + bool transverterMode = false + ); + + static int64_t calculateSourceCenterFrequency( + uint64_t deviceCenterFrequency, + int64_t transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + uint32_t devSampleRate, + FrequencyShiftScheme frequencyShiftScheme, + bool transverterMode = false + ); + + static int calculateSourceFrequencyShift( + int log2Decim, + fcPos_t fcPos, + uint32_t devSampleRate, + FrequencyShiftScheme frequencyShiftScheme + ); + + static int64_t calculateSinkDeviceCenterFrequency( + uint64_t centerFrequency, + int64_t transverterDeltaFrequency, + int log2Interp, + fcPos_t fcPos, + uint32_t devSampleRate, + bool transverterMode = false); + + static int64_t calculateSinkCenterFrequency( + uint64_t deviceCenterFrequency, + int64_t transverterDeltaFrequency, + int log2Interp, + fcPos_t fcPos, + uint32_t devSampleRate, + bool transverterMode = false); + + static int calculateSinkFrequencyShift( + int log2Interp, + fcPos_t fcPos, + uint32_t devSampleRate); +}; diff --git a/android/app/src/main/cpp/dsp/downchannelizer.cpp b/android/app/src/main/cpp/dsp/downchannelizer.cpp new file mode 100644 index 0000000..6c2bd48 --- /dev/null +++ b/android/app/src/main/cpp/dsp/downchannelizer.cpp @@ -0,0 +1,336 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include "dsp/hbfilterchainconverter.h" +#include "downchannelizer.h" + +DownChannelizer::DownChannelizer(ChannelSampleSink* sampleSink) : + m_filterChainSetMode(false), + m_sampleSink(sampleSink), + m_basebandSampleRate(0), + m_requestedOutputSampleRate(0), + m_requestedCenterFrequency(0), + m_channelSampleRate(0), + m_channelFrequencyOffset(0), + m_log2Decim(0), + m_filterChainHash(0) +{ +} + +DownChannelizer::~DownChannelizer() +{ + freeFilterChain(); +} + +void DownChannelizer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + if (m_sampleSink == 0) + { + m_sampleBuffer.clear(); + return; + } + + if (m_filterStages.size() == 0) // optimization when no downsampling is done anyway + { + m_sampleSink->feed(begin, end); + } + else + { + for (SampleVector::const_iterator sample = begin; sample != end; ++sample) + { + Sample s(*sample); + FilterStages::iterator stage = m_filterStages.begin(); + + for (; stage != m_filterStages.end(); ++stage) + { +#ifndef SDR_RX_SAMPLE_24BIT + s.m_real /= 2; // avoid saturation on 16 bit samples + s.m_imag /= 2; +#endif + if (!(*stage)->work(&s)) { + break; + } + } + + if(stage == m_filterStages.end()) + { +#ifdef SDR_RX_SAMPLE_24BIT + s.m_real /= (1<<(m_filterStages.size())); // on 32 bit samples there is enough headroom to just divide the final result + s.m_imag /= (1<<(m_filterStages.size())); +#endif + m_sampleBuffer.push_back(s); + } + } + + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end()); + m_sampleBuffer.clear(); + } +} + +void DownChannelizer::setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency) +{ + if (requestedSampleRate < 0) + { + qWarning("DownChannelizer::setChannelization: wrong sample rate requested: %d", requestedSampleRate); + return; + } + + m_requestedOutputSampleRate = requestedSampleRate; + m_requestedCenterFrequency = requestedCenterFrequency; + applyChannelization(); +} + +void DownChannelizer::setBasebandSampleRate(int basebandSampleRate, bool decim) +{ + m_basebandSampleRate = basebandSampleRate; + + if (decim) { + applyDecimation(); + } else { + applyChannelization(); + } +} + +void DownChannelizer::applyChannelization() +{ + m_filterChainSetMode = false; + + if (m_basebandSampleRate == 0) + { + qDebug() << "DownChannelizer::applyChannelization: aborting (in=0)" + << " in (baseband):" << m_basebandSampleRate + << " req:" << m_requestedOutputSampleRate + << " out (channel):" << m_channelSampleRate + << " fc:" << m_channelFrequencyOffset; + return; + } + + freeFilterChain(); + + m_channelFrequencyOffset = createFilterChain( + m_basebandSampleRate / -2, m_basebandSampleRate / 2, + m_requestedCenterFrequency - m_requestedOutputSampleRate / 2, m_requestedCenterFrequency + m_requestedOutputSampleRate / 2); + + m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size()); + + qDebug() << "DownChannelizer::applyChannelization done:" + << " nb stages:" << m_filterStages.size() + << " in (baseband):" << m_basebandSampleRate + << " req:" << m_requestedOutputSampleRate + << " out (channel):" << m_channelSampleRate + << " fc:" << m_channelFrequencyOffset; +} + +void DownChannelizer::setDecimation(unsigned int log2Decim, unsigned int filterChainHash) +{ + m_log2Decim = log2Decim; + m_filterChainHash = filterChainHash; + applyDecimation(); +} + +void DownChannelizer::applyDecimation() +{ + m_filterChainSetMode = true; + std::vector stageIndexes; + m_channelFrequencyOffset = m_basebandSampleRate * HBFilterChainConverter::convertToIndexes(m_log2Decim, m_filterChainHash, stageIndexes); + m_requestedCenterFrequency = m_channelFrequencyOffset; + + freeFilterChain(); + + m_channelFrequencyOffset = m_basebandSampleRate * setFilterChain(stageIndexes); + m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size()); + m_requestedOutputSampleRate = m_channelSampleRate; + + qDebug() << "DownChannelizer::applyDecimation:" + << " m_log2Decim:" << m_log2Decim + << " m_filterChainHash:" << m_filterChainHash + << " out:" << m_basebandSampleRate + << " in:" << m_channelSampleRate + << " fc:" << m_channelFrequencyOffset; +} + +#ifdef SDR_RX_SAMPLE_24BIT +DownChannelizer::FilterStage::FilterStage(Mode mode) : + m_filter(new IntHalfbandFilterEO), + m_workFunction(0), + m_mode(mode), + m_sse(true) +{ + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; + break; + + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; + break; + + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; + break; + } +} +#else +DownChannelizer::FilterStage::FilterStage(Mode mode) : + m_filter(new IntHalfbandFilterEO), + m_workFunction(0), + m_mode(mode), + m_sse(true) +{ + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; + break; + + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; + break; + + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; + break; + } +} +#endif + +DownChannelizer::FilterStage::~FilterStage() +{ + delete m_filter; +} + +Real DownChannelizer::channelMinSpace(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) +{ + Real leftSpace = chanStart - sigStart; + Real rightSpace = sigEnd - chanEnd; + return std::min(leftSpace, rightSpace); +} + +Real DownChannelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) +{ + Real sigBw = sigEnd - sigStart; + Real chanBw = chanEnd - chanStart; + Real rot = sigBw / 4; + + + std::array filterMinSpaces; // Array of left, center and right filter min spaces respectively + filterMinSpaces[0] = channelMinSpace(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd); + filterMinSpaces[1] = channelMinSpace(sigStart + rot, sigEnd - rot, chanStart, chanEnd); + filterMinSpaces[2] = channelMinSpace(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd); + auto maxIt = std::max_element(filterMinSpaces.begin(), filterMinSpaces.end()); + int maxIndex = maxIt - filterMinSpaces.begin(); + Real maxValue = *maxIt; + + qDebug("DownChannelizer::createFilterChain: Signal [%.1f, %.1f] (BW %.1f) Channel [%.1f, %.1f] (BW %.1f) Selected: %d (fit %.1f)", + sigStart, sigEnd, sigBw, chanStart, chanEnd, chanBw, maxIndex, maxValue); + + if ((sigStart < sigEnd) && (chanStart < chanEnd) && (maxValue >= chanBw/8.0)) + { + if (maxIndex == 0) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); + return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd); + } + + if (maxIndex == 1) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); + return createFilterChain(sigStart + rot, sigEnd - rot, chanStart, chanEnd); + } + + if (maxIndex == 2) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); + return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd); + } + } + + Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart); + qDebug("DownChannelizer::createFilterChain: -> complete (final BW %.1f, frequency offset %.1f)", sigBw, ofs); + return ofs; +} + +double DownChannelizer::setFilterChain(const std::vector& stageIndexes) +{ + // filters are described from lower to upper level but the chain is constructed the other way round + std::vector::const_reverse_iterator rit = stageIndexes.rbegin(); + double ofs = 0.0, ofs_stage = 0.25; + + // Each index is a base 3 number with 0 = low, 1 = center, 2 = high + // Functions at upper level will convert a number to base 3 to describe the filter chain. Common converting + // algorithms will go from LSD to MSD. This explains the reverse order. + for (; rit != stageIndexes.rend(); ++rit) + { + if (*rit == 0) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); + ofs -= ofs_stage; + qDebug("DownChannelizer::setFilterChain: lower half: ofs: %f", ofs); + } + else if (*rit == 1) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); + qDebug("DownChannelizer::setFilterChain: center: ofs: %f", ofs); + } + else if (*rit == 2) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); + ofs += ofs_stage; + qDebug("DownChannelizer::setFilterChain: upper half: ofs: %f", ofs); + } + } + + return ofs; +} + +void DownChannelizer::freeFilterChain() +{ + for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it) + delete *it; + m_filterStages.clear(); +} + +void DownChannelizer::debugFilterChain() +{ + qDebug("DownChannelizer::debugFilterChain: %lu stages", m_filterStages.size()); + + for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it) + { + switch ((*it)->m_mode) + { + case FilterStage::ModeCenter: + qDebug("DownChannelizer::debugFilterChain: center %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + case FilterStage::ModeLowerHalf: + qDebug("DownChannelizer::debugFilterChain: lower %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + case FilterStage::ModeUpperHalf: + qDebug("DownChannelizer::debugFilterChain: upper %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + default: + qDebug("DownChannelizer::debugFilterChain: none %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + } + } +} diff --git a/android/app/src/main/cpp/dsp/downchannelizer.h b/android/app/src/main/cpp/dsp/downchannelizer.h new file mode 100644 index 0000000..5072f60 --- /dev/null +++ b/android/app/src/main/cpp/dsp/downchannelizer.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020, 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DOWNCHANNELIZER_H +#define SDRBASE_DSP_DOWNCHANNELIZER_H + +#include +#include + +#include "export.h" +#include "dsp/inthalfbandfiltereo.h" + +#include "channelsamplesink.h" + +#define DOWNCHANNELIZER_HB_FILTER_ORDER 48 + +class SDRBASE_API DownChannelizer : public ChannelSampleSink { +public: + DownChannelizer(ChannelSampleSink* sampleSink); + virtual ~DownChannelizer(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void setDecimation(unsigned int log2Decim, unsigned int filterChainHash); //!< Define channelizer with decimation factor and filter chain definition + void setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency); //!< Define channelizer with requested sample rate and center frequency (shift in the baseband) + void setBasebandSampleRate(int basebandSampleRate, bool decim = false); //!< decim: true => use direct decimation false => use channel configuration + int getBasebandSampleRate() const { return m_basebandSampleRate; } + int getChannelSampleRate() const { return m_channelSampleRate; } + int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; } + +protected: + struct FilterStage { + enum Mode { + ModeCenter, + ModeLowerHalf, + ModeUpperHalf + }; + +#ifdef SDR_RX_SAMPLE_24BIT + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; +#else + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; +#endif + + WorkFunction m_workFunction; + Mode m_mode; + bool m_sse; + + FilterStage(Mode mode); + ~FilterStage(); + + bool work(Sample* sample) + { + return (m_filter->*m_workFunction)(sample); + } + }; + typedef std::list FilterStages; + FilterStages m_filterStages; + bool m_filterChainSetMode; + ChannelSampleSink* m_sampleSink; //!< Demodulator + int m_basebandSampleRate; + int m_requestedOutputSampleRate; + int m_requestedCenterFrequency; + int m_channelSampleRate; + int m_channelFrequencyOffset; + unsigned int m_log2Decim; + unsigned int m_filterChainHash; + SampleVector m_sampleBuffer; + + void applyChannelization(); + void applyDecimation(); + static Real channelMinSpace(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd); + Real createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd); + double setFilterChain(const std::vector& stageIndexes); + void freeFilterChain(); + void debugFilterChain(); +}; + +#endif // SDRBASE_DSP_DOWNCHANNELIZER_H diff --git a/android/app/src/main/cpp/dsp/dspcommands.cpp b/android/app/src/main/cpp/dsp/dspcommands.cpp new file mode 100644 index 0000000..ef3ccbf --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspcommands.cpp @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019, 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/dspcommands.h" + +MESSAGE_CLASS_DEFINITION(DSPAcquisitionInit, Message) +MESSAGE_CLASS_DEFINITION(DSPAcquisitionStart, Message) +MESSAGE_CLASS_DEFINITION(DSPAcquisitionStop, Message) +MESSAGE_CLASS_DEFINITION(DSPGenerationInit, Message) +MESSAGE_CLASS_DEFINITION(DSPGenerationStart, Message) +MESSAGE_CLASS_DEFINITION(DSPGenerationStop, Message) +MESSAGE_CLASS_DEFINITION(DSPGetSourceDeviceDescription, Message) +MESSAGE_CLASS_DEFINITION(DSPGetSinkDeviceDescription, Message) +MESSAGE_CLASS_DEFINITION(DSPGetErrorMessage, Message) +MESSAGE_CLASS_DEFINITION(DSPSetSource, Message) +MESSAGE_CLASS_DEFINITION(DSPSetSink, Message) +MESSAGE_CLASS_DEFINITION(DSPAddBasebandSampleSink, Message) +MESSAGE_CLASS_DEFINITION(DSPAddSpectrumSink, Message) +MESSAGE_CLASS_DEFINITION(DSPAddBasebandSampleSource, Message) +MESSAGE_CLASS_DEFINITION(DSPRemoveBasebandSampleSink, Message) +MESSAGE_CLASS_DEFINITION(DSPRemoveSpectrumSink, Message) +MESSAGE_CLASS_DEFINITION(DSPRemoveBasebandSampleSource, Message) +MESSAGE_CLASS_DEFINITION(DSPAddAudioSink, Message) +MESSAGE_CLASS_DEFINITION(DSPRemoveAudioSink, Message) +MESSAGE_CLASS_DEFINITION(DSPConfigureCorrection, Message) +MESSAGE_CLASS_DEFINITION(DSPEngineReport, Message) +MESSAGE_CLASS_DEFINITION(DSPConfigureScopeVis, Message) +MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message) +MESSAGE_CLASS_DEFINITION(DSPMIMOSignalNotification, Message) +MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(DSPConfigureAudio, Message) +MESSAGE_CLASS_DEFINITION(DSPPushMbeFrame, Message) diff --git a/android/app/src/main/cpp/dsp/dspcommands.h b/android/app/src/main/cpp/dsp/dspcommands.h new file mode 100644 index 0000000..377b699 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspcommands.h @@ -0,0 +1,400 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019, 2022-2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSPCOMMANDS_H +#define INCLUDE_DSPCOMMANDS_H + +#include +#include "dsp/dsptypes.h" +#include "util/message.h" +#include "export.h" + +class DeviceSampleSource; +class BasebandSampleSink; +class DeviceSampleSink; +class BasebandSampleSource; +class AudioFifo; + +class SDRBASE_API DSPAcquisitionInit : public Message { + MESSAGE_CLASS_DECLARATION +}; + +class SDRBASE_API DSPAcquisitionStart : public Message { + MESSAGE_CLASS_DECLARATION +}; + +class SDRBASE_API DSPAcquisitionStop : public Message { + MESSAGE_CLASS_DECLARATION +}; + +class SDRBASE_API DSPGenerationInit : public Message { + MESSAGE_CLASS_DECLARATION +}; + +class SDRBASE_API DSPGenerationStart : public Message { + MESSAGE_CLASS_DECLARATION +}; + +class SDRBASE_API DSPGenerationStop : public Message { + MESSAGE_CLASS_DECLARATION +}; + +class SDRBASE_API DSPGetSourceDeviceDescription : public Message { + MESSAGE_CLASS_DECLARATION + +public: + void setDeviceDescription(const QString& text) { m_deviceDescription = text; } + const QString& getDeviceDescription() const { return m_deviceDescription; } + +private: + QString m_deviceDescription; +}; + +class SDRBASE_API DSPGetSinkDeviceDescription : public Message { + MESSAGE_CLASS_DECLARATION + +public: + void setDeviceDescription(const QString& text) { m_deviceDescription = text; } + const QString& getDeviceDescription() const { return m_deviceDescription; } + +private: + QString m_deviceDescription; +}; + +class SDRBASE_API DSPGetErrorMessage : public Message { + MESSAGE_CLASS_DECLARATION + +public: + void setErrorMessage(const QString& text) { m_errorMessage = text; } + const QString& getErrorMessage() const { return m_errorMessage; } + +private: + QString m_errorMessage; +}; + +class SDRBASE_API DSPSetSource : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPSetSource(DeviceSampleSource* sampleSource) : Message(), m_sampleSource(sampleSource) { } + + DeviceSampleSource* getSampleSource() const { return m_sampleSource; } + +private: + DeviceSampleSource* m_sampleSource; +}; + +class SDRBASE_API DSPSetSink : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPSetSink(DeviceSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { } + + DeviceSampleSink* getSampleSink() const { return m_sampleSink; } + +private: + DeviceSampleSink* m_sampleSink; +}; + +class SDRBASE_API DSPAddBasebandSampleSink : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPAddBasebandSampleSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { } + + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + +private: + BasebandSampleSink* m_sampleSink; +}; + +class SDRBASE_API DSPAddSpectrumSink : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPAddSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { } + + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + +private: + BasebandSampleSink* m_sampleSink; +}; + +class SDRBASE_API DSPAddBasebandSampleSource : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPAddBasebandSampleSource(BasebandSampleSource* sampleSource) : Message(), m_sampleSource(sampleSource) { } + + BasebandSampleSource* getSampleSource() const { return m_sampleSource; } + +private: + BasebandSampleSource* m_sampleSource; +}; + +class SDRBASE_API DSPRemoveBasebandSampleSink : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPRemoveBasebandSampleSink(BasebandSampleSink* sampleSink, bool deleting) : Message(), m_sampleSink(sampleSink), m_deleting(deleting) { } + + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + bool getDeleting() const { return m_deleting; } + +private: + BasebandSampleSink* m_sampleSink; + bool m_deleting; +}; + +class SDRBASE_API DSPRemoveSpectrumSink : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPRemoveSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { } + + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + +private: + BasebandSampleSink* m_sampleSink; +}; + +class SDRBASE_API DSPRemoveBasebandSampleSource : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPRemoveBasebandSampleSource(BasebandSampleSource* sampleSource, bool deleting) : Message(), m_sampleSource(sampleSource), m_deleting(deleting) { } + + BasebandSampleSource* getSampleSource() const { return m_sampleSource; } + bool getDeleting() const { return m_deleting; } + +private: + BasebandSampleSource* m_sampleSource; + bool m_deleting; +}; + +class SDRBASE_API DSPAddAudioSink : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPAddAudioSink(AudioFifo* audioFifo) : Message(), m_audioFifo(audioFifo) { } + + AudioFifo* getAudioFifo() const { return m_audioFifo; } + +private: + AudioFifo* m_audioFifo; +}; + +class SDRBASE_API DSPRemoveAudioSink : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPRemoveAudioSink(AudioFifo* audioFifo) : Message(), m_audioFifo(audioFifo) { } + + AudioFifo* getAudioFifo() const { return m_audioFifo; } + +private: + AudioFifo* m_audioFifo; +}; + +class SDRBASE_API DSPConfigureCorrection : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPConfigureCorrection(bool dcOffsetCorrection, bool iqImbalanceCorrection) : + Message(), + m_dcOffsetCorrection(dcOffsetCorrection), + m_iqImbalanceCorrection(iqImbalanceCorrection) + { } + + bool getDCOffsetCorrection() const { return m_dcOffsetCorrection; } + bool getIQImbalanceCorrection() const { return m_iqImbalanceCorrection; } + +private: + bool m_dcOffsetCorrection; + bool m_iqImbalanceCorrection; + +}; + +class SDRBASE_API DSPEngineReport : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPEngineReport(int sampleRate, quint64 centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + + int getSampleRate() const { return m_sampleRate; } + quint64 getCenterFrequency() const { return m_centerFrequency; } + +private: + int m_sampleRate; + quint64 m_centerFrequency; +}; + +class SDRBASE_API DSPConfigureScopeVis : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPConfigureScopeVis(int triggerChannel, Real triggerLevelHigh, Real triggerLevelLow) : + Message(), + m_triggerChannel(triggerChannel), + m_triggerLevelHigh(triggerLevelHigh), + m_triggerLevelLow(triggerLevelLow) + { } + + int getTriggerChannel() const { return m_triggerChannel; } + Real getTriggerLevelHigh() const { return m_triggerLevelHigh; } + Real getTriggerLevelLow() const { return m_triggerLevelLow; } + +private: + int m_triggerChannel; + Real m_triggerLevelHigh; + Real m_triggerLevelLow; +}; + +class SDRBASE_API DSPSignalNotification : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPSignalNotification(int samplerate, qint64 centerFrequency, bool realElseComplex = false) : + Message(), + m_sampleRate(samplerate), + m_centerFrequency(centerFrequency), + m_realElseComplex(realElseComplex) + { } + + int getSampleRate() const { return m_sampleRate; } + qint64 getCenterFrequency() const { return m_centerFrequency; } + bool getRealElseComplex() const { return m_realElseComplex; } + +private: + int m_sampleRate; + qint64 m_centerFrequency; + bool m_realElseComplex; +}; + +class SDRBASE_API DSPMIMOSignalNotification : public Message { + MESSAGE_CLASS_DECLARATION +public: + DSPMIMOSignalNotification(int samplerate, qint64 centerFrequency, bool sourceOrSink, unsigned int index, bool realElseComplex = false) : + Message(), + m_sampleRate(samplerate), + m_centerFrequency(centerFrequency), + m_realElseComplex(realElseComplex), + m_sourceOrSink(sourceOrSink), + m_index(index) + { } + int getSampleRate() const { return m_sampleRate; } + qint64 getCenterFrequency() const { return m_centerFrequency; } + bool getRealElseComplex() const { return m_realElseComplex; } + bool getSourceOrSink() const { return m_sourceOrSink; } + unsigned int getIndex() const { return m_index; } +private: + int m_sampleRate; + qint64 m_centerFrequency; + bool m_realElseComplex; + bool m_sourceOrSink; + unsigned int m_index; +}; + +class SDRBASE_API DSPConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + +private: + int m_sampleRate; + int m_centerFrequency; +}; + +class SDRBASE_API DSPConfigureAudio : public Message { + MESSAGE_CLASS_DECLARATION + +public: + enum AudioType + { + AudioInput, + AudioOutput + }; + + DSPConfigureAudio(int sampleRate, AudioType audioType) : + m_sampleRate(sampleRate), + m_autioType(audioType) + { } + + int getSampleRate() const { return m_sampleRate; } + AudioType getAudioType() const { return m_autioType; } + +private: + int m_sampleRate; + AudioType m_autioType; +}; + +class SDRBASE_API DSPPushMbeFrame : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPPushMbeFrame( + const unsigned char *mbeFrame, + int mbeRateIndex, + int mbeVolumeIndex, + unsigned char channels, + bool useHP, + int upsampling, + AudioFifo *audioFifo + ) : + Message(), + m_mbeFrame(mbeFrame), + m_mbeRateIndex(mbeRateIndex), + m_mbeVolumeIndex(mbeVolumeIndex), + m_channels(channels), + m_useHP(useHP), + m_upsampling(upsampling), + m_audioFifo(audioFifo) + { } + + const unsigned char * getMbeFrame() const { return m_mbeFrame; } + int getMbeRateIndex() const { return m_mbeRateIndex; } + int getMbeVolumeIndex() const { return m_mbeVolumeIndex; } + unsigned char getChannels() const { return m_channels; } + bool getUseHP() const { return m_useHP; } + int getUpsampling() const { return m_upsampling; } + AudioFifo *getAudioFifo() const { return m_audioFifo; } + +private: + const unsigned char *m_mbeFrame; + int m_mbeRateIndex; + int m_mbeVolumeIndex; + unsigned char m_channels; + bool m_useHP; + int m_upsampling; + AudioFifo *m_audioFifo; +}; + +#endif // INCLUDE_DSPCOMMANDS_H diff --git a/android/app/src/main/cpp/dsp/dspdevicemimoengine.cpp b/android/app/src/main/cpp/dsp/dspdevicemimoengine.cpp new file mode 100644 index 0000000..c97bcaf --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspdevicemimoengine.cpp @@ -0,0 +1,1351 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dspcommands.h" +#include "basebandsamplesink.h" +#include "basebandsamplesource.h" +#include "devicesamplemimo.h" +#include "mimochannel.h" + +#include "dspdevicemimoengine.h" + +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::SetSampleMIMO, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::AddBasebandSampleSource, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::RemoveBasebandSampleSource, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::AddMIMOChannel, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::RemoveMIMOChannel, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::AddBasebandSampleSink, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::RemoveBasebandSampleSink, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::AddSpectrumSink, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::RemoveSpectrumSink, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::GetErrorMessage, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::GetMIMODeviceDescription, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::ConfigureCorrection, Message) +MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::SetSpectrumSinkInput, Message) + +DSPDeviceMIMOEngine::DSPDeviceMIMOEngine(uint32_t uid, QObject* parent) : + QObject(parent), + m_uid(uid), + m_stateRx(State::StNotStarted), + m_stateTx(State::StNotStarted), + m_deviceSampleMIMO(nullptr), + m_spectrumInputSourceElseSink(true), + m_spectrumInputIndex(0) +{ + setStateRx(State::StIdle); + setStateTx(State::StIdle); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +DSPDeviceMIMOEngine::~DSPDeviceMIMOEngine() +{ + qDebug("DSPDeviceMIMOEngine::~DSPDeviceMIMOEngine"); +} + +void DSPDeviceMIMOEngine::setStateRx(State state) +{ + if (m_stateRx != state) + { + m_stateRx = state; + emit stateChanged(); + } +} + +void DSPDeviceMIMOEngine::setStateTx(State state) +{ + if (m_stateTx != state) + { + m_stateTx = state; + emit stateChanged(); + } +} + +bool DSPDeviceMIMOEngine::initProcess(int subsystemIndex) +{ + qDebug() << "DSPDeviceMIMOEngine::initProcess: subsystemIndex: " << subsystemIndex; + + if (subsystemIndex == 0) // Rx side + { + auto *cmd = new DSPAcquisitionInit(); + getInputMessageQueue()->push(cmd); + return true; + } + else if (subsystemIndex == 1) // Tx side + { + auto *cmd = new DSPGenerationInit(); + getInputMessageQueue()->push(cmd); + return true; + } + + return false; +} + +bool DSPDeviceMIMOEngine::startProcess(int subsystemIndex) +{ + qDebug() << "DSPDeviceMIMOEngine::startProcess: subsystemIndex: " << subsystemIndex; + if (subsystemIndex == 0) // Rx side + { + auto *cmd = new DSPAcquisitionStart(); + getInputMessageQueue()->push(cmd); + return true; + } + else if (subsystemIndex == 1) // Tx side + { + auto *cmd = new DSPGenerationStart(); + getInputMessageQueue()->push(cmd); + return true; + } + + return false; +} + +void DSPDeviceMIMOEngine::stopProcess(int subsystemIndex) +{ + qDebug() << "DSPDeviceMIMOEngine::stopProcess: subsystemIndex: " << subsystemIndex; + + if (subsystemIndex == 0) // Rx side + { + auto *cmd = new DSPAcquisitionStop(); + getInputMessageQueue()->push(cmd); + } + else if (subsystemIndex == 1) // Tx side + { + auto *cmd = new DSPGenerationStop(); + getInputMessageQueue()->push(cmd); + } +} + +void DSPDeviceMIMOEngine::setMIMO(DeviceSampleMIMO* mimo) +{ + qDebug() << "DSPDeviceMIMOEngine::setMIMO"; + auto *cmd = new SetSampleMIMO(mimo); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::setMIMOSequence(int sequence) +{ + qDebug("DSPDeviceMIMOEngine::setSinkSequence: seq: %d", sequence); + m_sampleMIMOSequence = sequence; +} + +void DSPDeviceMIMOEngine::addChannelSource(BasebandSampleSource* source, int index) +{ + qDebug() << "DSPDeviceMIMOEngine::addChannelSource: " + << source->getSourceName().toStdString().c_str() + << " at: " + << index; + auto *cmd = new AddBasebandSampleSource(source, index); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::removeChannelSource(BasebandSampleSource* source, bool deleting, int index) +{ + qDebug() << "DSPDeviceMIMOEngine::removeChannelSource: " + << source->getSourceName().toStdString().c_str() + << " at: " + << index; + auto *cmd = new RemoveBasebandSampleSource(source, index, deleting); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::addChannelSink(BasebandSampleSink* sink, int index) +{ + qDebug() << "DSPDeviceMIMOEngine::addChannelSink: " + << sink->getSinkName().toStdString().c_str() + << " at: " + << index; + auto *cmd = new AddBasebandSampleSink(sink, index); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::removeChannelSink(BasebandSampleSink* sink, int index) +{ + qDebug() << "DSPDeviceMIMOEngine::removeChannelSink: " + << sink->getSinkName().toStdString().c_str() + << " at: " + << index; + auto *cmd = new RemoveBasebandSampleSink(sink, index); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::addMIMOChannel(MIMOChannel *channel) +{ + qDebug() << "DSPDeviceMIMOEngine::addMIMOChannel: " + << channel->getMIMOName().toStdString().c_str(); + auto *cmd = new AddMIMOChannel(channel); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::removeMIMOChannel(MIMOChannel *channel) +{ + qDebug() << "DSPDeviceMIMOEngine::removeMIMOChannel: " + << channel->getMIMOName().toStdString().c_str(); + auto *cmd = new RemoveMIMOChannel(channel); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::addSpectrumSink(BasebandSampleSink* spectrumSink) +{ + qDebug() << "DSPDeviceMIMOEngine::addSpectrumSink: " << spectrumSink->getSinkName().toStdString().c_str(); + auto *cmd = new AddSpectrumSink(spectrumSink); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::removeSpectrumSink(BasebandSampleSink* spectrumSink) +{ + qDebug() << "DSPDeviceSinkEngine::removeSpectrumSink: " << spectrumSink->getSinkName().toStdString().c_str(); + auto *cmd = new RemoveSpectrumSink(spectrumSink); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceMIMOEngine::setSpectrumSinkInput(bool sourceElseSink, int index) +{ + qDebug() << "DSPDeviceSinkEngine::setSpectrumSinkInput: " + << " sourceElseSink: " << sourceElseSink + << " index: " << index; + auto *cmd = new SetSpectrumSinkInput(sourceElseSink, index); + getInputMessageQueue()->push(cmd); +} + +QString DSPDeviceMIMOEngine::errorMessage(int subsystemIndex) const +{ + qDebug() << "DSPDeviceMIMOEngine::errorMessage: subsystemIndex:" << subsystemIndex; + if (subsystemIndex == 0) { + return m_errorMessageRx; + } else if (subsystemIndex == 1) { + return m_errorMessageTx; + } else { + return "Not implemented"; + } +} + +QString DSPDeviceMIMOEngine::deviceDescription() const +{ + qDebug() << "DSPDeviceMIMOEngine::deviceDescription"; + return m_deviceDescription; +} + +void DSPDeviceMIMOEngine::workSampleSinkFifos() +{ + SampleMIFifo* sampleFifo = m_deviceSampleMIMO->getSampleMIFifo(); + + if (!sampleFifo) { + return; + } + + unsigned int iPart1Begin; + unsigned int iPart1End; + unsigned int iPart2Begin; + unsigned int iPart2End; + const std::vector& data = sampleFifo->getData(); + + while ((sampleFifo->fillSync() > 0) && (m_inputMessageQueue.size() == 0)) + { + sampleFifo->readSync(iPart1Begin, iPart1End, iPart2Begin, iPart2End); + + if (iPart1Begin != iPart1End) + { + for (unsigned int stream = 0; stream < data.size(); stream++) { + workSamplesSink(data[stream].begin() + iPart1Begin, data[stream].begin() + iPart1End, stream); + } + } + + if (iPart2Begin != iPart2End) + { + for (unsigned int stream = 0; stream < data.size(); stream++) { + workSamplesSink(data[stream].begin() + iPart2Begin, data[stream].begin() + iPart2End, stream); + } + } + } +} + +void DSPDeviceMIMOEngine::workSampleSourceFifos() +{ + SampleMOFifo* sampleFifo = m_deviceSampleMIMO->getSampleMOFifo(); + + if (!sampleFifo) { + return; + } + + std::vector vbegin; + std::vector& data = sampleFifo->getData(); + unsigned int iPart1Begin; + unsigned int iPart1End; + unsigned int iPart2Begin; + unsigned int iPart2End; + unsigned int remainder = sampleFifo->remainderSync(); + + while ((remainder > 0) && (m_inputMessageQueue.size() == 0)) + { + sampleFifo->writeSync(remainder, iPart1Begin, iPart1End, iPart2Begin, iPart2End); + + // pull samples from the sources by stream + + if (iPart1Begin != iPart1End) + { + for (unsigned int streamIndex = 0; streamIndex < sampleFifo->getNbStreams(); streamIndex++) { + workSamplesSource(data[streamIndex], iPart1Begin, iPart1End, streamIndex); + } + } + + if (iPart2Begin != iPart2End) + { + for (unsigned int streamIndex = 0; streamIndex < sampleFifo->getNbStreams(); streamIndex++) { + workSamplesSource(data[streamIndex], iPart2Begin, iPart2End, streamIndex); + } + } + + // get new remainder + remainder = sampleFifo->remainderSync(); + } +} + +void DSPDeviceMIMOEngine::workSampleSinkFifo(unsigned int streamIndex) +{ + SampleMIFifo* sampleFifo = m_deviceSampleMIMO->getSampleMIFifo(); + + if (!sampleFifo) { + return; + } + + SampleVector::const_iterator part1begin; + SampleVector::const_iterator part1end; + SampleVector::const_iterator part2begin; + SampleVector::const_iterator part2end; + + while ((sampleFifo->fillAsync(streamIndex) > 0) && (m_inputMessageQueue.size() == 0)) + { + sampleFifo->readAsync(&part1begin, &part1end, &part2begin, &part2end, streamIndex); + + if (part1begin != part1end) { // first part of FIFO data + workSamplesSink(part1begin, part1end, streamIndex); + } + + if (part2begin != part2end) { // second part of FIFO data (used when block wraps around) + workSamplesSink(part2begin, part2end, streamIndex); + } + } +} + + +void DSPDeviceMIMOEngine::workSampleSourceFifo(unsigned int streamIndex) +{ + SampleMOFifo* sampleFifo = m_deviceSampleMIMO->getSampleMOFifo(); + + if (!sampleFifo) { + return; + } + + SampleVector& data = sampleFifo->getData(streamIndex); + unsigned int iPart1Begin; + unsigned int iPart1End; + unsigned int iPart2Begin; + unsigned int iPart2End; + unsigned int amount = sampleFifo->remainderAsync(streamIndex); + + while ((amount > 0) && (m_inputMessageQueue.size() == 0)) + { + sampleFifo->writeAsync(amount, iPart1Begin, iPart1End, iPart2Begin, iPart2End, streamIndex); + // part1 + if (iPart1Begin != iPart1End) { + workSamplesSource(data, iPart1Begin, iPart1End, streamIndex); + } + // part2 + if (iPart2Begin != iPart2End) { + workSamplesSource(data, iPart2Begin, iPart2End, streamIndex); + } + // get new amount + amount = sampleFifo->remainderAsync(streamIndex); + } +} + +/** + * Routes samples from device source FIFO to sink channels that are registered for the FIFO + * Routes samples from source channels registered for the FIFO to the device sink FIFO + */ +void DSPDeviceMIMOEngine::workSamplesSink(const SampleVector::const_iterator& vbegin, const SampleVector::const_iterator& vend, unsigned int streamIndex) +{ + std::map::const_iterator rcIt = m_rxRealElseComplex.find(streamIndex); + bool positiveOnly = (rcIt == m_rxRealElseComplex.end() ? false : rcIt->second); + + // feed data to direct sinks + if (streamIndex < m_basebandSampleSinks.size()) + { + for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks[streamIndex].begin(); it != m_basebandSampleSinks[streamIndex].end(); ++it) { + (*it)->feed(vbegin, vend, positiveOnly); + } + } + + // possibly feed data to spectrum sink + if (m_spectrumSink && m_spectrumInputSourceElseSink && (streamIndex == m_spectrumInputIndex)) { + m_spectrumSink->feed(vbegin, vend, positiveOnly); + } + + // feed data to MIMO channels + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) { + (*it)->feed(vbegin, vend, streamIndex); + } +} + +void DSPDeviceMIMOEngine::workSamplesSource(SampleVector& data, unsigned int iBegin, unsigned int iEnd, unsigned int streamIndex) +{ + unsigned int nbSamples = iEnd - iBegin; + SampleVector::iterator begin = data.begin() + iBegin; + + // pull data from MIMO channels + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) { + (*it)->pull(begin, nbSamples, streamIndex); + } + + if (m_mimoChannels.size() == 0) // Process single stream channels only if there are no MIMO channels + { + if (m_basebandSampleSources[streamIndex].size() == 0) + { + m_sourceZeroBuffers[streamIndex].allocate(nbSamples, Sample{0,0}); + std::copy( + m_sourceZeroBuffers[streamIndex].m_vector.begin(), + m_sourceZeroBuffers[streamIndex].m_vector.begin() + nbSamples, + begin + ); + } + else if (m_basebandSampleSources[streamIndex].size() == 1) + { + BasebandSampleSource *sampleSource = m_basebandSampleSources[streamIndex].front(); + sampleSource->pull(begin, nbSamples); + } + else + { + m_sourceSampleBuffers[streamIndex].allocate(nbSamples); + BasebandSampleSources::const_iterator srcIt = m_basebandSampleSources[streamIndex].begin(); + BasebandSampleSource *sampleSource = *srcIt; + sampleSource->pull(begin, nbSamples); + ++srcIt; + m_sumIndex = 1; + + for (; srcIt != m_basebandSampleSources[streamIndex].end(); ++srcIt, m_sumIndex++) + { + sampleSource = *srcIt; + auto aBegin = m_sourceSampleBuffers[streamIndex].m_vector.begin(); + sampleSource->pull(aBegin, nbSamples); + std::transform( + aBegin, + aBegin + nbSamples, + begin, + begin, + [this](Sample const& a, const Sample& b) -> Sample { + FixReal den = m_sumIndex + 1; // at each stage scale sum by n/n+1 and input by 1/n+1 + FixReal nom = m_sumIndex; // so that final sum is scaled by N (number of channels) + FixReal x = a.real()/den + nom*(b.real()/den); + FixReal y = a.imag()/den + nom*(b.imag()/den); + return Sample{x, y}; + } + ); + } + } + } + + // possibly feed data to spectrum sink + std::map::const_iterator rcIt = m_txRealElseComplex.find(streamIndex); + bool positiveOnly = (rcIt == m_txRealElseComplex.end() ? false : rcIt->second); + + if (m_spectrumSink && (!m_spectrumInputSourceElseSink) && (streamIndex == m_spectrumInputIndex)) { + m_spectrumSink->feed(begin, begin + nbSamples, positiveOnly); + } +} + +// notStarted -> idle -> init -> running -+ +// ^ | +// +-----------------------+ + +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoIdle(int subsystemIndex) +{ + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: subsystemIndex:" << subsystemIndex; + + if (!m_deviceSampleMIMO) { + return State::StIdle; + } + + if (subsystemIndex == 0) // Rx + { + switch (m_stateRx) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + case State::StError: + return State::StIdle; + + case State::StReady: + case State::StRunning: + break; + } + + m_deviceSampleMIMO->stopRx(); // stop everything + + std::vector::const_iterator vbit = m_basebandSampleSinks.begin(); + + for (; vbit != m_basebandSampleSinks.end(); ++vbit) + { + for (auto it = vbit->begin(); it != vbit->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping BasebandSampleSink: " << (*it)->getSinkName().toStdString().c_str(); + (*it)->stop(); + } + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping MIMOChannel sinks: " << (*it)->getMIMOName().toStdString().c_str(); + (*it)->stopSinks(); + } + } + else if (subsystemIndex == 1) // Tx + { + switch (m_stateTx) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + case State::StError: + return State::StIdle; + + case State::StReady: + case State::StRunning: + break; + } + + m_deviceSampleMIMO->stopTx(); // stop everything + + std::vector::const_iterator vSourceIt = m_basebandSampleSources.begin(); + + for (; vSourceIt != m_basebandSampleSources.end(); vSourceIt++) + { + for (auto it = vSourceIt->begin(); it != vSourceIt->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping BasebandSampleSource(" << (*it)->getSourceName().toStdString().c_str() << ")"; + (*it)->stop(); + } + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping MIMOChannel sources: " << (*it)->getMIMOName().toStdString().c_str(); + (*it)->stopSources(); + } + } + else + { + return State::StIdle; + } + + m_deviceDescription.clear(); + + return State::StIdle; +} + +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoInit(int subsystemIndex) +{ + if (!m_deviceSampleMIMO) { + return gotoError(subsystemIndex, "No sample MIMO configured"); + } + + m_deviceDescription = m_deviceSampleMIMO->getDeviceDescription(); + + qDebug() << "DSPDeviceMIMOEngine::gotoInit:" + << "subsystemIndex: " << subsystemIndex + << "m_deviceDescription: " << m_deviceDescription.toStdString().c_str(); + + if (subsystemIndex == 0) // Rx + { + switch(m_stateRx) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + return State::StReady; + + case State::StIdle: + case State::StError: + break; + } + + // init: pass sample rate and center frequency to all sample rate and/or center frequency dependent sinks and wait for completion + for (unsigned int isource = 0; isource < m_deviceSampleMIMO->getNbSourceStreams(); isource++) + { + if (isource < m_sourcesCorrections.size()) + { + m_sourcesCorrections[isource].m_iOffset = 0; + m_sourcesCorrections[isource].m_qOffset = 0; + m_sourcesCorrections[isource].m_iRange = 1 << 16; + m_sourcesCorrections[isource].m_qRange = 1 << 16; + } + + quint64 sourceCenterFrequency = m_deviceSampleMIMO->getSourceCenterFrequency(isource); + int sourceStreamSampleRate = m_deviceSampleMIMO->getSourceSampleRate(isource); + + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sourceCenterFrequencies[%d] = %llu", isource, sourceCenterFrequency); + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sourceStreamSampleRates[%d] = %d", isource, sourceStreamSampleRate); + + DSPSignalNotification notif(sourceStreamSampleRate, sourceCenterFrequency); + + if (isource < m_basebandSampleSinks.size()) + { + for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks[isource].begin(); it != m_basebandSampleSinks[isource].end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoInit: initializing " << (*it)->getSinkName().toStdString().c_str(); + (*it)->pushMessage(new DSPSignalNotification(notif)); + } + } + } + } + else if (subsystemIndex == 1) // Tx + { + switch(m_stateTx) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + return State::StReady; + + case State::StIdle: + case State::StError: + break; + } + + for (unsigned int isink = 0; isink < m_deviceSampleMIMO->getNbSinkStreams(); isink++) + { + quint64 sinkCenterFrequency = m_deviceSampleMIMO->getSinkCenterFrequency(isink); + int sinkStreamSampleRate = m_deviceSampleMIMO->getSinkSampleRate(isink); + + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sinkCenterFrequencies[%d] = %llu", isink, sinkCenterFrequency); + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sinkStreamSampleRates[%d] = %d", isink, sinkStreamSampleRate); + + DSPSignalNotification notif(sinkStreamSampleRate, sinkCenterFrequency); + + if (isink < m_basebandSampleSources.size()) + { + for (BasebandSampleSources::const_iterator it = m_basebandSampleSources[isink].begin(); it != m_basebandSampleSources[isink].end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoInit: initializing BasebandSampleSource(" << (*it)->getSourceName().toStdString().c_str() << ")"; + (*it)->pushMessage(new DSPSignalNotification(notif)); + } + } + } + } + + return State::StReady; +} + +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoRunning(int subsystemIndex) +{ + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: subsystemIndex:" << subsystemIndex; + + if (!m_deviceSampleMIMO) { + return gotoError(subsystemIndex, "DSPDeviceMIMOEngine::gotoRunning: No sample source configured"); + } + + qDebug() << "DSPDeviceMIMOEngine::gotoRunning:" << m_deviceDescription.toStdString().c_str() << "started"; + + if (subsystemIndex == 0) // Rx + { + switch (m_stateRx) + { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + return State::StIdle; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + case State::StError: + break; + } + + if (!m_deviceSampleMIMO->startRx()) { // Start everything + return gotoError(0, "Could not start sample source"); + } + + std::vector::const_iterator vbit = m_basebandSampleSinks.begin(); + + for (; vbit != m_basebandSampleSinks.end(); ++vbit) + { + for (auto it = vbit->begin(); it != vbit->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting BasebandSampleSink: " << (*it)->getSinkName().toStdString().c_str(); + (*it)->start(); + } + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting MIMOChannel sinks: " << (*it)->getMIMOName().toStdString().c_str(); + (*it)->startSinks(); + } + } + else if (subsystemIndex == 1) // Tx + { + switch (m_stateTx) + { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + return State::StIdle; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + case State::StError: + break; + } + + if (!m_deviceSampleMIMO->startTx()) { // Start everything + return gotoError(1, "Could not start sample sink"); + } + + std::vector::const_iterator vSourceIt = m_basebandSampleSources.begin(); + + for (; vSourceIt != m_basebandSampleSources.end(); vSourceIt++) + { + for (auto it = vSourceIt->begin(); it != vSourceIt->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting BasebandSampleSource(" << (*it)->getSourceName().toStdString().c_str() << ")"; + (*it)->start(); + } + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting MIMOChannel sources: " << (*it)->getMIMOName().toStdString().c_str(); + (*it)->startSources(); + } + } + + qDebug() << "DSPDeviceMIMOEngine::gotoRunning:input message queue pending: " << m_inputMessageQueue.size(); + + return State::StRunning; +} + +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoError(int subsystemIndex, const QString& errorMessage) +{ + qDebug() << "DSPDeviceMIMOEngine::gotoError: " + << " subsystemIndex: " << subsystemIndex + << " errorMessage: " << errorMessage; + + + if (subsystemIndex == 0) + { + m_errorMessageRx = errorMessage; + setStateRx(State::StError); + } + else if (subsystemIndex == 1) + { + m_errorMessageTx = errorMessage; + setStateTx(State::StError); + } + + return State::StError; +} + +void DSPDeviceMIMOEngine::handleDataRxSync() +{ + if (m_stateRx == State::StRunning) { + workSampleSinkFifos(); + } +} + +void DSPDeviceMIMOEngine::handleDataRxAsync(int streamIndex) +{ + if (m_stateRx == State::StRunning) { + workSampleSinkFifo(streamIndex); + } +} + +void DSPDeviceMIMOEngine::handleDataTxSync() +{ + if (m_stateTx == State::StRunning) { + workSampleSourceFifos(); + } +} + +void DSPDeviceMIMOEngine::handleDataTxAsync(int streamIndex) +{ + if (m_stateTx == State::StRunning) { + workSampleSourceFifo(streamIndex); + } +} + +void DSPDeviceMIMOEngine::handleSetMIMO(DeviceSampleMIMO* mimo) +{ + m_deviceSampleMIMO = mimo; + + if (!mimo) { // Early leave + return; + } + + for (unsigned int i = 0; i < m_deviceSampleMIMO->getNbSinkFifos(); i++) + { + m_basebandSampleSinks.emplace_back(); + m_sourcesCorrections.emplace_back(); + } + + for (unsigned int i = 0; i < m_deviceSampleMIMO->getNbSourceFifos(); i++) + { + m_basebandSampleSources.emplace_back(); + m_sourceSampleBuffers.emplace_back(); + m_sourceZeroBuffers.emplace_back(); + } + + if (m_deviceSampleMIMO->getMIMOType() == DeviceSampleMIMO::MIMOHalfSynchronous) // synchronous FIFOs on Rx and not with Tx + { + qDebug("DSPDeviceMIMOEngine::handleSetMIMO: synchronous sources set %s", qPrintable(mimo->getDeviceDescription())); + QObject::connect( + m_deviceSampleMIMO->getSampleMIFifo(), + &SampleMIFifo::dataSyncReady, + this, + &DSPDeviceMIMOEngine::handleDataRxSync, + Qt::QueuedConnection + ); + QObject::connect( + m_deviceSampleMIMO->getSampleMOFifo(), + &SampleMOFifo::dataReadSync, + this, + &DSPDeviceMIMOEngine::handleDataTxSync, + Qt::QueuedConnection + ); + } + else if (m_deviceSampleMIMO->getMIMOType() == DeviceSampleMIMO::MIMOAsynchronous) // asynchronous FIFOs + { + for (unsigned int stream = 0; stream < m_deviceSampleMIMO->getNbSourceStreams(); stream++) + { + qDebug("DSPDeviceMIMOEngine::handleSetMIMO: asynchronous sources set %s channel %u", + qPrintable(mimo->getDeviceDescription()), stream); + QObject::connect( + m_deviceSampleMIMO->getSampleMIFifo(), + &SampleMIFifo::dataAsyncReady, + this, + &DSPDeviceMIMOEngine::handleDataRxAsync, + Qt::QueuedConnection + ); + QObject::connect( + m_deviceSampleMIMO->getSampleMOFifo(), + &SampleMOFifo::dataReadAsync, + this, + &DSPDeviceMIMOEngine::handleDataTxAsync, + Qt::QueuedConnection + ); + } + } +} + +bool DSPDeviceMIMOEngine::handleMessage(const Message& message) +{ + if (ConfigureCorrection::match(message)) + { + const auto& conf = (const ConfigureCorrection&) message; + unsigned int isource = conf.getIndex(); + + if (isource < m_sourcesCorrections.size()) + { + m_sourcesCorrections[isource].m_iqImbalanceCorrection = conf.getIQImbalanceCorrection(); + + if (m_sourcesCorrections[isource].m_dcOffsetCorrection != conf.getDCOffsetCorrection()) + { + m_sourcesCorrections[isource].m_dcOffsetCorrection = conf.getDCOffsetCorrection(); + m_sourcesCorrections[isource].m_iOffset = 0; + m_sourcesCorrections[isource].m_qOffset = 0; + + if (m_sourcesCorrections[isource].m_iqImbalanceCorrection != conf.getIQImbalanceCorrection()) + { + m_sourcesCorrections[isource].m_iqImbalanceCorrection = conf.getIQImbalanceCorrection(); + m_sourcesCorrections[isource].m_iRange = 1 << 16; + m_sourcesCorrections[isource].m_qRange = 1 << 16; + m_sourcesCorrections[isource].m_imbalance = 65536; + } + } + m_sourcesCorrections[isource].m_iBeta.reset(); + m_sourcesCorrections[isource].m_qBeta.reset(); + m_sourcesCorrections[isource].m_avgAmp.reset(); + m_sourcesCorrections[isource].m_avgII.reset(); + m_sourcesCorrections[isource].m_avgII2.reset(); + m_sourcesCorrections[isource].m_avgIQ.reset(); + m_sourcesCorrections[isource].m_avgPhi.reset(); + m_sourcesCorrections[isource].m_avgQQ2.reset(); + m_sourcesCorrections[isource].m_iBeta.reset(); + m_sourcesCorrections[isource].m_qBeta.reset(); + } + + return true; + } + else if (DSPMIMOSignalNotification::match(message)) + { + const auto& notif = (const DSPMIMOSignalNotification&) message; + + // update DSP values + + bool sourceElseSink = notif.getSourceOrSink(); + unsigned int istream = notif.getIndex(); + int sampleRate = notif.getSampleRate(); + qint64 centerFrequency = notif.getCenterFrequency(); + bool realElseComplex = notif.getRealElseComplex(); + + qDebug() << "DeviceMIMOEngine::handleInputMessages: DSPMIMOSignalNotification:" + << " sourceElseSink: " << sourceElseSink + << " istream: " << istream + << " sampleRate: " << sampleRate + << " centerFrequency: " << centerFrequency + << " realElseComplex" << realElseComplex; + + if (sourceElseSink) { + m_rxRealElseComplex[istream] = realElseComplex; + } else { + m_txRealElseComplex[istream] = realElseComplex; + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + auto *msg = new DSPMIMOSignalNotification(notif); + (*it)->pushMessage(msg); + } + + if (m_deviceSampleMIMO) + { + if (sourceElseSink) + { + if (istream < m_deviceSampleMIMO->getNbSourceStreams()) + { + + // forward source changes to ancillary sinks + if (istream < m_basebandSampleSinks.size()) + { + for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks[istream].begin(); it != m_basebandSampleSinks[istream].end(); ++it) + { + auto *msg = new DSPSignalNotification(sampleRate, centerFrequency); + qDebug() << "DSPDeviceMIMOEngine::handleInputMessages: starting " << (*it)->getSinkName().toStdString().c_str(); + (*it)->pushMessage(msg); + } + } + + // forward changes to MIMO GUI input queue + MessageQueue *guiMessageQueue = m_deviceSampleMIMO->getMessageQueueToGUI(); + qDebug("DeviceMIMOEngine::handleInputMessages: DSPMIMOSignalNotification: guiMessageQueue: %p", guiMessageQueue); + + if (guiMessageQueue) { + auto* rep = new DSPMIMOSignalNotification(notif); // make a copy for the MIMO GUI + guiMessageQueue->push(rep); + } + + // forward changes to spectrum sink if currently active + if (m_spectrumSink && m_spectrumInputSourceElseSink && (m_spectrumInputIndex == istream)) + { + auto *spectrumNotif = new DSPSignalNotification(sampleRate, centerFrequency); + m_spectrumSink->pushMessage(spectrumNotif); + } + } + } + else + { + if (istream < m_deviceSampleMIMO->getNbSinkStreams()) + { + + // forward source changes to channel sources with immediate execution (no queuing) + if (istream < m_basebandSampleSources.size()) + { + for (BasebandSampleSources::const_iterator it = m_basebandSampleSources[istream].begin(); it != m_basebandSampleSources[istream].end(); ++it) + { + auto *msg = new DSPSignalNotification(sampleRate, centerFrequency); + qDebug() << "DSPDeviceMIMOEngine::handleSinkMessages: forward message to BasebandSampleSource(" << (*it)->getSourceName().toStdString().c_str() << ")"; + (*it)->pushMessage(msg); + } + } + + // forward changes to MIMO GUI input queue + MessageQueue *guiMessageQueue = m_deviceSampleMIMO->getMessageQueueToGUI(); + qDebug("DSPDeviceMIMOEngine::handleInputMessages: DSPSignalNotification: guiMessageQueue: %p", guiMessageQueue); + + if (guiMessageQueue) { + auto* rep = new DSPMIMOSignalNotification(notif); // make a copy for the source GUI + guiMessageQueue->push(rep); + } + + // forward changes to spectrum sink if currently active + if (m_spectrumSink && !m_spectrumInputSourceElseSink && (m_spectrumInputIndex == istream)) + { + auto *spectrumNotif = new DSPSignalNotification(sampleRate, centerFrequency); + m_spectrumSink->pushMessage(spectrumNotif); + } + } + } + } + + return true; + } + // was in handleSynchronousMessages + else if (DSPAcquisitionInit::match(message)) + { + setStateRx(gotoIdle(0)); + + if (m_stateRx == State::StIdle) { + setStateRx(gotoInit(0)); // State goes ready if init is performed + } + + return true; + } + else if (DSPAcquisitionStart::match(message)) + { + if (m_stateRx == State::StReady) { + setStateRx(gotoRunning(0)); + } + + return true; + } + else if (DSPAcquisitionStop::match(message)) + { + setStateRx(gotoIdle(0)); + emit acquisitionStopped(); + return true; + } + else if (DSPGenerationInit::match(message)) + { + setStateTx(gotoIdle(1)); + + if (m_stateTx == State::StIdle) { + setStateTx(gotoInit(1)); // State goes ready if init is performed + } + + return true; + } + else if (DSPGenerationStart::match(message)) + { + if (m_stateTx == State::StReady) { + setStateTx(gotoRunning(1)); + } + + return true; + } + else if (DSPGenerationStop::match(message)) + { + setStateTx(gotoIdle(1)); + emit generationStopped(); + return true; + } + else if (SetSampleMIMO::match(message)) { + const auto& cmd = (const SetSampleMIMO&) message; + handleSetMIMO(cmd.getSampleMIMO()); + emit sampleSet(); + return true; + } + else if (AddBasebandSampleSink::match(message)) + { + const auto& msg = (const AddBasebandSampleSink&) message; + BasebandSampleSink* sink = msg.getSampleSink(); + unsigned int isource = msg.getIndex(); + + if (isource < m_basebandSampleSinks.size()) + { + m_basebandSampleSinks[isource].push_back(sink); + // initialize sample rate and center frequency in the sink: + int sourceStreamSampleRate = m_deviceSampleMIMO->getSourceSampleRate(isource); + quint64 sourceCenterFrequency = m_deviceSampleMIMO->getSourceCenterFrequency(isource); + auto *msgToSink = new DSPSignalNotification(sourceStreamSampleRate, sourceCenterFrequency); + sink->pushMessage(msgToSink); + // start the sink: + if (m_stateRx == State::StRunning) { + sink->start(); + } + } + + return true; + } + else if (RemoveBasebandSampleSink::match(message)) + { + const auto& msg = (const RemoveBasebandSampleSink&) message; + BasebandSampleSink* sink = msg.getSampleSink(); + unsigned int isource = msg.getIndex(); + + if (isource < m_basebandSampleSinks.size()) + { + if (m_stateRx == State::StRunning) { + sink->stop(); + } + + m_basebandSampleSinks[isource].remove(sink); + } + + return true; + } + else if (AddBasebandSampleSource::match(message)) + { + const auto& msg = (const AddBasebandSampleSource&) message; + BasebandSampleSource *sampleSource = msg.getSampleSource(); + unsigned int isink = msg.getIndex(); + + if (isink < m_basebandSampleSources.size()) + { + m_basebandSampleSources[isink].push_back(sampleSource); + // initialize sample rate and center frequency in the sink: + int sinkStreamSampleRate = m_deviceSampleMIMO->getSinkSampleRate(isink); + quint64 sinkCenterFrequency = m_deviceSampleMIMO->getSinkCenterFrequency(isink); + auto *msgToSource = new DSPSignalNotification(sinkStreamSampleRate, sinkCenterFrequency); + sampleSource->pushMessage(msgToSource); + // start the sink: + if (m_stateTx == State::StRunning) { + sampleSource->start(); + } + } + + return true; + } + else if (RemoveBasebandSampleSource::match(message)) + { + const auto& msg = (const RemoveBasebandSampleSource&) message; + BasebandSampleSource* sampleSource = msg.getSampleSource(); + unsigned int isink = msg.getIndex(); + bool deleting = msg.getDeleting(); + + if (isink < m_basebandSampleSources.size()) + { + if (!deleting) { + sampleSource->stop(); + } + m_basebandSampleSources[isink].remove(sampleSource); + } + + return true; + } + else if (AddMIMOChannel::match(message)) + { + const auto& msg = (const AddMIMOChannel&) message; + MIMOChannel *channel = msg.getChannel(); + m_mimoChannels.push_back(channel); + + for (unsigned int isource = 0; isource < m_deviceSampleMIMO->getNbSourceStreams(); isource++) + { + auto *notif = new DSPMIMOSignalNotification( + m_deviceSampleMIMO->getSourceSampleRate(isource), + m_deviceSampleMIMO->getSourceCenterFrequency(isource), + true, + isource + ); + channel->pushMessage(notif); + } + + for (unsigned int isink = 0; isink < m_deviceSampleMIMO->getNbSinkStreams(); isink++) + { + auto *notif = new DSPMIMOSignalNotification( + m_deviceSampleMIMO->getSinkSampleRate(isink), + m_deviceSampleMIMO->getSinkCenterFrequency(isink), + false, + isink + ); + channel->pushMessage(notif); + } + + if (m_stateRx == State::StRunning) { + channel->startSinks(); + } + + if (m_stateTx == State::StRunning) { + channel->startSources(); + } + + return true; + } + else if (RemoveMIMOChannel::match(message)) + { + const auto& msg = (const RemoveMIMOChannel&) message; + MIMOChannel *channel = msg.getChannel(); + channel->stopSinks(); + channel->stopSources(); + m_mimoChannels.remove(channel); + return true; + } + else if (AddSpectrumSink::match(message)) + { + const auto& msg = (const AddSpectrumSink&) message; + m_spectrumSink = msg.getSampleSink(); + return true; + } + else if (RemoveSpectrumSink::match(message)) + { + const auto& msg = (const RemoveSpectrumSink&) message; + BasebandSampleSink* spectrumSink = msg.getSampleSink(); + spectrumSink->stop(); + m_spectrumSink = nullptr; + emit spectrumSinkRemoved(); + return true; + } + else if (SetSpectrumSinkInput::match(message)) + { + const auto& msg = (const SetSpectrumSinkInput&) message; + bool spectrumInputSourceElseSink = msg.getSourceElseSink(); + unsigned int spectrumInputIndex = msg.getIndex(); + + if ((spectrumInputSourceElseSink != m_spectrumInputSourceElseSink) || (spectrumInputIndex != m_spectrumInputIndex)) + { + if ((!spectrumInputSourceElseSink) + && (spectrumInputIndex < m_deviceSampleMIMO->getNbSinkStreams()) + && m_spectrumSink) // add the source listener + { + auto *notif = new DSPSignalNotification( + m_deviceSampleMIMO->getSinkSampleRate(spectrumInputIndex), + m_deviceSampleMIMO->getSinkCenterFrequency(spectrumInputIndex)); + m_spectrumSink->pushMessage(notif); + } + + if (m_spectrumSink && spectrumInputSourceElseSink && (spectrumInputIndex < m_deviceSampleMIMO->getNbSinkFifos())) + { + auto *notif = new DSPSignalNotification( + m_deviceSampleMIMO->getSourceSampleRate(spectrumInputIndex), + m_deviceSampleMIMO->getSourceCenterFrequency(spectrumInputIndex)); + m_spectrumSink->pushMessage(notif); + } + + m_spectrumInputSourceElseSink = spectrumInputSourceElseSink; + m_spectrumInputIndex = spectrumInputIndex; + } + + return true; + } + + + return false; +} + +void DSPDeviceMIMOEngine::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + qDebug("DSPDeviceMIMOEngine::handleInputMessages: message: %s", message->getIdentifier()); + + if (handleMessage(*message)) { + delete message; + } + } +} + +void DSPDeviceMIMOEngine::configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection, int isource) +{ + qDebug() << "DSPDeviceMIMOEngine::configureCorrections"; + auto* cmd = new ConfigureCorrection(dcOffsetCorrection, iqImbalanceCorrection, isource); + m_inputMessageQueue.push(cmd); +} + +void DSPDeviceMIMOEngine::iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, int isource, bool imbalanceCorrection) +{ + for(SampleVector::iterator it = begin; it < end; it++) + { + m_sourcesCorrections[isource].m_iBeta(it->real()); + m_sourcesCorrections[isource].m_qBeta(it->imag()); + + if (imbalanceCorrection) + { +#if IMBALANCE_INT + // acquisition + int64_t xi = (it->m_real - (int32_t) m_sourcesCorrections[isource].m_iBeta) << 5; + int64_t xq = (it->m_imag - (int32_t) m_sourcesCorrections[isource].m_qBeta) << 5; + + // phase imbalance + m_sourcesCorrections[isource].m_avgII((xi*xi)>>28); // + m_sourcesCorrections[isource].m_avgIQ((xi*xq)>>28); // + + if ((int64_t) m_sourcesCorrections[isource].m_avgII != 0) + { + int64_t phi = (((int64_t) m_sourcesCorrections[isource].m_avgIQ)<<28) / (int64_t) m_sourcesCorrections[isource].m_avgII; + m_sourcesCorrections[isource].m_avgPhi(phi); + } + + int64_t corrPhi = (((int64_t) m_sourcesCorrections[isource].m_avgPhi) * xq) >> 28; //(m_avgPhi.asDouble()/16777216.0) * ((double) xq); + + int64_t yi = xi - corrPhi; + int64_t yq = xq; + + // amplitude I/Q imbalance + m_sourcesCorrections[isource].m_avgII2((yi*yi)>>28); // + m_sourcesCorrections[isource].m_avgQQ2((yq*yq)>>28); // + + if ((int64_t) m_sourcesCorrections[isource].m_avgQQ2 != 0) + { + int64_t a = (((int64_t) m_sourcesCorrections[isource].m_avgII2)<<28) / (int64_t) m_sourcesCorrections[isource].m_avgQQ2; + Fixed fA(Fixed::internal(), a); + Fixed sqrtA = sqrt((Fixed) fA); + m_sourcesCorrections[isource].m_avgAmp(sqrtA.as_internal()); + } + + int64_t zq = (((int64_t) m_sourcesCorrections[isource].m_avgAmp) * yq) >> 28; + + it->m_real = yi >> 5; + it->m_imag = zq >> 5; + +#else + // DC correction and conversion + float xi = (float) (it->m_real - (int32_t) m_sourcesCorrections[isource].m_iBeta) / SDR_RX_SCALEF; + float xq = (float) (it->m_imag - (int32_t) m_sourcesCorrections[isource].m_qBeta) / SDR_RX_SCALEF; + + // phase imbalance + m_sourcesCorrections[isource].m_avgII(xi*xi); // + m_sourcesCorrections[isource].m_avgIQ(xi*xq); // + + + if (m_sourcesCorrections[isource].m_avgII.asDouble() != 0) { + m_sourcesCorrections[isource].m_avgPhi(m_sourcesCorrections[isource].m_avgIQ.asDouble()/m_sourcesCorrections[isource].m_avgII.asDouble()); + } + + const float& yi = xi; // the in phase remains the reference + float yq = xq - (float) m_sourcesCorrections[isource].m_avgPhi.asDouble()*xi; + + // amplitude I/Q imbalance + m_sourcesCorrections[isource].m_avgII2(yi*yi); // + m_sourcesCorrections[isource].m_avgQQ2(yq*yq); // + + if (m_sourcesCorrections[isource].m_avgQQ2.asDouble() != 0) { + m_sourcesCorrections[isource].m_avgAmp(sqrt(m_sourcesCorrections[isource].m_avgII2.asDouble() / m_sourcesCorrections[isource].m_avgQQ2.asDouble())); + } + + // final correction + const float& zi = yi; // the in phase remains the reference + auto zq = (float) (m_sourcesCorrections[isource].m_avgAmp.asDouble() * yq); + + // convert and store + it->m_real = (FixReal) (zi * SDR_RX_SCALEF); + it->m_imag = (FixReal) (zq * SDR_RX_SCALEF); +#endif + } + else + { + // DC correction only + it->m_real -= (int32_t) m_sourcesCorrections[isource].m_iBeta; + it->m_imag -= (int32_t) m_sourcesCorrections[isource].m_qBeta; + } + } +} diff --git a/android/app/src/main/cpp/dsp/dspdevicemimoengine.h b/android/app/src/main/cpp/dsp/dspdevicemimoengine.h new file mode 100644 index 0000000..11b2e77 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspdevicemimoengine.h @@ -0,0 +1,369 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2020, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_ +#define SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_ + +#include + +#include "dsp/dsptypes.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "util/movingaverage.h" +#include "util/incrementalvector.h" +#include "export.h" + +class DeviceSampleMIMO; +class BasebandSampleSink; +class BasebandSampleSource; +class MIMOChannel; + +class SDRBASE_API DSPDeviceMIMOEngine : public QObject { + Q_OBJECT + +public: + class SetSampleMIMO : public Message { + MESSAGE_CLASS_DECLARATION + public: + explicit SetSampleMIMO(DeviceSampleMIMO* sampleMIMO) : Message(), m_sampleMIMO(sampleMIMO) { } + DeviceSampleMIMO* getSampleMIMO() const { return m_sampleMIMO; } + private: + DeviceSampleMIMO* m_sampleMIMO; + }; + + class AddBasebandSampleSource : public Message { + MESSAGE_CLASS_DECLARATION + public: + AddBasebandSampleSource(BasebandSampleSource* sampleSource, unsigned int index) : + Message(), + m_sampleSource(sampleSource), + m_index(index) + { } + BasebandSampleSource* getSampleSource() const { return m_sampleSource; } + unsigned int getIndex() const { return m_index; } + private: + BasebandSampleSource* m_sampleSource; + unsigned int m_index; + }; + + class RemoveBasebandSampleSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + RemoveBasebandSampleSource(BasebandSampleSource* sampleSource, unsigned int index, bool deleting) : + Message(), + m_sampleSource(sampleSource), + m_index(index), + m_deleting(deleting) + { } + BasebandSampleSource* getSampleSource() const { return m_sampleSource; } + unsigned int getIndex() const { return m_index; } + bool getDeleting() const { return m_deleting; } + private: + BasebandSampleSource* m_sampleSource; + unsigned int m_index; + bool m_deleting; + }; + + class AddMIMOChannel : public Message { + MESSAGE_CLASS_DECLARATION + public: + explicit AddMIMOChannel(MIMOChannel* channel) : + Message(), + m_channel(channel) + { } + MIMOChannel* getChannel() const { return m_channel; } + private: + MIMOChannel* m_channel; + }; + + class RemoveMIMOChannel : public Message { + MESSAGE_CLASS_DECLARATION + public: + explicit RemoveMIMOChannel(MIMOChannel* channel) : + Message(), + m_channel(channel) + { } + MIMOChannel* getChannel() const { return m_channel; } + private: + MIMOChannel* m_channel; + }; + + class AddBasebandSampleSink : public Message { + MESSAGE_CLASS_DECLARATION + public: + AddBasebandSampleSink(BasebandSampleSink* sampleSink, unsigned int index) : + Message(), + m_sampleSink(sampleSink), + m_index(index) + { } + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + unsigned int getIndex() const { return m_index; } + private: + BasebandSampleSink* m_sampleSink; + unsigned int m_index; + }; + + class RemoveBasebandSampleSink : public Message { + MESSAGE_CLASS_DECLARATION + public: + RemoveBasebandSampleSink(BasebandSampleSink* sampleSink, unsigned int index) : + Message(), + m_sampleSink(sampleSink), + m_index(index) + { } + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + unsigned int getIndex() const { return m_index; } + private: + BasebandSampleSink* m_sampleSink; + unsigned int m_index; + }; + + class AddSpectrumSink : public Message { + MESSAGE_CLASS_DECLARATION + public: + explicit AddSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { } + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + private: + BasebandSampleSink* m_sampleSink; + }; + + class RemoveSpectrumSink : public Message { + MESSAGE_CLASS_DECLARATION + public: + explicit RemoveSpectrumSink(BasebandSampleSink* sampleSink) : Message(), m_sampleSink(sampleSink) { } + BasebandSampleSink* getSampleSink() const { return m_sampleSink; } + private: + BasebandSampleSink* m_sampleSink; + }; + + class GetErrorMessage : public Message { + MESSAGE_CLASS_DECLARATION + public: + explicit GetErrorMessage(unsigned int subsystemIndex) : + m_subsystemIndex(subsystemIndex) + {} + void setErrorMessage(const QString& text) { m_errorMessage = text; } + int getSubsystemIndex() const { return m_subsystemIndex; } + const QString& getErrorMessage() const { return m_errorMessage; } + private: + int m_subsystemIndex; + QString m_errorMessage; + }; + + class GetMIMODeviceDescription : public Message { + MESSAGE_CLASS_DECLARATION + public: + void setDeviceDescription(const QString& text) { m_deviceDescription = text; } + const QString& getDeviceDescription() const { return m_deviceDescription; } + private: + QString m_deviceDescription; + }; + + class ConfigureCorrection : public Message { + MESSAGE_CLASS_DECLARATION + public: + ConfigureCorrection(bool dcOffsetCorrection, bool iqImbalanceCorrection, unsigned int index) : + Message(), + m_dcOffsetCorrection(dcOffsetCorrection), + m_iqImbalanceCorrection(iqImbalanceCorrection), + m_index(index) + { } + bool getDCOffsetCorrection() const { return m_dcOffsetCorrection; } + bool getIQImbalanceCorrection() const { return m_iqImbalanceCorrection; } + unsigned int getIndex() const { return m_index; } + private: + bool m_dcOffsetCorrection; + bool m_iqImbalanceCorrection; + unsigned int m_index; + }; + + class SetSpectrumSinkInput : public Message { + MESSAGE_CLASS_DECLARATION + public: + SetSpectrumSinkInput(bool sourceElseSink, int index) : + m_sourceElseSink(sourceElseSink), + m_index(index) + { } + bool getSourceElseSink() const { return m_sourceElseSink; } + int getIndex() const { return m_index; } + private: + bool m_sourceElseSink; + int m_index; + }; + + enum class State { + StNotStarted, //!< engine is before initialization + StIdle, //!< engine is idle + StReady, //!< engine is ready to run + StRunning, //!< engine is running + StError //!< engine is in error + }; + + DSPDeviceMIMOEngine(uint32_t uid, QObject* parent = nullptr); + ~DSPDeviceMIMOEngine() override; + + MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; } + + bool initProcess(int subsystemIndex); //!< Initialize process sequence + bool startProcess(int subsystemIndex); //!< Start process sequence + void stopProcess(int subsystemIndex); //!< Stop process sequence + + void setMIMO(DeviceSampleMIMO* mimo); //!< Set the sample MIMO type + DeviceSampleMIMO *getMIMO() { return m_deviceSampleMIMO; } + void setMIMOSequence(int sequence); //!< Set the sample MIMO sequence in type + uint getUID() const { return m_uid; } + + void addChannelSource(BasebandSampleSource* source, int index = 0); //!< Add a channel source + void removeChannelSource(BasebandSampleSource* source, bool deleting, int index = 0); //!< Remove a channel source + void addChannelSink(BasebandSampleSink* sink, int index = 0); //!< Add a channel sink + void removeChannelSink(BasebandSampleSink* sink, int index = 0); //!< Remove a channel sink + void addMIMOChannel(MIMOChannel *channel); //!< Add a MIMO channel + void removeMIMOChannel(MIMOChannel *channel); //!< Remove a MIMO channel + + void addSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink + void removeSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink + void setSpectrumSinkInput(bool sourceElseSink, int index); + + State state(int subsystemIndex) const //!< Return DSP engine current state + { + if (subsystemIndex == 0) { + return m_stateRx; + } else if (subsystemIndex == 1) { + return m_stateTx; + } else { + return State::StNotStarted; + } + } + + QString errorMessage(int subsystemIndex) const; //!< Return the current error message + QString deviceDescription() const; //!< Return the device description + + void configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection, int isource); //!< Configure source DSP corrections + +private: + struct SourceCorrection + { + bool m_dcOffsetCorrection = false; + bool m_iqImbalanceCorrection = false; + double m_iOffset = 0; + double m_qOffset = 0; + int m_iRange = 1 << 16; + int m_qRange = 1 << 16; + int m_imbalance = 65536; + MovingAverageUtil m_iBeta; + MovingAverageUtil m_qBeta; +#if IMBALANCE_INT + // Fixed point DC + IQ corrections + MovingAverageUtil m_avgII; + MovingAverageUtil m_avgIQ; + MovingAverageUtil m_avgPhi; + MovingAverageUtil m_avgII2; + MovingAverageUtil m_avgQQ2; + MovingAverageUtil m_avgAmp; +#else + // Floating point DC + IQ corrections + MovingAverageUtil m_avgII; + MovingAverageUtil m_avgIQ; + MovingAverageUtil m_avgII2; + MovingAverageUtil m_avgQQ2; + MovingAverageUtil m_avgPhi; + MovingAverageUtil m_avgAmp; +#endif + SourceCorrection() + { + m_iBeta.reset(); + m_qBeta.reset(); + m_avgAmp.reset(); + m_avgII.reset(); + m_avgII2.reset(); + m_avgIQ.reset(); + m_avgPhi.reset(); + m_avgQQ2.reset(); + m_iBeta.reset(); + m_qBeta.reset(); + } + }; + + uint32_t m_uid; //!< unique ID + State m_stateRx; + State m_stateTx; + + QString m_errorMessageRx; + QString m_errorMessageTx; + QString m_deviceDescription; + + DeviceSampleMIMO* m_deviceSampleMIMO; + int m_sampleMIMOSequence; + + MessageQueue m_inputMessageQueue; //; + std::vector m_basebandSampleSinks; //!< ancillary sample sinks on main thread (per input stream) + std::map m_rxRealElseComplex; //!< map of real else complex indicators for device sources (by input stream) + using BasebandSampleSources = std::list; + std::vector m_basebandSampleSources; //!< channel sample sources (per output stream) + std::map m_txRealElseComplex; //!< map of real else complex indicators for device sinks (by input stream) + std::vector> m_sourceSampleBuffers; + std::vector> m_sourceZeroBuffers; + unsigned int m_sumIndex; //!< channel index when summing channels + + using MIMOChannels = std::list; + MIMOChannels m_mimoChannels; //!< MIMO channels + + std::vector m_sourcesCorrections; + + BasebandSampleSink *m_spectrumSink; //!< The spectrum sink + bool m_spectrumInputSourceElseSink; //!< Source else sink stream to be used as spectrum sink input + unsigned int m_spectrumInputIndex; //!< Index of the stream to be used as spectrum sink input + + void workSampleSinkFifos(); //!< transfer samples of all sink streams (sync mode) + void workSampleSinkFifo(unsigned int streamIndex); //!< transfer samples of one sink stream (async mode) + void workSamplesSink(const SampleVector::const_iterator& vbegin, const SampleVector::const_iterator& vend, unsigned int streamIndex); + void workSampleSourceFifos(); //!< transfer samples of all source streams (sync mode) + void workSampleSourceFifo(unsigned int streamIndex); //!< transfer samples of one source stream (async mode) + void workSamplesSource(SampleVector& data, unsigned int iBegin, unsigned int iEnd, unsigned int streamIndex); + + State gotoIdle(int subsystemIndex); //!< Go to the idle state + State gotoInit(int subsystemIndex); //!< Go to the acquisition init state from idle + State gotoRunning(int subsystemIndex); //!< Go to the running state from ready state + State gotoError(int subsystemIndex, const QString& errorMsg); //!< Go to an error state + void setStateRx(State state); + void setStateTx(State state); + + void handleSetMIMO(DeviceSampleMIMO* mimo); //!< Manage MIMO device setting + void iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, int isource, bool imbalanceCorrection); + bool handleMessage(const Message& cmd); + +private slots: + void handleDataRxSync(); //!< Handle data when Rx samples have to be processed synchronously + void handleDataRxAsync(int streamIndex); //!< Handle data when Rx samples have to be processed asynchronously + void handleDataTxSync(); //!< Handle data when Tx samples have to be processed synchronously + void handleDataTxAsync(int streamIndex); //!< Handle data when Tx samples have to be processed asynchronously + void handleInputMessages(); //!< Handle input message queue + +signals: + void stateChanged(); + + void acquisitionStopped(); + void sampleSet(); + void generationStopped(); + void spectrumSinkRemoved(); +}; + +#endif // SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_ diff --git a/android/app/src/main/cpp/dsp/dspdevicesinkengine.cpp b/android/app/src/main/cpp/dsp/dspdevicesinkengine.cpp new file mode 100644 index 0000000..3f1b8aa --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspdevicesinkengine.cpp @@ -0,0 +1,526 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "dspdevicesinkengine.h" + +#include "dsp/basebandsamplesource.h" +#include "dsp/basebandsamplesink.h" +#include "dsp/devicesamplesink.h" +#include "dsp/dspcommands.h" + +DSPDeviceSinkEngine::DSPDeviceSinkEngine(uint32_t uid, QObject* parent) : + QObject(parent), + m_uid(uid), + m_state(State::StNotStarted), + m_deviceSampleSink(nullptr), + m_sampleSinkSequence(0), + m_basebandSampleSources(), + m_spectrumSink(nullptr), + m_sampleRate(0), + m_centerFrequency(0), + m_realElseComplex(false) +{ + setState(State::StIdle); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +DSPDeviceSinkEngine::~DSPDeviceSinkEngine() +{ + qDebug("DSPDeviceSinkEngine::~DSPDeviceSinkEngine"); +} + +void DSPDeviceSinkEngine::setState(State state) +{ + if (m_state != state) + { + m_state = state; + emit stateChanged(); + } +} + +bool DSPDeviceSinkEngine::initGeneration() +{ + qDebug() << "DSPDeviceSinkEngine::initGeneration"; + auto *cmd = new DSPGenerationInit(); + getInputMessageQueue()->push(cmd); + return true; +} + +bool DSPDeviceSinkEngine::startGeneration() +{ + qDebug() << "DSPDeviceSinkEngine::startGeneration"; + auto *cmd = new DSPGenerationStart(); + getInputMessageQueue()->push(cmd); + return true; +} + +void DSPDeviceSinkEngine::stopGeneration() +{ + qDebug() << "DSPDeviceSinkEngine::stopGeneration"; + auto *cmd = new DSPGenerationStop(); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceSinkEngine::setSink(DeviceSampleSink* sink) +{ + qDebug() << "DSPDeviceSinkEngine::setSink"; + m_deviceSampleSink = sink; + auto *cmd = new DSPSetSink(sink); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceSinkEngine::setSinkSequence(int sequence) +{ + qDebug("DSPDeviceSinkEngine::setSinkSequence: seq: %d", sequence); + m_sampleSinkSequence = sequence; +} + +void DSPDeviceSinkEngine::addChannelSource(BasebandSampleSource* source) +{ + qDebug() << "DSPDeviceSinkEngine::addChannelSource: " << source->getSourceName().toStdString().c_str(); + auto *cmd = new DSPAddBasebandSampleSource(source); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceSinkEngine::removeChannelSource(BasebandSampleSource* source, bool deleting) +{ + qDebug() << "DSPDeviceSinkEngine::removeChannelSource: " << source->getSourceName().toStdString().c_str(); + auto *cmd = new DSPRemoveBasebandSampleSource(source, deleting); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceSinkEngine::addSpectrumSink(BasebandSampleSink* spectrumSink) +{ + qDebug() << "DSPDeviceSinkEngine::addSpectrumSink: " << spectrumSink->getSinkName().toStdString().c_str(); + m_spectrumSink = spectrumSink; +} + +void DSPDeviceSinkEngine::removeSpectrumSink(BasebandSampleSink* spectrumSink) +{ + qDebug() << "DSPDeviceSinkEngine::removeSpectrumSink: " << spectrumSink->getSinkName().toStdString().c_str(); + auto *cmd = new DSPRemoveSpectrumSink(spectrumSink); + getInputMessageQueue()->push(cmd); +} + +QString DSPDeviceSinkEngine::errorMessage() const +{ + qDebug() << "DSPDeviceSinkEngine::errorMessage"; + return m_errorMessage; +} + +QString DSPDeviceSinkEngine::sinkDeviceDescription() const +{ + qDebug() << "DSPDeviceSinkEngine::sinkDeviceDescription"; + return m_deviceDescription; +} + +void DSPDeviceSinkEngine::workSampleFifo() +{ + SampleSourceFifo *sourceFifo = m_deviceSampleSink->getSampleFifo(); + + if (!sourceFifo) { + return; + } + + SampleVector& data = sourceFifo->getData(); + unsigned int iPart1Begin; + unsigned int iPart1End; + unsigned int iPart2Begin; + unsigned int iPart2End; + unsigned int remainder = sourceFifo->remainder(); + + while ((remainder > 0) && (m_inputMessageQueue.size() == 0)) + { + sourceFifo->write(remainder, iPart1Begin, iPart1End, iPart2Begin, iPart2End); + + if (iPart1Begin != iPart1End) { + workSamples(data, iPart1Begin, iPart1End); + } + + if (iPart2Begin != iPart2End) { + workSamples(data, iPart2Begin, iPart2End); + } + + remainder = sourceFifo->remainder(); + } +} + +void DSPDeviceSinkEngine::workSamples(SampleVector& data, unsigned int iBegin, unsigned int iEnd) +{ + unsigned int nbSamples = iEnd - iBegin; + SampleVector::iterator begin = data.begin() + iBegin; + + if (m_basebandSampleSources.size() == 0) + { + m_sourceZeroBuffer.allocate(nbSamples, Sample{0,0}); + std::copy( + m_sourceZeroBuffer.m_vector.begin(), + m_sourceZeroBuffer.m_vector.begin() + nbSamples, + data.begin() + iBegin + ); + } + else if (m_basebandSampleSources.size() == 1) + { + BasebandSampleSource *source = m_basebandSampleSources.front(); + source->pull(begin, nbSamples); + } + else + { + m_sourceSampleBuffer.allocate(nbSamples); + auto sBegin = m_sourceSampleBuffer.m_vector.begin(); + BasebandSampleSources::const_iterator srcIt = m_basebandSampleSources.begin(); + BasebandSampleSource *source = *srcIt; + source->pull(begin, nbSamples); + srcIt++; + m_sumIndex = 1; + + for (; srcIt != m_basebandSampleSources.end(); ++srcIt, m_sumIndex++) + { + source = *srcIt; + source->pull(sBegin, nbSamples); + std::transform( + sBegin, + sBegin + nbSamples, + data.begin() + iBegin, + data.begin() + iBegin, + [this](const Sample& a, const Sample& b) -> Sample { + FixReal den = m_sumIndex + 1; // at each stage scale sum by n/n+1 and input by 1/n+1 + FixReal nom = m_sumIndex; // so that final sum is scaled by N (number of channels) + FixReal x = a.real()/den + nom*(b.real()/den); + FixReal y = a.imag()/den + nom*(b.imag()/den); + return Sample{x, y}; + } + ); + } + } + + // possibly feed data to spectrum sink + if (m_spectrumSink) { + m_spectrumSink->feed(data.begin() + iBegin, data.begin() + iEnd, m_realElseComplex); + } +} + +// notStarted -> idle -> init -> running -+ +// ^ | +// +-----------------------+ + +DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoIdle() +{ + qDebug() << "DSPDeviceSinkEngine::gotoIdle"; + + switch(m_state) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + case State::StError: + return State::StIdle; + + case State::StReady: + case State::StRunning: + break; + } + + if (!m_deviceSampleSink) { + return State::StIdle; + } + + // stop everything + m_deviceSampleSink->stop(); + + for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++) + { + qDebug() << "DSPDeviceSinkEngine::gotoIdle: stopping " << (*it)->getSourceName().toStdString().c_str(); + (*it)->stop(); + } + + m_deviceDescription.clear(); + m_sampleRate = 0; + + return State::StIdle; +} + +DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoInit() +{ + switch(m_state) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + return State::StReady; + + case State::StIdle: + case State::StError: + break; + } + + if (!m_deviceSampleSink) { + return gotoError("DSPDeviceSinkEngine::gotoInit: No sample source configured"); + } + + // init: pass sample rate and center frequency to all sample rate and/or center frequency dependent sinks and wait for completion + + m_deviceDescription = m_deviceSampleSink->getDeviceDescription(); + m_centerFrequency = m_deviceSampleSink->getCenterFrequency(); + m_sampleRate = m_deviceSampleSink->getSampleRate(); + + qDebug() << "DSPDeviceSinkEngine::gotoInit: " + << " m_deviceDescription: " << m_deviceDescription.toStdString().c_str() + << " sampleRate: " << m_sampleRate + << " centerFrequency: " << m_centerFrequency; + + DSPSignalNotification notif(m_sampleRate, m_centerFrequency); + + for (BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); ++it) + { + qDebug() << "DSPDeviceSinkEngine::gotoInit: initializing " << (*it)->getSourceName().toStdString().c_str(); + (*it)->pushMessage(new DSPSignalNotification(notif)); + } + + if (m_spectrumSink) { + m_spectrumSink->pushMessage(new DSPSignalNotification(notif)); + } + + // pass data to listeners + if (m_deviceSampleSink->getMessageQueueToGUI()) + { + auto* rep = new DSPSignalNotification(notif); // make a copy for the output queue + m_deviceSampleSink->getMessageQueueToGUI()->push(rep); + } + + return State::StReady; +} + +DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoRunning() +{ + qDebug() << "DSPDeviceSinkEngine::gotoRunning"; + + switch(m_state) + { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + return State::StIdle; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + case State::StError: + break; + } + + if (!m_deviceSampleSink) { + return gotoError("DSPDeviceSinkEngine::gotoRunning: No sample source configured"); + } + + qDebug() << "DSPDeviceSinkEngine::gotoRunning: " << m_deviceDescription.toStdString().c_str() << " started"; + + // Start everything + + if (!m_deviceSampleSink->start()) { + return gotoError("DSPDeviceSinkEngine::gotoRunning: Could not start sample sink"); + } + + for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++) + { + qDebug() << "DSPDeviceSinkEngine::gotoRunning: starting " << (*it)->getSourceName().toStdString().c_str(); + (*it)->start(); + } + + if (m_spectrumSink) + { + m_spectrumSink->start(); + } + + qDebug() << "DSPDeviceSinkEngine::gotoRunning: input message queue pending: " << m_inputMessageQueue.size(); + + return State::StRunning; +} + +DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoError(const QString& errorMessage) +{ + qDebug() << "DSPDeviceSinkEngine::gotoError"; + + m_errorMessage = errorMessage; + m_deviceDescription.clear(); + setState(State::StError); + return State::StError; +} + +void DSPDeviceSinkEngine::handleSetSink(const DeviceSampleSink*) +{ + if (!m_deviceSampleSink) { // Early leave + return; + } + + qDebug("DSPDeviceSinkEngine::handleSetSink: set %s", qPrintable(m_deviceSampleSink->getDeviceDescription())); + + QObject::connect( + m_deviceSampleSink->getSampleFifo(), + &SampleSourceFifo::dataRead, + this, + &DSPDeviceSinkEngine::handleData, + Qt::QueuedConnection + ); +} + +void DSPDeviceSinkEngine::handleData() +{ + if (m_state == State::StRunning) { + workSampleFifo(); + } +} + +bool DSPDeviceSinkEngine::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + auto& notif = (const DSPSignalNotification&) message; + + // update DSP values + + m_sampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + m_realElseComplex = notif.getRealElseComplex(); + + qDebug() << "DSPDeviceSinkEngine::handleInputMessages: DSPSignalNotification:" + << " m_sampleRate: " << m_sampleRate + << " m_centerFrequency: " << m_centerFrequency + << " m_realElseComplex" << m_realElseComplex; + + // forward source changes to sources with immediate execution + + for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++) + { + auto *rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "DSPDeviceSinkEngine::handleInputMessages: forward message to " << (*it)->getSourceName().toStdString().c_str(); + (*it)->pushMessage(rep); + } + + // forward changes to listeners on DSP output queue + if (m_deviceSampleSink) + { + MessageQueue *guiMessageQueue = m_deviceSampleSink->getMessageQueueToGUI(); + qDebug("DSPDeviceSinkEngine::handleInputMessages: DSPSignalNotification: guiMessageQueue: %p", guiMessageQueue); + + if (guiMessageQueue) + { + auto *rep = new DSPSignalNotification(notif); // make a copy for the output queue + guiMessageQueue->push(rep); + } + } + + return true; + } + // From synchronous messages + if (DSPGenerationInit::match(message)) + { + setState(gotoIdle()); + + if(m_state == State::StIdle) { + setState(gotoInit()); // State goes ready if init is performed + } + + return true; + } + else if (DSPGenerationStart::match(message)) + { + if(m_state == State::StReady) { + setState(gotoRunning()); + } + + return true; + } + else if (DSPGenerationStop::match(message)) + { + setState(gotoIdle()); + emit generationStopped(); + return true; + } + else if (DSPSetSink::match(message)) + { + const auto& cmd = (const DSPSetSink&) message; + handleSetSink(cmd.getSampleSink()); + emit sampleSet(); + return true; + } + else if (DSPRemoveSpectrumSink::match(message)) + { + auto& cmd = (const DSPRemoveSpectrumSink&) message; + BasebandSampleSink* spectrumSink = cmd.getSampleSink(); + + if(m_state == State::StRunning) { + spectrumSink->stop(); + } + + m_spectrumSink = nullptr; + emit spectrumSinkRemoved(); + return true; + } + else if (DSPAddBasebandSampleSource::match(message)) + { + auto& cmd = (const DSPAddBasebandSampleSource&) message; + BasebandSampleSource* source = cmd.getSampleSource(); + m_basebandSampleSources.push_back(source); + auto *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency); + source->pushMessage(notif); + + if (m_state == State::StRunning) { + source->start(); + } + + return true; + } + else if (DSPRemoveBasebandSampleSource::match(message)) + { + auto& cmd = (const DSPRemoveBasebandSampleSource&) message; + BasebandSampleSource* source = cmd.getSampleSource(); + bool deleting = cmd.getDeleting(); + + if (!deleting && (m_state == State::StRunning)) { + source->stop(); + } + + m_basebandSampleSources.remove(source); + return true; + } + + return false; +} + +void DSPDeviceSinkEngine::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + qDebug("DSPDeviceSinkEngine::handleInputMessages: message: %s", message->getIdentifier()); + if (handleMessage(*message)) { + delete message; + } + } +} diff --git a/android/app/src/main/cpp/dsp/dspdevicesinkengine.h b/android/app/src/main/cpp/dsp/dspdevicesinkengine.h new file mode 100644 index 0000000..d8c100b --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspdevicesinkengine.h @@ -0,0 +1,134 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DSPDEVICESINKENGINE_H_ +#define SDRBASE_DSP_DSPDEVICESINKENGINE_H_ + +#include +#include +#include +#include + +#include +#include +#include + +#include "dsp/dsptypes.h" +#include "util/messagequeue.h" +#include "util/incrementalvector.h" +#include "export.h" + +class DeviceSampleSink; +class BasebandSampleSource; +class BasebandSampleSink; + +class SDRBASE_API DSPDeviceSinkEngine : public QObject { + Q_OBJECT + +public: + enum class State { + StNotStarted, //!< engine is before initialization + StIdle, //!< engine is idle + StReady, //!< engine is ready to run + StRunning, //!< engine is running + StError //!< engine is in error + }; + + DSPDeviceSinkEngine(uint32_t uid, QObject* parent = nullptr); + ~DSPDeviceSinkEngine() final; + + uint32_t getUID() const { return m_uid; } + + MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; } + + bool initGeneration(); //!< Initialize generation sequence + bool startGeneration(); //!< Start generation sequence + void stopGeneration(); //!< Stop generation sequence + + void setSink(DeviceSampleSink* sink); //!< Set the sample sink type + DeviceSampleSink *getSink() { return m_deviceSampleSink; } + void setSinkSequence(int sequence); //!< Set the sample sink sequence in type + + void addChannelSource(BasebandSampleSource* source); //!< Add a baseband sample source + void removeChannelSource(BasebandSampleSource* source, bool deleting); //!< Remove a baseband sample source + + void addSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink + void removeSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink + + State state() const { return m_state; } //!< Return DSP engine current state + + QString errorMessage() const; //!< Return the current error message + QString sinkDeviceDescription() const; //!< Return the sink device description + +private: + uint32_t m_uid; //!< unique ID + + MessageQueue m_inputMessageQueue; //; + BasebandSampleSources m_basebandSampleSources; //!< baseband sample sources within main thread (usually file input) + + BasebandSampleSink *m_spectrumSink; + IncrementalVector m_sourceSampleBuffer; + IncrementalVector m_sourceZeroBuffer; + + uint32_t m_sampleRate; + quint64 m_centerFrequency; + bool m_realElseComplex; + unsigned int m_sumIndex; //!< channel index when summing channels + + void workSampleFifo(); //!< transfer samples from baseband sources to sink if in running state + void workSamples(SampleVector& data, unsigned int iBegin, unsigned int iEnd); + + State gotoIdle(); //!< Go to the idle state + State gotoInit(); //!< Go to the acquisition init state from idle + State gotoRunning(); //!< Go to the running state from ready state + State gotoError(const QString& errorMsg); //!< Go to an error state + void setState(State state); + + void handleSetSink(const DeviceSampleSink* sink); //!< Manage sink setting + bool handleMessage(const Message& cmd); + +private slots: + void handleData(); //!< Handle data when samples have to be written to the sample FIFO + void handleInputMessages(); //!< Handle input message queue + +signals: + void stateChanged(); + + void generationStopped(); + void sampleSet(); + void spectrumSinkRemoved(); +}; + + + + + +#endif /* SDRBASE_DSP_DSPDEVICESINKENGINE_H_ */ diff --git a/android/app/src/main/cpp/dsp/dspdevicesourceengine.cpp b/android/app/src/main/cpp/dsp/dspdevicesourceengine.cpp new file mode 100644 index 0000000..57c4274 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspdevicesourceengine.cpp @@ -0,0 +1,659 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2019, 2022-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dspdevicesourceengine.h" + +#include +#include +#include +#include +#include "dsp/dspcommands.h" +#include "samplesinkfifo.h" + +DSPDeviceSourceEngine::DSPDeviceSourceEngine(uint uid, QObject* parent) : + QObject(parent), + m_uid(uid), + m_state(State::StNotStarted), + m_deviceSampleSource(nullptr), + m_sampleSourceSequence(0), + m_basebandSampleSinks(), + m_sampleRate(0), + m_centerFrequency(0), + m_realElseComplex(false), + m_dcOffsetCorrection(false), + m_iqImbalanceCorrection(false), + m_iOffset(0), + m_qOffset(0), + m_iRange(1 << 16), + m_qRange(1 << 16), + m_imbalance(65536) +{ + setState(State::StIdle); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +DSPDeviceSourceEngine::~DSPDeviceSourceEngine() +{ + qDebug("DSPDeviceSourceEngine::~DSPDeviceSourceEngine"); +} + +void DSPDeviceSourceEngine::setState(State state) +{ + if (m_state != state) + { + m_state = state; + emit stateChanged(); + } +} + +bool DSPDeviceSourceEngine::initAcquisition() const +{ + qDebug("DSPDeviceSourceEngine::initAcquisition (dummy)"); + return true; +} + +bool DSPDeviceSourceEngine::startAcquisition() +{ + qDebug("DSPDeviceSourceEngine::startAcquisition"); + auto *cmd = new DSPAcquisitionStart(); + getInputMessageQueue()->push(cmd); + return true; +} + +void DSPDeviceSourceEngine::stopAcquistion() +{ + qDebug("DSPDeviceSourceEngine::stopAcquistion"); + auto *cmd = new DSPAcquisitionStop(); + getInputMessageQueue()->push(cmd); + + if (m_dcOffsetCorrection) { + qDebug("DC offset:%f,%f", m_iOffset, m_qOffset); + } +} + +void DSPDeviceSourceEngine::setSource(DeviceSampleSource* source) +{ + qDebug("DSPDeviceSourceEngine::setSource"); + auto *cmd = new DSPSetSource(source); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceSourceEngine::setSourceSequence(int sequence) +{ + qDebug("DSPDeviceSourceEngine::setSourceSequence: seq: %d", sequence); + m_sampleSourceSequence = sequence; +} + +void DSPDeviceSourceEngine::addSink(BasebandSampleSink* sink) +{ + qDebug() << "DSPDeviceSourceEngine::addSink: " << sink->getSinkName().toStdString().c_str(); + auto *cmd = new DSPAddBasebandSampleSink(sink); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceSourceEngine::removeSink(BasebandSampleSink* sink, bool deleting) +{ + qDebug() << "DSPDeviceSourceEngine::removeSink: " << sink->getSinkName().toStdString().c_str(); + auto *cmd = new DSPRemoveBasebandSampleSink(sink, deleting); + getInputMessageQueue()->push(cmd); +} + +void DSPDeviceSourceEngine::configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection) +{ + qDebug("DSPDeviceSourceEngine::configureCorrections"); + auto *cmd = new DSPConfigureCorrection(dcOffsetCorrection, iqImbalanceCorrection); + getInputMessageQueue()->push(cmd); +} + +QString DSPDeviceSourceEngine::errorMessage() const +{ + qDebug("DSPDeviceSourceEngine::errorMessage"); + return m_errorMessage; +} + +QString DSPDeviceSourceEngine::sourceDeviceDescription() const +{ + qDebug("DSPDeviceSourceEngine::sourceDeviceDescription"); + return m_deviceDescription; +} + +void DSPDeviceSourceEngine::iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, bool imbalanceCorrection) +{ + for(SampleVector::iterator it = begin; it < end; it++) + { + m_iBeta(it->real()); + m_qBeta(it->imag()); + + if (imbalanceCorrection) + { +#if IMBALANCE_INT + // acquisition + int64_t xi = (it->m_real - (int32_t) m_iBeta) << 5; + int64_t xq = (it->m_imag - (int32_t) m_qBeta) << 5; + + // phase imbalance + m_avgII((xi*xi)>>28); // + m_avgIQ((xi*xq)>>28); // + + if ((int64_t) m_avgII != 0) + { + int64_t phi = (((int64_t) m_avgIQ)<<28) / (int64_t) m_avgII; + m_avgPhi(phi); + } + + int64_t corrPhi = (((int64_t) m_avgPhi) * xq) >> 28; //(m_avgPhi.asDouble()/16777216.0) * ((double) xq); + + int64_t yi = xi - corrPhi; + int64_t yq = xq; + + // amplitude I/Q imbalance + m_avgII2((yi*yi)>>28); // + m_avgQQ2((yq*yq)>>28); // + + if ((int64_t) m_avgQQ2 != 0) + { + int64_t a = (((int64_t) m_avgII2)<<28) / (int64_t) m_avgQQ2; + Fixed fA(Fixed::internal(), a); + Fixed sqrtA = sqrt((Fixed) fA); + m_avgAmp(sqrtA.as_internal()); + } + + int64_t zq = (((int64_t) m_avgAmp) * yq) >> 28; + + it->m_real = yi >> 5; + it->m_imag = zq >> 5; + +#else + // DC correction and conversion + float xi = (float) (it->m_real - (int32_t) m_iBeta) / SDR_RX_SCALEF; + float xq = (float) (it->m_imag - (int32_t) m_qBeta) / SDR_RX_SCALEF; + + // phase imbalance + m_avgII(xi*xi); // + m_avgIQ(xi*xq); // + + + if (m_avgII.asDouble() != 0) { + m_avgPhi(m_avgIQ.asDouble()/m_avgII.asDouble()); + } + + const float& yi = xi; // the in phase remains the reference + float yq = xq - (float) m_avgPhi.asDouble()*xi; + + // amplitude I/Q imbalance + m_avgII2(yi*yi); // + m_avgQQ2(yq*yq); // + + if (m_avgQQ2.asDouble() != 0) { + m_avgAmp(sqrt(m_avgII2.asDouble() / m_avgQQ2.asDouble())); + } + + // final correction + const float& zi = yi; // the in phase remains the reference + auto zq = (float) (m_avgAmp.asDouble() * yq); + + // convert and store + it->m_real = (FixReal) (zi * SDR_RX_SCALEF); + it->m_imag = (FixReal) (zq * SDR_RX_SCALEF); +#endif + } + else + { + // DC correction only + it->m_real -= (int32_t) m_iBeta; + it->m_imag -= (int32_t) m_qBeta; + } + } +} + +void DSPDeviceSourceEngine::dcOffset(SampleVector::iterator begin, SampleVector::iterator end) +{ + // sum and correct in one pass + for(SampleVector::iterator it = begin; it < end; it++) + { + m_iBeta(it->real()); + m_qBeta(it->imag()); + it->m_real -= (int32_t) m_iBeta; + it->m_imag -= (int32_t) m_qBeta; + } +} + +void DSPDeviceSourceEngine::imbalance(SampleVector::iterator begin, SampleVector::iterator end) +{ + int iMin = 0; + int iMax = 0; + int qMin = 0; + int qMax = 0; + + // find value ranges for both I and Q + // both intervals should be same same size (for a perfect circle) + for (SampleVector::iterator it = begin; it < end; it++) + { + if (it != begin) + { + if (it->real() < iMin) { + iMin = it->real(); + } else if (it->real() > iMax) { + iMax = it->real(); + } + + if (it->imag() < qMin) { + qMin = it->imag(); + } else if (it->imag() > qMax) { + qMax = it->imag(); + } + } + else + { + iMin = it->real(); + iMax = it->real(); + qMin = it->imag(); + qMax = it->imag(); + } + } + + // sliding average (el cheapo again) + m_iRange = (m_iRange * 15 + (iMax - iMin)) >> 4; + m_qRange = (m_qRange * 15 + (qMax - qMin)) >> 4; + + // calculate imbalance on 32 bit full scale + if(m_qRange != 0) { + m_imbalance = ((uint)m_iRange << (32-SDR_RX_SAMP_SZ)) / (uint)m_qRange; + } + + // correct imbalance and convert back to sample size + for(SampleVector::iterator it = begin; it < end; it++) { + it->m_imag = (it->m_imag * m_imbalance) >> (32-SDR_RX_SAMP_SZ); + } +} + +void DSPDeviceSourceEngine::work() +{ + SampleSinkFifo* sampleFifo = m_deviceSampleSource->getSampleFifo(); + std::size_t samplesDone = 0; + bool positiveOnly = m_realElseComplex; + + while ((sampleFifo->fill() > 0) && (m_inputMessageQueue.size() == 0) && (samplesDone < m_sampleRate)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = sampleFifo->readBegin(sampleFifo->fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) + { + // correct stuff + if (m_dcOffsetCorrection) { + iqCorrections(part1begin, part1end, m_iqImbalanceCorrection); + } + + // feed data to direct sinks + for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); ++it) { + (*it)->feed(part1begin, part1end, positiveOnly); + } + + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) + { + // correct stuff + if (m_dcOffsetCorrection) { + iqCorrections(part2begin, part2end, m_iqImbalanceCorrection); + } + + // feed data to direct sinks + for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++) { + (*it)->feed(part2begin, part2end, positiveOnly); + } + + } + + // adjust FIFO pointers + sampleFifo->readCommit((unsigned int) count); + samplesDone += count; + } +} + +// notStarted -> idle -> init -> running -+ +// ^ | +// +-----------------------+ + +DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoIdle() +{ + qDebug("DSPDeviceSourceEngine::gotoIdle"); + + switch(m_state) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + case State::StError: + return State::StIdle; + + case State::StReady: + case State::StRunning: + break; + } + + if (!m_deviceSampleSource) { + return State::StIdle; + } + + // stop everything + m_deviceSampleSource->stop(); + + for(BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++) + { + (*it)->stop(); + } + + m_deviceDescription.clear(); + m_sampleRate = 0; + + return State::StIdle; +} + +DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoInit() +{ + switch(m_state) { + case State::StNotStarted: + return State::StNotStarted; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + return State::StReady; + + case State::StIdle: + case State::StError: + break; + } + + if (!m_deviceSampleSource) { + return gotoError("No sample source configured"); + } + + // init: pass sample rate and center frequency to all sample rate and/or center frequency dependent sinks and wait for completion + + m_iOffset = 0; + m_qOffset = 0; + m_iRange = 1 << 16; + m_qRange = 1 << 16; + + m_deviceDescription = m_deviceSampleSource->getDeviceDescription(); + m_centerFrequency = m_deviceSampleSource->getCenterFrequency(); + m_sampleRate = m_deviceSampleSource->getSampleRate(); + + qDebug() << "DSPDeviceSourceEngine::gotoInit: " + << " m_deviceDescription: " << m_deviceDescription.toStdString().c_str() + << " sampleRate: " << m_sampleRate + << " centerFrequency: " << m_centerFrequency; + + + for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); ++it) + { + auto *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency); + qDebug() << "DSPDeviceSourceEngine::gotoInit: initializing " << (*it)->getSinkName().toStdString().c_str(); + (*it)->pushMessage(notif); + } + + // pass data to listeners + if (m_deviceSampleSource->getMessageQueueToGUI()) + { + auto *rep = new DSPSignalNotification(m_sampleRate, m_centerFrequency); + m_deviceSampleSource->getMessageQueueToGUI()->push(rep); + } + + return State::StReady; +} + +DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoRunning() +{ + qDebug("DSPDeviceSourceEngine::gotoRunning"); + + switch(m_state) + { + case State::StNotStarted: + return State::StNotStarted; + + case State::StIdle: + return State::StIdle; + + case State::StRunning: + return State::StRunning; + + case State::StReady: + case State::StError: + break; + } + + if (!m_deviceSampleSource) { + return gotoError("DSPDeviceSourceEngine::gotoRunning: No sample source configured"); + } + + qDebug() << "DSPDeviceSourceEngine::gotoRunning: " << m_deviceDescription.toStdString().c_str() << " started"; + + // Start everything + + if (!m_deviceSampleSource->start()) { + return gotoError("Could not start sample source"); + } + + for(BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++) + { + qDebug() << "DSPDeviceSourceEngine::gotoRunning: starting " << (*it)->getSinkName().toStdString().c_str(); + (*it)->start(); + } + + qDebug() << "DSPDeviceSourceEngine::gotoRunning:input message queue pending: " << m_inputMessageQueue.size(); + + return State::StRunning; +} + +DSPDeviceSourceEngine::State DSPDeviceSourceEngine::gotoError(const QString& errorMessage) +{ + qDebug() << "DSPDeviceSourceEngine::gotoError: " << errorMessage; + + m_errorMessage = errorMessage; + m_deviceDescription.clear(); + setState(State::StError); + return State::StError; +} + +void DSPDeviceSourceEngine::handleSetSource(DeviceSampleSource* source) +{ + gotoIdle(); + + m_deviceSampleSource = source; + + if (m_deviceSampleSource) + { + qDebug("DSPDeviceSourceEngine::handleSetSource: set %s", qPrintable(source->getDeviceDescription())); + connect(m_deviceSampleSource->getSampleFifo(), SIGNAL(dataReady()), this, SLOT(handleData()), Qt::QueuedConnection); + } + else + { + qDebug("DSPDeviceSourceEngine::handleSetSource: set none"); + } +} + +void DSPDeviceSourceEngine::handleData() +{ + if(m_state == State::StRunning) + { + work(); + } +} + +bool DSPDeviceSourceEngine::handleMessage(const Message& message) +{ + if (DSPConfigureCorrection::match(message)) + { + auto& conf = (const DSPConfigureCorrection&) message; + m_iqImbalanceCorrection = conf.getIQImbalanceCorrection(); + + if (m_dcOffsetCorrection != conf.getDCOffsetCorrection()) + { + m_dcOffsetCorrection = conf.getDCOffsetCorrection(); + m_iOffset = 0; + m_qOffset = 0; + } + + if (m_iqImbalanceCorrection != conf.getIQImbalanceCorrection()) + { + m_iqImbalanceCorrection = conf.getIQImbalanceCorrection(); + m_iRange = 1 << 16; + m_qRange = 1 << 16; + m_imbalance = 65536; + } + + m_avgAmp.reset(); + m_avgII.reset(); + m_avgII2.reset(); + m_avgIQ.reset(); + m_avgPhi.reset(); + m_avgQQ2.reset(); + m_iBeta.reset(); + m_qBeta.reset(); + + return true; + } + else if (DSPSignalNotification::match(message)) + { + auto& notif = (const DSPSignalNotification&) message; + + // update DSP values + + m_sampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + m_realElseComplex = notif.getRealElseComplex(); + + qDebug() << "DSPDeviceSourceEngine::handleInputMessages: DSPSignalNotification:" + << " m_sampleRate: " << m_sampleRate + << " m_centerFrequency: " << m_centerFrequency; + + // forward source changes to channel sinks with immediate execution (no queuing) + + for(BasebandSampleSinks::const_iterator it = m_basebandSampleSinks.begin(); it != m_basebandSampleSinks.end(); it++) + { + auto* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "DSPDeviceSourceEngine::handleInputMessages: forward message to " << (*it)->getSinkName().toStdString().c_str(); + (*it)->pushMessage(rep); + } + + // forward changes to source GUI input queue + if (m_deviceSampleSource) + { + MessageQueue *guiMessageQueue = m_deviceSampleSource->getMessageQueueToGUI(); + qDebug("DSPDeviceSourceEngine::handleInputMessages: DSPSignalNotification: guiMessageQueue: %p", guiMessageQueue); + + if (guiMessageQueue) + { + auto* rep = new DSPSignalNotification(notif); // make a copy for the source GUI + guiMessageQueue->push(rep); + } + } + + return true; + } + // was in handleSynchronousMessages: + else if (DSPAcquisitionInit::match(message)) + { + return true; // discard + } + else if (DSPAcquisitionStart::match(message)) + { + setState(gotoIdle()); + + if(m_state == State::StIdle) { + setState(gotoInit()); // State goes ready if init is performed + } + + if(m_state == State::StReady) { + setState(gotoRunning()); + } + + return true; + } + else if (DSPAcquisitionStop::match(message)) + { + setState(gotoIdle()); + emit acquistionStopped(); + return true; + } + else if (DSPSetSource::match(message)) + { + auto cmd = (const DSPSetSource&) message; + handleSetSource(cmd.getSampleSource()); + emit sampleSet(); + return true; + } + else if (DSPAddBasebandSampleSink::match(message)) + { + auto cmd = (const DSPAddBasebandSampleSink&) message; + BasebandSampleSink* sink = cmd.getSampleSink(); + m_basebandSampleSinks.push_back(sink); + // initialize sample rate and center frequency in the sink: + auto *msg = new DSPSignalNotification(m_sampleRate, m_centerFrequency); + sink->pushMessage(msg); + // start the sink: + if(m_state == State::StRunning) { + sink->start(); + } + return true; + } + else if (DSPRemoveBasebandSampleSink::match(message)) + { + auto cmd = (const DSPRemoveBasebandSampleSink&) message; + BasebandSampleSink* sink = cmd.getSampleSink(); + bool deleting = cmd.getDeleting(); + + // Don't dereference sink if deleting, as it may have already been deleted + if (!deleting && (m_state == State::StRunning)) { + sink->stop(); + } + + m_basebandSampleSinks.remove(sink); + emit sinkRemoved(); + return true; + } + + return false; +} + +void DSPDeviceSourceEngine::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + qDebug("DSPDeviceSourceEngine::handleInputMessages: message: %s", message->getIdentifier()); + + if (handleMessage(*message)) { + delete message; + } + } +} diff --git a/android/app/src/main/cpp/dsp/dspdevicesourceengine.h b/android/app/src/main/cpp/dsp/dspdevicesourceengine.h new file mode 100644 index 0000000..92f9741 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspdevicesourceengine.h @@ -0,0 +1,151 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSPDEVICEENGINE_H +#define INCLUDE_DSPDEVICEENGINE_H + +#include +#include +#include +#include +#include "dsp/dsptypes.h" +#include "util/messagequeue.h" +#include "export.h" +#include "util/movingaverage.h" + +class DeviceSampleSource; +class BasebandSampleSink; + +class SDRBASE_API DSPDeviceSourceEngine : public QObject { + Q_OBJECT + +public: + enum class State { + StNotStarted, //!< engine is before initialization + StIdle, //!< engine is idle + StReady, //!< engine is ready to run + StRunning, //!< engine is running + StError //!< engine is in error + }; + + DSPDeviceSourceEngine(uint uid, QObject* parent = nullptr); + ~DSPDeviceSourceEngine() final; + + uint getUID() const { return m_uid; } + + MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; } + + bool initAcquisition() const; //!< Initialize acquisition sequence + bool startAcquisition(); //!< Start acquisition sequence + void stopAcquistion(); //!< Stop acquisition sequence + + void setSource(DeviceSampleSource* source); //!< Set the sample source type + void setSourceSequence(int sequence); //!< Set the sample source sequence in type + DeviceSampleSource *getSource() { return m_deviceSampleSource; } + + void addSink(BasebandSampleSink* sink); //!< Add a sample sink + void removeSink(BasebandSampleSink* sink, bool deleting); //!< Remove a sample sink + + void configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection); //!< Configure DSP corrections + + State state() const { return m_state; } //!< Return DSP engine current state + + QString errorMessage() const; //!< Return the current error message + QString sourceDeviceDescription() const; //!< Return the source device description + +private: + uint m_uid; //!< unique ID + + MessageQueue m_inputMessageQueue; //; + BasebandSampleSinks m_basebandSampleSinks; //!< sample sinks within main thread (usually spectrum, file output) + + uint m_sampleRate; + quint64 m_centerFrequency; + bool m_realElseComplex; + + bool m_dcOffsetCorrection; + bool m_iqImbalanceCorrection; + double m_iOffset; + double m_qOffset; + + MovingAverageUtil m_iBeta; + MovingAverageUtil m_qBeta; + +#if IMBALANCE_INT + // Fixed point DC + IQ corrections + MovingAverageUtil m_avgII; + MovingAverageUtil m_avgIQ; + MovingAverageUtil m_avgPhi; + MovingAverageUtil m_avgII2; + MovingAverageUtil m_avgQQ2; + MovingAverageUtil m_avgAmp; + +#else + // Floating point DC + IQ corrections + MovingAverageUtil m_avgII; + MovingAverageUtil m_avgIQ; + MovingAverageUtil m_avgII2; + MovingAverageUtil m_avgQQ2; + MovingAverageUtil m_avgPhi; + MovingAverageUtil m_avgAmp; +#endif + + qint32 m_iRange; + qint32 m_qRange; + qint32 m_imbalance; + + void iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, bool imbalanceCorrection); + void dcOffset(SampleVector::iterator begin, SampleVector::iterator end); + void imbalance(SampleVector::iterator begin, SampleVector::iterator end); + void work(); //!< transfer samples from source to sinks if in running state + + State gotoIdle(); //!< Go to the idle state + State gotoInit(); //!< Go to the acquisition init state from idle + State gotoRunning(); //!< Go to the running state from ready state + State gotoError(const QString& errorMsg); //!< Go to an error state + void setState(State state); + + void handleSetSource(DeviceSampleSource* source); //!< Manage source setting + bool handleMessage(const Message& cmd); + +private slots: + void handleData(); //!< Handle data when samples from source FIFO are ready to be processed + void handleInputMessages(); //!< Handle input message queue + +signals: + void stateChanged(); + + void acquistionStopped(); + void sampleSet(); + void sinkRemoved(); +}; + +#endif // INCLUDE_DSPDEVICEENGINE_H diff --git a/android/app/src/main/cpp/dsp/dspengine.cpp b/android/app/src/main/cpp/dsp/dspengine.cpp new file mode 100644 index 0000000..da86975 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspengine.cpp @@ -0,0 +1,235 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/dspengine.h" +#include "dsp/dspdevicesourceengine.h" +#include "dsp/dspdevicesinkengine.h" +#include "dsp/dspdevicemimoengine.h" +#include "dsp/fftfactory.h" + +DSPEngine::DSPEngine() : + m_deviceSourceEnginesUIDSequence(0), + m_deviceSinkEnginesUIDSequence(0), + m_deviceMIMOEnginesUIDSequence(0), + m_audioInputDeviceIndex(-1), // default device + m_audioOutputDeviceIndex(-1), // default device + m_fftFactory(nullptr) +{ + m_dvSerialSupport = false; + m_mimoSupport = false; + m_masterTimer.start(50); +} + +DSPEngine::~DSPEngine() +{ + auto it = m_deviceSourceEngines.begin(); + + while (it != m_deviceSourceEngines.end()) + { + delete *it; + ++it; + } + + if (m_fftFactory) { + delete m_fftFactory; + } +} + +Q_GLOBAL_STATIC(DSPEngine, dspEngine) +DSPEngine *DSPEngine::instance() +{ + return dspEngine; +} + +DSPDeviceSourceEngine *DSPEngine::addDeviceSourceEngine() +{ + auto *deviceSourceEngine = new DSPDeviceSourceEngine(m_deviceSourceEnginesUIDSequence); + auto *deviceThread = new QThread(); + m_deviceSourceEnginesUIDSequence++; + m_deviceSourceEngines.push_back(deviceSourceEngine); + m_deviceEngineReferences.push_back(DeviceEngineReference{0, m_deviceSourceEngines.back(), nullptr, nullptr, deviceThread}); + deviceSourceEngine->moveToThread(deviceThread); + + QObject::connect( + deviceThread, + &QThread::finished, + deviceThread, + &QThread::deleteLater + ); + + deviceThread->start(); + + return deviceSourceEngine; +} + +void DSPEngine::removeLastDeviceSourceEngine() +{ + if (!m_deviceSourceEngines.empty()) + { + const DSPDeviceSourceEngine *lastDeviceEngine = m_deviceSourceEngines.back(); + m_deviceSourceEngines.pop_back(); + + for (int i = 0; i < m_deviceEngineReferences.size(); i++) + { + if (m_deviceEngineReferences[i].m_deviceSourceEngine == lastDeviceEngine) + { + QThread* deviceThread = m_deviceEngineReferences[i].m_thread; + deviceThread->exit(); + deviceThread->wait(); + m_deviceEngineReferences.removeAt(i); + break; + } + } + } +} + +DSPDeviceSinkEngine *DSPEngine::addDeviceSinkEngine() +{ + auto *deviceSinkEngine = new DSPDeviceSinkEngine(m_deviceSinkEnginesUIDSequence); + auto *deviceThread = new QThread(); + m_deviceSinkEnginesUIDSequence++; + m_deviceSinkEngines.push_back(deviceSinkEngine); + m_deviceEngineReferences.push_back(DeviceEngineReference{1, nullptr, m_deviceSinkEngines.back(), nullptr, deviceThread}); + deviceSinkEngine->moveToThread(deviceThread); + + QObject::connect( + deviceThread, + &QThread::finished, + deviceThread, + &QThread::deleteLater + ); + + deviceThread->start(); + + return deviceSinkEngine; +} + +void DSPEngine::removeLastDeviceSinkEngine() +{ + if (!m_deviceSinkEngines.empty()) + { + const DSPDeviceSinkEngine *lastDeviceEngine = m_deviceSinkEngines.back(); + m_deviceSinkEngines.pop_back(); + + for (int i = 0; i < m_deviceEngineReferences.size(); i++) + { + if (m_deviceEngineReferences[i].m_deviceSinkEngine == lastDeviceEngine) + { + QThread* deviceThread = m_deviceEngineReferences[i].m_thread; + deviceThread->exit(); + deviceThread->wait(); + m_deviceEngineReferences.removeAt(i); + break; + } + } + } +} + +DSPDeviceMIMOEngine *DSPEngine::addDeviceMIMOEngine() +{ + auto *deviceMIMOEngine = new DSPDeviceMIMOEngine(m_deviceMIMOEnginesUIDSequence); + auto *deviceThread = new QThread(); + m_deviceMIMOEnginesUIDSequence++; + m_deviceMIMOEngines.push_back(deviceMIMOEngine); + m_deviceEngineReferences.push_back(DeviceEngineReference{2, nullptr, nullptr, m_deviceMIMOEngines.back(), deviceThread}); + deviceMIMOEngine->moveToThread(deviceThread); + + QObject::connect( + deviceThread, + &QThread::finished, + deviceThread, + &QThread::deleteLater + ); + + deviceThread->start(); + + return deviceMIMOEngine; +} + +void DSPEngine::removeLastDeviceMIMOEngine() +{ + if (!m_deviceMIMOEngines.empty()) + { + const DSPDeviceMIMOEngine *lastDeviceEngine = m_deviceMIMOEngines.back(); + m_deviceMIMOEngines.pop_back(); + + for (int i = 0; i < m_deviceEngineReferences.size(); i++) + { + if (m_deviceEngineReferences[i].m_deviceMIMOEngine == lastDeviceEngine) + { + QThread* deviceThread = m_deviceEngineReferences[i].m_thread; + deviceThread->exit(); + deviceThread->wait(); + m_deviceEngineReferences.removeAt(i); + break; + } + } + } +} + +QThread *DSPEngine::getDeviceEngineThread(int deviceIndex) +{ + if (deviceIndex >= m_deviceEngineReferences.size()) { + return nullptr; + } else { + return m_deviceEngineReferences[deviceIndex].m_thread; + } +} + +void DSPEngine::removeDeviceEngineAt(int deviceIndex) +{ + if (deviceIndex >= m_deviceEngineReferences.size()) { + return; + } + + if (m_deviceEngineReferences[deviceIndex].m_deviceEngineType == 0) // source + { + DSPDeviceSourceEngine *deviceEngine = m_deviceEngineReferences[deviceIndex].m_deviceSourceEngine; + m_deviceEngineReferences[deviceIndex].m_thread->exit(); + m_deviceSourceEngines.removeAll(deviceEngine); + } + else if (m_deviceEngineReferences[deviceIndex].m_deviceEngineType == 1) // sink + { + DSPDeviceSinkEngine *deviceEngine = m_deviceEngineReferences[deviceIndex].m_deviceSinkEngine; + m_deviceEngineReferences[deviceIndex].m_thread->exit(); + m_deviceSinkEngines.removeAll(deviceEngine); + } + else if (m_deviceEngineReferences[deviceIndex].m_deviceEngineType == 2) // MIMO + { + DSPDeviceMIMOEngine *deviceEngine = m_deviceEngineReferences[deviceIndex].m_deviceMIMOEngine; + m_deviceEngineReferences[deviceIndex].m_thread->exit(); + m_deviceMIMOEngines.removeAll(deviceEngine); + } + + m_deviceEngineReferences.removeAt(deviceIndex); +} + +void DSPEngine::createFFTFactory(const QString& fftWisdomFileName) +{ + m_fftFactory = new FFTFactory(fftWisdomFileName); +} + +void DSPEngine::preAllocateFFTs() +{ + m_fftFactory->preallocate(7, 10, 1, 0); // pre-acllocate forward FFT only 1 per size from 128 to 1024 +} diff --git a/android/app/src/main/cpp/dsp/dspengine.h b/android/app/src/main/cpp/dsp/dspengine.h new file mode 100644 index 0000000..a3948c0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dspengine.h @@ -0,0 +1,103 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSPENGINE_H +#define INCLUDE_DSPENGINE_H + +#include +#include +#include + +#include "audio/audiodevicemanager.h" +#include "export.h" + +class DSPDeviceSourceEngine; +class DSPDeviceSinkEngine; +class DSPDeviceMIMOEngine; +class FFTFactory; +class QThread; + +class SDRBASE_API DSPEngine : public QObject { + Q_OBJECT +public: + DSPEngine(); + ~DSPEngine(); + + static DSPEngine *instance(); + + unsigned int getDefaultAudioSampleRate() const { return AudioDeviceManager::m_defaultAudioSampleRate; } + + DSPDeviceSourceEngine *addDeviceSourceEngine(); + void removeLastDeviceSourceEngine(); + + DSPDeviceSinkEngine *addDeviceSinkEngine(); + void removeLastDeviceSinkEngine(); + + DSPDeviceMIMOEngine *addDeviceMIMOEngine(); + void removeLastDeviceMIMOEngine(); + + QThread *getDeviceEngineThread(int deviceIndex); + void removeDeviceEngineAt(int deviceIndex); + + AudioDeviceManager *getAudioDeviceManager() { return &m_audioDeviceManager; } + + uint32_t getDeviceSourceEnginesNumber() const { return m_deviceSourceEngines.size(); } + DSPDeviceSourceEngine *getDeviceSourceEngineByIndex(unsigned int deviceIndex) { return m_deviceSourceEngines[deviceIndex]; } + + uint32_t getDeviceSinkEnginesNumber() const { return m_deviceSinkEngines.size(); } + DSPDeviceSinkEngine *getDeviceSinkEngineByIndex(unsigned int deviceIndex) { return m_deviceSinkEngines[deviceIndex]; } + + uint32_t getDeviceMIMOEnginesNumber() const { return m_deviceMIMOEngines.size(); } + DSPDeviceMIMOEngine *getDeviceMIMOEngineByIndex(unsigned int deviceIndex) { return m_deviceMIMOEngines[deviceIndex]; } + + const QTimer& getMasterTimer() const { return m_masterTimer; } + void setMIMOSupport(bool mimoSupport) { m_mimoSupport = mimoSupport; } + bool getMIMOSupport() const { return m_mimoSupport; } + void createFFTFactory(const QString& fftWisdomFileName); + void preAllocateFFTs(); + FFTFactory *getFFTFactory() { return m_fftFactory; } + +private: + struct DeviceEngineReference + { + int m_deviceEngineType; //!< 0: Rx, 1: Tx, 2: MIMO + DSPDeviceSourceEngine *m_deviceSourceEngine; + DSPDeviceSinkEngine *m_deviceSinkEngine; + DSPDeviceMIMOEngine *m_deviceMIMOEngine; + QThread *m_thread; + }; + + QList m_deviceSourceEngines; + unsigned int m_deviceSourceEnginesUIDSequence; + QList m_deviceSinkEngines; + unsigned int m_deviceSinkEnginesUIDSequence; + QList m_deviceMIMOEngines; + unsigned int m_deviceMIMOEnginesUIDSequence; + QList m_deviceEngineReferences; + AudioDeviceManager m_audioDeviceManager; + int m_audioInputDeviceIndex; + int m_audioOutputDeviceIndex; + QTimer m_masterTimer; + bool m_dvSerialSupport; + bool m_mimoSupport; + FFTFactory *m_fftFactory; +}; + +#endif // INCLUDE_DSPENGINE_H diff --git a/android/app/src/main/cpp/dsp/dsptypes.h b/android/app/src/main/cpp/dsp/dsptypes.h new file mode 100644 index 0000000..50ed1d2 --- /dev/null +++ b/android/app/src/main/cpp/dsp/dsptypes.h @@ -0,0 +1,102 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSPTYPES_H +#define INCLUDE_DSPTYPES_H + +#include +#include +#include + +#ifdef SDR_RX_SAMPLE_24BIT +#define SDR_RX_SAMP_SZ 24 // internal fixed arithmetic sample size +#define SDR_RX_SCALEF 8388608.0f +#define SDR_RX_SCALED 8388608.0 +typedef qint32 FixReal; +#else +#define SDR_RX_SAMP_SZ 16 // internal fixed arithmetic sample size +#define SDR_RX_SCALEF 32768.0f +#define SDR_RX_SCALED 32768.0 +typedef int16_t FixReal; +#endif + +#define SDR_TX_SAMP_SZ 16 +#define SDR_TX_SCALEF 32768.0f +#define SDR_TX_SCALED 32768.0 + +typedef float Real; +typedef std::complex Complex; +typedef std::vector ComplexVector; + +#pragma pack(push, 1) +struct Sample +{ + Sample() : m_real(0), m_imag(0) {} + Sample(FixReal real) : m_real(real), m_imag(0) {} + Sample(FixReal real, FixReal imag) : m_real(real), m_imag(imag) {} + Sample(const Sample& other) : m_real(other.m_real), m_imag(other.m_imag) {} + inline Sample& operator=(const Sample& other) { m_real = other.m_real; m_imag = other.m_imag; return *this; } + + inline Sample& operator+=(const Sample& other) { m_real += other.m_real; m_imag += other.m_imag; return *this; } + inline Sample& operator-=(const Sample& other) { m_real -= other.m_real; m_imag -= other.m_imag; return *this; } + inline Sample& operator/=(const unsigned int& divisor) { m_real /= divisor; m_imag /= divisor; return *this; } + + inline void setReal(FixReal v) { m_real = v; } + inline void setImag(FixReal v) { m_imag = v; } + + inline FixReal real() const { return m_real; } + inline FixReal imag() const { return m_imag; } + + FixReal m_real; + FixReal m_imag; +}; + +struct FSample +{ + FSample() : m_real(0), m_imag(0) {} + FSample(Real real) : m_real(real), m_imag(0) {} + FSample(Real real, Real imag) : m_real(real), m_imag(imag) {} + FSample(const FSample& other) : m_real(other.m_real), m_imag(other.m_imag) {} + inline FSample& operator=(const FSample& other) { m_real = other.m_real; m_imag = other.m_imag; return *this; } + + inline FSample& operator+=(const FSample& other) { m_real += other.m_real; m_imag += other.m_imag; return *this; } + inline FSample& operator-=(const FSample& other) { m_real -= other.m_real; m_imag -= other.m_imag; return *this; } + inline FSample& operator/=(const Real& divisor) { m_real /= divisor; m_imag /= divisor; return *this; } + + inline void setReal(Real v) { m_real = v; } + inline void setImag(Real v) { m_imag = v; } + + inline Real real() const { return m_real; } + inline Real imag() const { return m_imag; } + + Real m_real; + Real m_imag; +}; + +struct AudioSample { + int16_t l; + int16_t r; +}; +#pragma pack(pop) + +typedef std::vector SampleVector; +typedef std::vector FSampleVector; +typedef std::vector AudioVector; + +#endif // INCLUDE_DSPTYPES_H diff --git a/android/app/src/main/cpp/dsp/fftcorr.cpp b/android/app/src/main/cpp/dsp/fftcorr.cpp new file mode 100644 index 0000000..239250c --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftcorr.cpp @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// // +// FFT based cross correlation. Uses FFTW/Kiss engine. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/fftfactory.h" +#include "dsp/fftengine.h" +#include "fftcorr.h" + +void fftcorr::init_fft() +{ + FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); + fftASequence = fftFactory->getEngine(flen, false, &fftA); + fftBSequence = fftFactory->getEngine(flen, false, &fftB); + fftInvASequence = fftFactory->getEngine(flen, true, &fftInvA); + + m_window.create(FFTWindow::Hanning, flen); + + dataA = new cmplx[flen]; + dataB = new cmplx[flen]; + dataBj = new cmplx[flen]; + dataP = new cmplx[flen]; + + std::fill(dataA, dataA+flen, 0); + std::fill(dataB, dataB+flen, 0); + + inptrA = 0; + inptrB = 0; + outptr = 0; +} + +fftcorr::fftcorr(int len) : + flen(len), + flen2(len>>1), + fftA(nullptr), + fftB(nullptr), + fftInvA(nullptr), + fftASequence(0), + fftBSequence(0), + fftInvASequence(0) +{ + init_fft(); +} + +fftcorr::~fftcorr() +{ + FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); + fftFactory->releaseEngine(flen, false, fftASequence); + fftFactory->releaseEngine(flen, false, fftBSequence); + fftFactory->releaseEngine(flen, true, fftInvASequence); + delete[] dataA; + delete[] dataB; + delete[] dataBj; + delete[] dataP; +} + +int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) +{ + dataA[inptrA++] = inA; + + if (inB) { + dataB[inptrB++] = *inB; + } + + if (inptrA < flen2) { + return 0; + } + + m_window.apply(dataA, fftA->in()); + fftA->transform(); + + if (inB) + { + m_window.apply(dataB, fftB->in()); + fftB->transform(); + } + + if (inB) { + std::transform(fftB->out(), fftB->out()+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); }); + } else { + std::transform(fftA->out(), fftA->out()+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); }); + } + + std::transform(fftA->out(), fftA->out()+flen, dataBj, fftInvA->in(), [](const cmplx& a, const cmplx& b) -> cmplx { return a*b; }); + + fftInvA->transform(); + std::copy(fftInvA->out(), fftInvA->out()+flen, dataP); + + std::fill(dataA, dataA+flen, 0); + inptrA = 0; + + if (inB) + { + std::fill(dataB, dataB+flen, 0); + inptrB = 0; + } + + *out = dataP; + return flen2; +} + +const fftcorr::cmplx& fftcorr::run(const cmplx& inA, const cmplx* inB) +{ + cmplx *dummy; + + if (run(inA, inB, &dummy)) { + outptr = 0; + } + + return dataP[outptr++]; +} diff --git a/android/app/src/main/cpp/dsp/fftcorr.h b/android/app/src/main/cpp/dsp/fftcorr.h new file mode 100644 index 0000000..d118bb0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftcorr.h @@ -0,0 +1,60 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// // +// FFT based cross correlation. Uses FFTW/Kiss engine. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_FFTCORR2_H_ +#define SDRBASE_DSP_FFTCORR2_H_ + +#include + +#include "dsp/fftwindow.h" +#include "export.h" + +class FFTEngine; + +class SDRBASE_API fftcorr { +public: + typedef std::complex cmplx; + fftcorr(int len); + ~fftcorr(); + + int run(const cmplx& inA, const cmplx* inB, cmplx **out); //!< if inB = 0 then run auto-correlation + const cmplx& run(const cmplx& inA, const cmplx* inB); + +private: + void init_fft(); + int flen; //!< FFT length + int flen2; //!< half FFT length + FFTEngine *fftA; + FFTEngine *fftB; + FFTEngine *fftInvA; + unsigned int fftASequence; + unsigned int fftBSequence; + unsigned int fftInvASequence; + FFTWindow m_window; + cmplx *dataA; // from A input + cmplx *dataB; // from B input + cmplx *dataBj; // conjugate of B + cmplx *dataP; // product of A with conjugate of B + int inptrA; + int inptrB; + int outptr; +}; + + +#endif /* SDRBASE_DSP_FFTCORR2_H_ */ diff --git a/android/app/src/main/cpp/dsp/fftengine.cpp b/android/app/src/main/cpp/dsp/fftengine.cpp new file mode 100644 index 0000000..1d44ac0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftengine.cpp @@ -0,0 +1,118 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2017-2018, 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/fftengine.h" +#ifdef USE_KISSFFT +#include "dsp/kissengine.h" +#endif +#ifdef USE_FFTW +#include "dsp/fftwengine.h" +#endif +#ifdef VKFFT_BACKEND +#if VKFFT_BACKEND==0 +#include "dsp/vulkanvkfftengine.h" +#elif VKFFT_BACKEND==1 +#include "dsp/cudavkfftengine.h" +#endif +#endif + +QStringList FFTEngine::m_allAvailableEngines; + +FFTEngine::~FFTEngine() +{ +} + +FFTEngine* FFTEngine::create(const QString& fftWisdomFileName, const QString& preferredEngine) +{ + QStringList allNames = getAllNames(); + QString engine; + + if (allNames.size() == 0) + { + // No engines available + qCritical("FFTEngine::create: no engine built"); + return nullptr; + } + else if (!preferredEngine.isEmpty() && allNames.contains(preferredEngine)) + { + // Use the preferred engine + engine = preferredEngine; + } + else + { + // Use first available + engine = allNames[0]; + } + + qDebug("FFTEngine::create: using %s engine", qPrintable(engine)); + +#ifdef VKFFT_BACKEND +#if VKFFT_BACKEND==0 + if (engine == VulkanvkFFTEngine::m_name) { + return new VulkanvkFFTEngine(); + } +#endif +#if VKFFT_BACKEND==1 + if (engine == CUDAvkFFTEngine::m_name) { + return new CUDAvkFFTEngine(); + } +#endif +#endif +#ifdef USE_FFTW + if (engine == FFTWEngine::m_name) { + return new FFTWEngine(fftWisdomFileName); + } +#endif +#ifdef USE_KISSFFT + if (engine == KissEngine::m_name) { + return new KissEngine; + } +#endif + return nullptr; +} + +QStringList FFTEngine::getAllNames() +{ + if (m_allAvailableEngines.size() == 0) + { +#ifdef USE_FFTW + m_allAvailableEngines.append(FFTWEngine::m_name); +#endif +#ifdef USE_KISSFFT + m_allAvailableEngines.append(KissEngine::m_name); +#endif +#ifdef VKFFT_BACKEND +#if VKFFT_BACKEND==0 + VulkanvkFFTEngine vulkanvkFFT; + if (vulkanvkFFT.isAvailable()) { + m_allAvailableEngines.append(vulkanvkFFT.getName()); + } +#elif VKFFT_BACKEND==1 + CUDAvkFFTEngine cudavkFFT; + if (cudavkFFT.isAvailable()) { + m_allAvailableEngines.append(cudavkFFT.getName()); + } +#endif +#endif + } + return m_allAvailableEngines; +} diff --git a/android/app/src/main/cpp/dsp/fftengine.h b/android/app/src/main/cpp/dsp/fftengine.h new file mode 100644 index 0000000..637d684 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftengine.h @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FFTENGINE_H +#define INCLUDE_FFTENGINE_H + +#include + +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API FFTEngine { +public: + virtual ~FFTEngine(); + + virtual void configure(int n, bool inverse) = 0; + virtual void transform() = 0; + + virtual Complex* in() = 0; + virtual Complex* out() = 0; + + virtual void setReuse(bool reuse) = 0; + + static FFTEngine* create(const QString& fftWisdomFileName, const QString& preferredEngine=""); + + virtual bool isAvailable() { return true; } // Is this FFT engine available to be used? + virtual QString getName() const = 0; // Get the name of this FFT engine + + static QStringList getAllNames(); // Get names of all available FFT engines + +private: + static QStringList m_allAvailableEngines; + +}; + +#endif // INCLUDE_FFTENGINE_H diff --git a/android/app/src/main/cpp/dsp/fftfactory.cpp b/android/app/src/main/cpp/dsp/fftfactory.cpp new file mode 100644 index 0000000..28e5804 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftfactory.cpp @@ -0,0 +1,153 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "fftfactory.h" +#include "maincore.h" + +FFTFactory::FFTFactory(const QString& fftwWisdomFileName) : + m_fftwWisdomFileName(fftwWisdomFileName) +{} + +FFTFactory::~FFTFactory() +{ + qDebug("FFTFactory::~FFTFactory: deleting FFTs"); + + for (auto mIt = m_fftEngineBySize.begin(); mIt != m_fftEngineBySize.end(); ++mIt) + { + for (auto eIt = mIt->second.begin(); eIt != mIt->second.end(); ++eIt) { + delete eIt->m_engine; + } + } +} + +void FFTFactory::preallocate( + unsigned int minLog2Size, + unsigned int maxLog2Size, + unsigned int numberFFT, + unsigned int numberInvFFT) +{ + if (minLog2Size <= maxLog2Size) + { + for (unsigned int log2Size = minLog2Size; log2Size <= maxLog2Size; log2Size++) + { + unsigned int fftSize = 1<>(fftSize, std::vector())); + m_invFFTEngineBySize.insert(std::pair>(fftSize, std::vector())); + std::vector& fftEngines = m_fftEngineBySize[fftSize]; + std::vector& invFFTEngines = m_invFFTEngineBySize[fftSize]; + + for (unsigned int i = 0; i < numberFFT; i++) + { + fftEngines.push_back(AllocatedEngine()); + fftEngines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName); + fftEngines.back().m_engine->setReuse(false); + fftEngines.back().m_engine->configure(fftSize, false); + } + + for (unsigned int i = 0; i < numberInvFFT; i++) + { + invFFTEngines.push_back(AllocatedEngine()); + invFFTEngines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName); + fftEngines.back().m_engine->setReuse(false); + invFFTEngines.back().m_engine->configure(fftSize, true); + } + } + } +} + +unsigned int FFTFactory::getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine, const QString& preferredEngine) +{ + QMutexLocker mutexLocker(&m_mutex); + std::map>& enginesBySize = inverse ? + m_invFFTEngineBySize : m_fftEngineBySize; + + // If no preferred engine requested, use user preference + QString requestedEngine = preferredEngine; + if (requestedEngine.isEmpty()) + { + const MainSettings& mainSettings = MainCore::instance()->getSettings(); + requestedEngine = mainSettings.getFFTEngine(); + } + + if (enginesBySize.find(fftSize) == enginesBySize.end()) + { + qDebug("FFTFactory::getEngine: new FFT %s size: %u", (inverse ? "inv" : "fwd"), fftSize); + enginesBySize.insert(std::pair>(fftSize, std::vector())); + std::vector& engines = enginesBySize[fftSize]; + engines.push_back(AllocatedEngine()); + engines.back().m_inUse = true; + engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName, requestedEngine); + engines.back().m_engine->setReuse(false); + engines.back().m_engine->configure(fftSize, inverse); + *engine = engines.back().m_engine; + return 0; + } + else + { + unsigned int i = 0; + + // Look for existing engine of requested size and type not currently in use + for (; i < enginesBySize[fftSize].size(); i++) + { + if (!enginesBySize[fftSize][i].m_inUse && (enginesBySize[fftSize][i].m_engine->getName() == requestedEngine)) { + break; + } + } + + if (i < enginesBySize[fftSize].size()) + { + qDebug("FFTFactory::getEngine: reuse engine: %u FFT %s size: %u", i, (inverse ? "inv" : "fwd"), fftSize); + enginesBySize[fftSize][i].m_inUse = true; + *engine = enginesBySize[fftSize][i].m_engine; + return i; + } + else + { + std::vector& engines = enginesBySize[fftSize]; + qDebug("FFTFactory::getEngine: create engine: %lu FFT %s size: %u", engines.size(), (inverse ? "inv" : "fwd"), fftSize); + engines.push_back(AllocatedEngine()); + engines.back().m_inUse = true; + engines.back().m_engine = FFTEngine::create(m_fftwWisdomFileName, requestedEngine); + engines.back().m_engine->setReuse(false); + engines.back().m_engine->configure(fftSize, inverse); + *engine = engines.back().m_engine; + return engines.size() - 1; + } + } +} + +void FFTFactory::releaseEngine(unsigned int fftSize, bool inverse, unsigned int engineSequence) +{ + QMutexLocker mutexLocker(&m_mutex); + std::map>& enginesBySize = inverse ? + m_invFFTEngineBySize : m_fftEngineBySize; + + if (enginesBySize.find(fftSize) != enginesBySize.end()) + { + std::vector& engines = enginesBySize[fftSize]; + + if (engineSequence < engines.size()) + { + qDebug("FFTFactory::releaseEngine: engineSequence: %u FFT %s size: %u", + engineSequence, (inverse ? "inv" : "fwd"), fftSize); + engines[engineSequence].m_inUse = false; + } + } +} diff --git a/android/app/src/main/cpp/dsp/fftfactory.h b/android/app/src/main/cpp/dsp/fftfactory.h new file mode 100644 index 0000000..fcfdb86 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftfactory.h @@ -0,0 +1,61 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _SDRBASE_FFTWFACTORY_H +#define _SDRBASE_FFTWFACTORY_H + +#include +#include + +#include +#include + +#include "export.h" +#include "fftengine.h" + +class SDRBASE_API FFTFactory { +public: + FFTFactory(const QString& fftwWisdomFileName); + ~FFTFactory(); + + void preallocate(unsigned int minLog2Size, unsigned int maxLog2Size, unsigned int numberFFT, unsigned int numberInvFFT); + unsigned int getEngine(unsigned int fftSize, bool inverse, FFTEngine **engine, const QString& preferredEngine=""); //!< returns an engine sequence + void releaseEngine(unsigned int fftSize, bool inverse, unsigned int engineSequence); + +private: + struct AllocatedEngine + { + FFTEngine *m_engine; + bool m_inUse; + + AllocatedEngine() : + m_engine(nullptr), + m_inUse(false) + {} + }; + + QString m_fftwWisdomFileName; + std::map> m_fftEngineBySize; + std::map> m_invFFTEngineBySize; + QRecursiveMutex m_mutex; +}; + +#endif // _SDRBASE_FFTWFACTORY_H diff --git a/android/app/src/main/cpp/dsp/fftfilt.cpp b/android/app/src/main/cpp/dsp/fftfilt.cpp new file mode 100644 index 0000000..5293ee0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftfilt.cpp @@ -0,0 +1,690 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014-2015 John Greb // +// Copyright (C) 2015, 2017-2018, 2020, 2022-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +// ---------------------------------------------------------------------------- +// fftfilt.cxx -- Fast convolution Overlap-Add filter +// +// Filter implemented using overlap-add FFT convolution method +// h(t) characterized by Windowed-Sinc impulse response +// +// Reference: +// "The Scientist and Engineer's Guide to Digital Signal Processing" +// by Dr. Steven W. Smith, http://www.dspguide.com +// Chapters 16, 18 and 21 +// +// Copyright (C) 2006-2008 Dave Freese, W1HKJ +// +// This file is part of fldigi. +// +// Fldigi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Fldigi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with fldigi. If not, see . +// +// Augmented with more filter types +// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +//------------------------------------------------------------------------------ +// initialize the filter +// create forward and reverse FFTs +//------------------------------------------------------------------------------ + +// Only need a single instance of g_fft, used for both forward and reverse +void fftfilt::init_filter() +{ + flen2 = flen >> 1; + fft = new g_fft(flen); + + filter = new cmplx[flen]; + filterOpp = new cmplx[flen]; + data = new cmplx[flen]; + output = new cmplx[flen2]; + ovlbuf = new cmplx[flen2]; + + std::fill(filter, filter + flen, cmplx{0, 0}); + std::fill(filterOpp, filterOpp + flen, cmplx{0, 0}); + std::fill(data, data + flen , cmplx{0, 0}); + std::fill(output, output + flen2, cmplx{0, 0}); + std::fill(ovlbuf, ovlbuf + flen2, cmplx{0, 0}); + + inptr = 0; +} + +//------------------------------------------------------------------------------ +// fft filter +// f1 < f2 ==> band pass filter +// f1 > f2 ==> band reject filter +// f1 == 0 ==> low pass filter +// f2 == 0 ==> high pass filter +//------------------------------------------------------------------------------ +fftfilt::fftfilt(int len) : + m_noiseReduction(len) +{ + flen = len; + pass = 0; + window = 0; + m_dnr = false; + init_filter(); +} + +fftfilt::fftfilt(float f1, float f2, int len) : + m_noiseReduction(len) +{ + flen = len; + pass = 0; + window = 0; + m_dnr = false; + init_filter(); + create_filter(f1, f2); +} + +fftfilt::fftfilt(float f2, int len) : + m_noiseReduction(len) +{ + flen = len; + pass = 0; + window = 0; + m_dnr = false; + init_filter(); + create_dsb_filter(f2); +} + +fftfilt::~fftfilt() +{ + if (fft) delete fft; + + if (filter) delete [] filter; + if (filterOpp) delete [] filterOpp; + if (data) delete [] data; + if (output) delete [] output; + if (ovlbuf) delete [] ovlbuf; +} + +void fftfilt::create_filter(float f1, float f2, FFTWindow::Function wf) +{ + // initialize the filter to zero + std::fill(filter, filter + flen, cmplx{0, 0}); + + // create the filter shape coefficients by fft + bool b_lowpass, b_highpass; + b_lowpass = (f2 != 0); + b_highpass = (f1 != 0); + + for (int i = 0; i < flen2; i++) { + filter[i] = 0; + // lowpass @ f2 + if (b_lowpass) + filter[i] += fsinc(f2, i, flen2); + // highighpass @ f1 + if (b_highpass) + filter[i] -= fsinc(f1, i, flen2); + } + // highpass is delta[flen2/2] - h(t) + if (b_highpass && f2 < f1) + filter[flen2 / 2] += 1; + + FFTWindow fwin; + fwin.create(wf, flen2); + fwin.apply(filter); + + // for (int i = 0; i < flen2; i++) + // filter[i] *= _blackman(i, flen2); + + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) + + // normalize the output filter for unity gain + float scale = 0, mag; + for (int i = 0; i < flen2; i++) { + mag = abs(filter[i]); + if (mag > scale) scale = mag; + } + if (scale != 0) { + for (int i = 0; i < flen; i++) + filter[i] /= scale; + } +} + +void fftfilt::create_filter(const std::vector>& limits, bool pass, FFTWindow::Function wf) +{ + std::vector canvasNeg(flen2, pass ? 0 : 1); // initialize the negative frequencies filter canvas + std::vector canvasPos(flen2, pass ? 0 : 1); // initialize the positive frequencies filter canvas + std::fill(filter, filter + flen, cmplx{0, 0}); // initialize the positive filter to zero + std::fill(filterOpp, filterOpp + flen, cmplx{0, 0}); // initialize the negative filter to zero + + for (const auto& fs : limits) + { + const float& f1 = fs.first + 0.5; + const float& w = fs.second > 0.0 ? fs.second : 0.0; + const float& f2 = f1 + w; + + for (int i = 0; i < flen; i++) + { + if (pass) // pass + { + if ((i >= f1*flen) && (i <= f2*flen)) + { + if (i < flen2) { + canvasNeg[flen2-1-i] = 1; + } else { + canvasPos[i-flen2] = 1; + } + } + } + else // reject + { + if ((i >= f1*flen) && (i <= f2*flen)) + { + if (i < flen2) { + canvasNeg[flen2-1-i] = 0; + } else { + canvasPos[i-flen2] = 0; + } + } + } + } + } + + std::vector> indexesNegList; + std::vector> indexesPosList; + int cn = 0; + int cp = 0; + int defaultSecond = pass ? 0 : flen2 - 1; + + for (int i = 0; i < flen2; i++) + { + if ((canvasNeg[i] == 1) && (cn == 0)) { + indexesNegList.push_back(std::pair{i, defaultSecond}); + } + + if ((canvasNeg[i] == 0) && (cn == 1)) { + indexesNegList.back().second = i; + } + + if ((canvasPos[i] == 1) && (cp == 0)) { + indexesPosList.push_back(std::pair{i, defaultSecond}); + } + + if ((canvasPos[i] == 0) && (cp == 1)) { + indexesPosList.back().second = i; + } + + cn = canvasNeg[i]; + cp = canvasPos[i]; + } + + for (const auto& indexes : indexesPosList) + { + const float f1 = indexes.first / (float) flen; + const float f2 = indexes.second / (float) flen; + + for (int i = 0; i < flen2; i++) + { + if (f2 != 0) { + filter[i] += fsinc(f2, i, flen2); + } + if (f1 != 0) { + filter[i] -= fsinc(f1, i, flen2); + } + } + + if (f2 == 0 && f1 != 0) { + filter[flen2 / 2] += 1; + } + } + + for (const auto& indexes : indexesNegList) + { + const float f1 = indexes.first / (float) flen; + const float f2 = indexes.second / (float) flen; + + for (int i = 0; i < flen2; i++) + { + if (f2 != 0) { + filterOpp[i] += fsinc(f2, i, flen2); + } + if (f1 != 0) { + filterOpp[i] -= fsinc(f1, i, flen2); + } + } + + if (f2 == 0 && f1 != 0) { + filterOpp[flen2 / 2] += 1; + } + } + + FFTWindow fwin; + fwin.create(wf, flen2); + fwin.apply(filter); + fwin.apply(filterOpp); + + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) + fft->ComplexFFT(filterOpp); // filter was expressed in the time domain (impulse response) + + float scalen = 0, scalep = 0, magn, magp; // normalize the output filter for unity gain + + for (int i = 0; i < flen2; i++) + { + magp = abs(filter[i]); + + if (magp > scalep) { + scalep = magp; + } + + magn = abs(filterOpp[i]); + + if (magn > scalen) { + scalen = magn; + } + } + + if (scalep != 0) + { + std::for_each( + filter, + filter + flen, + [scalep](fftfilt::cmplx& s) { s /= scalep; } + ); + } + + if (scalen != 0) + { + std::for_each( + filterOpp, + filterOpp + flen, + [scalen](fftfilt::cmplx& s) { s /= scalen; } + ); + } +} + +// Double the size of FFT used for equivalent SSB filter or assume FFT is half the size of the one used for SSB +void fftfilt::create_dsb_filter(float f2, FFTWindow::Function wf) +{ + // initialize the filter to zero + std::fill(filter, filter + flen, cmplx{0, 0}); + + for (int i = 0; i < flen2; i++) { + filter[i] = fsinc(f2, i, flen2); + // filter[i] *= _blackman(i, flen2); + } + + FFTWindow fwin; + fwin.create(wf, flen2); + fwin.apply(filter); + + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) + + // normalize the output filter for unity gain + float scale = 0, mag; + for (int i = 0; i < flen2; i++) { + mag = abs(filter[i]); + if (mag > scale) scale = mag; + } + if (scale != 0) { + for (int i = 0; i < flen; i++) + filter[i] /= scale; + } +} + +// Double the size of FFT used for equivalent SSB filter or assume FFT is half the size of the one used for SSB +// used with runAsym for in band / opposite band asymmetrical filtering. Can be used for vestigial sideband modulation. +void fftfilt::create_asym_filter(float fopp, float fin, FFTWindow::Function wf) +{ + // in band + // initialize the filter to zero + std::fill(filter, filter + flen, cmplx{0, 0}); + + for (int i = 0; i < flen2; i++) { + filter[i] = fsinc(fin, i, flen2); + // filter[i] *= _blackman(i, flen2); + } + + FFTWindow fwin; + fwin.create(wf, flen2); + fwin.apply(filter); + + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) + + // normalize the output filter for unity gain + float scale = 0, mag; + for (int i = 0; i < flen2; i++) { + mag = abs(filter[i]); + if (mag > scale) scale = mag; + } + if (scale != 0) { + for (int i = 0; i < flen; i++) + filter[i] /= scale; + } + + // opposite band + // initialize the filter to zero + std::fill(filterOpp, filterOpp + flen, cmplx{0, 0}); + + for (int i = 0; i < flen2; i++) { + filterOpp[i] = fsinc(fopp, i, flen2); + // filterOpp[i] *= _blackman(i, flen2); + } + + fwin.apply(filterOpp); + fft->ComplexFFT(filterOpp); // filter was expressed in the time domain (impulse response) + + // normalize the output filter for unity gain + scale = 0; + for (int i = 0; i < flen2; i++) { + mag = abs(filterOpp[i]); + if (mag > scale) scale = mag; + } + if (scale != 0) { + for (int i = 0; i < flen; i++) + filterOpp[i] /= scale; + } +} + +// This filter is constructed directly from frequency domain response. Run with runFilt. +void fftfilt::create_rrc_filter(float fb, float a) +{ + std::fill(filter, filter+flen, 0); + + for (int i = 0; i < flen; i++) { + filter[i] = frrc(fb, a, i, flen); + } + + // normalize the output filter for unity gain + float scale = 0, mag; + for (int i = 0; i < flen; i++) + { + mag = abs(filter[i]); + if (mag > scale) { + scale = mag; + } + } + if (scale != 0) + { + for (int i = 0; i < flen; i++) { + filter[i] /= scale; + } + } +} + +// test bypass +int fftfilt::noFilt(const cmplx & in, cmplx **out) +{ + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + *out = data; + return flen2; +} + +// Filter with fast convolution (overlap-add algorithm). +int fftfilt::runFilt(const cmplx & in, cmplx **out) +{ + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + fft->ComplexFFT(data); + for (int i = 0; i < flen; i++) + data[i] *= filter[i]; + + fft->InverseComplexFFT(data); + + for (int i = 0; i < flen2; i++) { + output[i] = ovlbuf[i] + data[i]; + ovlbuf[i] = data[flen2 + i]; + } + std::fill(data, data + flen , cmplx{0, 0}); + + *out = output; + return flen2; +} + +// Second version for single sideband +int fftfilt::runSSB(const cmplx & in, cmplx **out, bool usb, bool getDC) +{ + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + fft->ComplexFFT(data); + + // get or reject DC component + data[0] = getDC ? data[0]*filter[0] : 0; + m_noiseReduction.setScheme(m_dnrScheme); + m_noiseReduction.init(); + + // Discard frequencies for ssb + if (usb) + { + for (int i = 1; i < flen2; i++) + { + data[i] *= filter[i]; + data[flen2 + i] = 0; + + if (m_dnr) + { + m_noiseReduction.push(data[i], i); + m_noiseReduction.push(data[flen2 + i], flen2 + i); + } + } + } + else + { + for (int i = 1; i < flen2; i++) + { + data[i] = 0; + data[flen2 + i] *= filter[flen2 + i]; + + if (m_dnr) + { + m_noiseReduction.push(data[i], i); + m_noiseReduction.push(data[flen2 + i], flen2 + i); + } + } + } + + if (m_dnr) + { + m_noiseReduction.m_aboveAvgFactor = m_dnrAboveAvgFactor; + m_noiseReduction.m_sigmaFactor = m_dnrSigmaFactor; + m_noiseReduction.m_nbPeaks = m_dnrNbPeaks; + m_noiseReduction.calc(); + + for (int i = 0; i < flen; i++) + { + if (m_noiseReduction.cut(i)) { + data[i] = 0; + } + } + } + + // in-place FFT: freqdata overwritten with filtered timedata + fft->InverseComplexFFT(data); + + // overlap and add + for (int i = 0; i < flen2; i++) { + output[i] = ovlbuf[i] + data[i]; + ovlbuf[i] = data[i+flen2]; + } + std::fill(data, data + flen , cmplx{0, 0}); + + *out = output; + return flen2; +} + +// Version for double sideband. You have to double the FFT size used for SSB. +int fftfilt::runDSB(const cmplx & in, cmplx **out, bool getDC) +{ + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + fft->ComplexFFT(data); + + for (int i = 0; i < flen2; i++) { + data[i] *= filter[i]; + data[flen2 + i] *= filter[flen2 + i]; + } + + // get or reject DC component + data[0] = getDC ? data[0] : 0; + + // in-place FFT: freqdata overwritten with filtered timedata + fft->InverseComplexFFT(data); + + // overlap and add + for (int i = 0; i < flen2; i++) { + output[i] = ovlbuf[i] + data[i]; + ovlbuf[i] = data[i+flen2]; + } + + std::fill(data, data + flen , cmplx{0, 0}); + + *out = output; + return flen2; +} + +// Version for asymmetrical sidebands. You have to double the FFT size used for SSB. +int fftfilt::runAsym(const cmplx & in, cmplx **out, bool usb) +{ + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + fft->ComplexFFT(data); + + data[0] *= filter[0]; // always keep DC + + if (usb) + { + for (int i = 1; i < flen2; i++) + { + data[i] *= filter[i]; // usb + data[flen2 + i] *= filterOpp[flen2 + i]; // lsb is the opposite + } + } + else + { + for (int i = 1; i < flen2; i++) + { + data[i] *= filterOpp[i]; // usb is the opposite + data[flen2 + i] *= filter[flen2 + i]; // lsb + } + } + + // in-place FFT: freqdata overwritten with filtered timedata + fft->InverseComplexFFT(data); + + // overlap and add + for (int i = 0; i < flen2; i++) { + output[i] = ovlbuf[i] + data[i]; + ovlbuf[i] = data[i+flen2]; + } + + std::fill(data, data + flen , cmplx{0, 0}); + + *out = output; + return flen2; +} + +/* Sliding FFT from Fldigi */ + +struct sfft::vrot_bins_pair { + cmplx vrot; + cmplx bins; +} ; + +sfft::sfft(int len) +{ + vrot_bins = new vrot_bins_pair[len]; + delay = new cmplx[len]; + fftlen = len; + first = 0; + last = len - 1; + ptr = 0; + double phi = 0.0, tau = 2.0 * M_PI/ len; + k2 = 1.0; + for (int i = 0; i < len; i++) { + vrot_bins[i].vrot = cmplx( K1 * cos (phi), K1 * sin (phi) ); + phi += tau; + delay[i] = vrot_bins[i].bins = 0.0; + k2 *= K1; + } +} + +sfft::~sfft() +{ + delete [] vrot_bins; + delete [] delay; +} + +// Sliding FFT, cmplx input, cmplx output +// FFT is computed for each value from first to last +// Values are not stable until more than "len" samples have been processed. +void sfft::run(const cmplx& input) +{ + cmplx & de = delay[ptr]; + const cmplx z( input.real() - k2 * de.real(), input.imag() - k2 * de.imag()); + de = input; + + if (++ptr >= fftlen) + ptr = 0; + + for (vrot_bins_pair *itr = vrot_bins + first, *end = vrot_bins + last; itr != end ; ++itr) + itr->bins = (itr->bins + z) * itr->vrot; +} + +// Copies the frequencies to a pointer. +void sfft::fetch(float *result) +{ + for (vrot_bins_pair *itr = vrot_bins, *end = vrot_bins + last; itr != end; ++itr, ++result) + *result = itr->bins.real() * itr->bins.real() + + itr->bins.imag() * itr->bins.imag(); +} + diff --git a/android/app/src/main/cpp/dsp/fftfilt.h b/android/app/src/main/cpp/dsp/fftfilt.h new file mode 100644 index 0000000..5cd0623 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftfilt.h @@ -0,0 +1,153 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014-2015 John Greb // +// Copyright (C) 2015-2018, 2022-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + * Filters from Fldigi. +*/ + +#ifndef _FFTFILT_H +#define _FFTFILT_H + +#include +#include + +#include "gfft.h" +#include "fftwindow.h" +#include "fftnr.h" +#include "export.h" + +//---------------------------------------------------------------------- + +class SDRBASE_API fftfilt { +enum {NONE, BLACKMAN, HAMMING, HANNING}; + +public: + typedef std::complex cmplx; + + fftfilt(int len); + fftfilt(float f1, float f2, int len); + fftfilt(float f2, int len); + ~fftfilt(); +// f1 < f2 ==> bandpass +// f1 > f2 ==> band reject + void create_filter(float f1, float f2, FFTWindow::Function wf = FFTWindow::Blackman); + void create_filter(const std::vector>& limits, bool pass = true, FFTWindow::Function wf = FFTWindow::Blackman); + void create_filter(const std::vector>& limits, bool pass = true); //!< Windowless version + void create_dsb_filter(float f2, FFTWindow::Function wf = FFTWindow::Blackman); + void create_asym_filter(float fopp, float fin, FFTWindow::Function wf = FFTWindow::Blackman); //!< two different filters for in band and opposite band + void create_rrc_filter(float fb, float a); //!< root raised cosine. fb is half the band pass + + int noFilt(const cmplx& in, cmplx **out); + int runFilt(const cmplx& in, cmplx **out); + int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true); + int runDSB(const cmplx& in, cmplx **out, bool getDC = true); + int runAsym(const cmplx & in, cmplx **out, bool usb); //!< Asymmetrical filtering can be used for vestigial sideband + + void setDNR(bool dnr) { m_dnr = dnr; } + void setDNRScheme(FFTNoiseReduction::Scheme scheme) { m_dnrScheme = scheme; } + void setDNRAboveAvgFactor(float aboveAvgFactor) { m_dnrAboveAvgFactor = aboveAvgFactor; } + void setDNRSigmaFactor(float sigmaFactor) { m_dnrSigmaFactor = sigmaFactor; } + void setDNRNbPeaks(int nbPeaks) { m_dnrNbPeaks = nbPeaks; } + void setDNRAlpha(float alpha) { m_noiseReduction.setAlpha(alpha); } + +protected: + // helper class for FFT based noise reduction + int flen; + int flen2; + g_fft *fft; + cmplx *filter; + cmplx *filterOpp; + cmplx *data; + cmplx *ovlbuf; + cmplx *output; + int inptr; + int pass; + int window; + bool m_dnr; + FFTNoiseReduction::Scheme m_dnrScheme; + float m_dnrAboveAvgFactor; //!< above average factor + float m_dnrSigmaFactor; //!< sigma multiplicator for average + std deviation + int m_dnrNbPeaks; //!< number of peaks (peaks scheme) + FFTNoiseReduction m_noiseReduction; + + inline float fsinc(float fc, int i, int len) + { + int len2 = len/2; + return (i == len2) ? 2.0 * fc: + sin(2 * M_PI * fc * (i - len2)) / (M_PI * (i - len2)); + } + + inline float _blackman(int i, int len) + { + return (0.42 - + 0.50 * cos(2.0 * M_PI * i / len) + + 0.08 * cos(4.0 * M_PI * i / len)); + } + + /** RRC function in the frequency domain. Zero frequency is on the sides with first half in positive frequencies + * and second half in negative frequencies */ + inline cmplx frrc(float fb, float a, int i, int len) + { + float x = i/(float)len; // normalize to [0..1] + x = 0.5-fabs(x-0.5); // apply symmetry: now both halves overlap near 0 + float tr = fb*a; // half the transition zone + + if (x < fb-tr) + { + return 1.0; // in band + } + else if (x < fb+tr) // transition + { + float y = ((x-(fb-tr)) / (2.0*tr))*M_PI; + return (cos(y) + 1.0f)/2.0f; + } + else + { + return 0.0; // out of band + } + } + + void init_filter(); + void init_dsb_filter(); +}; + + + +/* Sliding FFT filter from Fldigi */ +class SDRBASE_API sfft { +#define K1 0.99999 +public: + typedef std::complex cmplx; + sfft(int len); + ~sfft(); + void run(const cmplx& input); + void fetch(float *result); +private: + int fftlen; + int first; + int last; + int ptr; + struct vrot_bins_pair; + vrot_bins_pair *vrot_bins; + cmplx *delay; + float k2; +}; + +#endif diff --git a/android/app/src/main/cpp/dsp/fftnr.cpp b/android/app/src/main/cpp/dsp/fftnr.cpp new file mode 100644 index 0000000..56328f6 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftnr.cpp @@ -0,0 +1,148 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// Helper class for noise reduction // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include "fftnr.h" + +FFTNoiseReduction::FFTNoiseReduction(int len) : + m_flen(len) +{ + m_scheme = SchemeAverage; + m_mags = new float[m_flen]; + m_tmp = new float[m_flen]; + m_aboveAvgFactor = 1.0; + m_sigmaFactor = 1.0; + m_nbPeaks = m_flen; +} + +FFTNoiseReduction::~FFTNoiseReduction() +{ + delete[] m_mags; + delete[] m_tmp; +} + +void FFTNoiseReduction::init() +{ + std::fill(m_mags, m_mags + m_flen, 0); + std::fill(m_tmp, m_tmp + m_flen, 0); + m_magAvg = 0; +} + +void FFTNoiseReduction::push(cmplx data, int index) +{ + m_mags[index] = std::abs(data); + + if ((m_scheme == SchemeAverage) || (m_scheme == SchemeAvgStdDev)) { + m_magAvg += m_mags[index]; + } +} + +void FFTNoiseReduction::calc() +{ + if (m_scheme == SchemeAverage) + { + m_magAvg /= m_flen; + m_magAvg = m_expFilter.push(m_magAvg); + } + if (m_scheme == SchemeAvgStdDev) + { + m_magAvg /= m_flen; + + auto variance_func = [this](float accumulator, const float& val) { + return accumulator + ((val - m_magAvg)*(val - m_magAvg) / (m_flen - 1)); + }; + + float var = std::accumulate(m_mags, m_mags + m_flen, 0.0, variance_func); + m_magThr = (m_sigmaFactor/2.0)*std::sqrt(var) + m_magAvg; + m_magThr = m_expFilter.push(m_magThr); + } + else if (m_scheme == SchemePeaks) + { + std::copy(m_mags, m_mags + m_flen, m_tmp); + std::sort(m_tmp, m_tmp + m_flen); + m_magThr = m_tmp[m_flen - m_nbPeaks]; + } +} + +bool FFTNoiseReduction::cut(int index) +{ + if (m_scheme == SchemeAverage) + { + return m_mags[index] < m_aboveAvgFactor * m_magAvg; + } + else if ((m_scheme == SchemePeaks) || (m_scheme == SchemeAvgStdDev)) + { + return m_mags[index] < m_magThr; + } + + return false; +} + +void FFTNoiseReduction::setScheme(Scheme scheme) +{ + if (m_scheme != scheme) { + m_expFilter.reset(); + } + + m_scheme = scheme; +} + +FFTNoiseReduction::ExponentialFilter::ExponentialFilter() +{ + m_alpha = 1.0; + m_init = true; +} + +float FFTNoiseReduction::ExponentialFilter::push(float newValue) +{ + if (m_init) + { + m_prev = newValue; + m_init = false; + } + + if (m_alpha == 1.0) + { + m_prev = newValue; + return newValue; + } + else + { + float result = m_alpha*m_prev + (1.0 - m_alpha)*newValue; + m_prev = result; + return result; + } +} + +void FFTNoiseReduction::ExponentialFilter::reset() +{ + m_init = true; +} + +void FFTNoiseReduction::ExponentialFilter::setAlpha(float alpha) +{ + m_alpha = alpha < 0.0f ? 0.0f : alpha > 1.0f ? 1.0f : alpha; + qDebug("FFTNoiseReduction::ExponentialFilter::setAlpha: %f", m_alpha); + m_init = true; +} + diff --git a/android/app/src/main/cpp/dsp/fftnr.h b/android/app/src/main/cpp/dsp/fftnr.h new file mode 100644 index 0000000..d11f5c4 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftnr.h @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// Helper class for noise reduction // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _FFTNR_H +#define _FFTNR_H + +#include + +#include "export.h" + +class SDRBASE_API FFTNoiseReduction { +public: + typedef std::complex cmplx; + enum Scheme { + SchemeAverage, + SchemeAvgStdDev, + SchemePeaks + }; + + FFTNoiseReduction(int len); + ~FFTNoiseReduction(); + + void init(); //!< call before start of initial FFT scan + void push(cmplx data, int index); //!< Push FFT bin during initial FFT scan + void calc(); //!< calculate after initial FFT scan + bool cut(int index); //!< true if bin is to be zeroed else false (during second FFT scan) + void setAlpha(float alpha) { m_expFilter.setAlpha(alpha); } + void setScheme(Scheme scheme); + + float m_aboveAvgFactor; //!< above average factor + float m_sigmaFactor; //!< sigma multiplicator for average + std deviation + int m_nbPeaks; //!< number of peaks (peaks scheme) + +private: + class ExponentialFilter { + public: + ExponentialFilter(); + float push(float newValue); + void reset(); + void setAlpha(float alpha); + private: + bool m_init; + float m_alpha; + float m_prev; + }; + + Scheme m_scheme; + int m_flen; //!< FFT length + float *m_mags; //!< magnitudes (PSD) + float *m_tmp; //!< temporary buffer + float m_magAvg; //!< average of magnitudes + float m_magThr; //!< magnitude threshold (peaks scheme) + ExponentialFilter m_expFilter; //!< exponential filter for parameter smoothing +}; + +#endif diff --git a/android/app/src/main/cpp/dsp/fftwengine.cpp b/android/app/src/main/cpp/dsp/fftwengine.cpp new file mode 100644 index 0000000..cf9b365 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftwengine.cpp @@ -0,0 +1,127 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/fftwengine.h" +#include "util/profiler.h" + +FFTWEngine::FFTWEngine(const QString& fftWisdomFileName) : + m_fftWisdomFileName(fftWisdomFileName), + m_plans(), + m_currentPlan(nullptr), + m_reuse(true) +{ +} + +FFTWEngine::~FFTWEngine() +{ + freeAll(); +} + +const QString FFTWEngine::m_name = "FFTW"; + +QString FFTWEngine::getName() const +{ + return m_name; +} + +void FFTWEngine::configure(int n, bool inverse) +{ + if (m_reuse) + { + for (Plans::const_iterator it = m_plans.begin(); it != m_plans.end(); ++it) + { + if (((*it)->n == n) && ((*it)->inverse == inverse)) + { + m_currentPlan = *it; + return; + } + } + } + + m_currentPlan = new Plan; + m_currentPlan->n = n; + m_currentPlan->inverse = inverse; + m_currentPlan->in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n); + m_currentPlan->out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n); + QElapsedTimer t; + t.start(); + m_globalPlanMutex.lock(); + + if (m_fftWisdomFileName.size() > 0) + { + int rc = fftwf_import_wisdom_from_filename(m_fftWisdomFileName.toStdString().c_str()); + + if (rc == 0) { // that's an error (undocumented) + qInfo("FFTWEngine::configure: importing from FFTW wisdom file: '%s' failed", qPrintable(m_fftWisdomFileName)); + } else { + qDebug("FFTWEngine::configure: successfully imported from FFTW wisdom file: '%s'", qPrintable(m_fftWisdomFileName)); + } + } + else + { + qDebug("FFTWEngine::configure: no FFTW wisdom file"); + } + + m_currentPlan->plan = fftwf_plan_dft_1d(n, m_currentPlan->in, m_currentPlan->out, inverse ? FFTW_BACKWARD : FFTW_FORWARD, FFTW_PATIENT); + m_globalPlanMutex.unlock(); + + qDebug("FFT: creating FFTW plan (n=%d,%s) took %lld ms", n, inverse ? "inverse" : "forward", t.elapsed()); + m_plans.push_back(m_currentPlan); +} + +void FFTWEngine::transform() +{ + PROFILER_START() + + if(m_currentPlan != NULL) + fftwf_execute(m_currentPlan->plan); + + PROFILER_STOP(QString("%1 %2").arg(getName()).arg(m_currentPlan->n)) +} + +Complex* FFTWEngine::in() +{ + if(m_currentPlan != NULL) + return reinterpret_cast(m_currentPlan->in); + else return NULL; +} + +Complex* FFTWEngine::out() +{ + if(m_currentPlan != NULL) + return reinterpret_cast(m_currentPlan->out); + else return NULL; +} + +QMutex FFTWEngine::m_globalPlanMutex; + +void FFTWEngine::freeAll() +{ + for(Plans::iterator it = m_plans.begin(); it != m_plans.end(); ++it) { + fftwf_destroy_plan((*it)->plan); + fftwf_free((*it)->in); + fftwf_free((*it)->out); + delete *it; + } + m_plans.clear(); +} diff --git a/android/app/src/main/cpp/dsp/fftwengine.h b/android/app/src/main/cpp/dsp/fftwengine.h new file mode 100644 index 0000000..644d6ed --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftwengine.h @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FFTWENGINE_H +#define INCLUDE_FFTWENGINE_H + +#include +#include + +#include +#include +#include "dsp/fftengine.h" +#include "export.h" + +class SDRBASE_API FFTWEngine : public FFTEngine { +public: + FFTWEngine(const QString& fftWisdomFileName); + virtual ~FFTWEngine(); + + virtual void configure(int n, bool inverse); + virtual void transform(); + + virtual Complex* in(); + virtual Complex* out(); + + virtual void setReuse(bool reuse) { m_reuse = reuse; } + QString getName() const override; + static const QString m_name; + +protected: + static QMutex m_globalPlanMutex; + QString m_fftWisdomFileName; + + struct Plan { + int n; + bool inverse; + fftwf_plan plan; + fftwf_complex* in; + fftwf_complex* out; + }; + typedef std::list Plans; + Plans m_plans; + Plan* m_currentPlan; + bool m_reuse; + + void freeAll(); +}; + +#endif // INCLUDE_FFTWENGINE_H diff --git a/android/app/src/main/cpp/dsp/fftwindow.cpp b/android/app/src/main/cpp/dsp/fftwindow.cpp new file mode 100644 index 0000000..2c981b6 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftwindow.cpp @@ -0,0 +1,129 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2019-2020, 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/fftwindow.h" + +FFTWindow::FFTWindow() : + m_kaiserAlpha(M_PI) // first sidelobe at < -70dB +{ + m_kaiserI0Alpha = zeroethOrderBessel(m_kaiserAlpha); +} + +void FFTWindow::setKaiserAlpha(Real alpha) +{ + m_kaiserAlpha = alpha; + m_kaiserI0Alpha = zeroethOrderBessel(m_kaiserAlpha); +} + +void FFTWindow::setKaiserBeta(Real beta) +{ + m_kaiserAlpha = beta / M_PI; + m_kaiserI0Alpha = zeroethOrderBessel(m_kaiserAlpha); +} + +void FFTWindow::create(Function function, int n) +{ + Real (*wFunc)(Real n, Real i); + + m_window.clear(); + + if (function == Kaiser) // Kaiser special case + { + for(int i = 0; i < n; i++) { + m_window.push_back(kaiser(n, i)); + } + + return; + } + + switch (function) { + case Flattop: + wFunc = flatTop; + break; + + case Bartlett: + wFunc = bartlett; + break; + + case BlackmanHarris: + wFunc = blackmanHarris; + break; + + case Hamming: + wFunc = hamming; + break; + + case Hanning: + wFunc = hanning; + break; + + case Blackman: + wFunc = blackman; + break; + + case BlackmanHarris7: + wFunc = blackmanHarris7; + break; + + case Rectangle: + default: + wFunc = rectangle; + break; + } + + for(int i = 0; i < n; i++) { + m_window.push_back(wFunc(n, i)); + } +} + +void FFTWindow::apply(const std::vector& in, std::vector* out) +{ + for(size_t i = 0; i < m_window.size(); i++) { + (*out)[i] = in[i] * m_window[i]; + } +} + +void FFTWindow::apply(const std::vector& in, std::vector* out) +{ + for(size_t i = 0; i < m_window.size(); i++) { + (*out)[i] = in[i] * m_window[i]; + } +} + +void FFTWindow::apply(std::vector& in) +{ + for(size_t i = 0; i < m_window.size(); i++) { + in[i] *= m_window[i]; + } +} + +void FFTWindow::apply(const Complex* in, Complex* out) +{ + for(size_t i = 0; i < m_window.size(); i++) { + out[i] = in[i] * m_window[i]; + } +} + +void FFTWindow::apply(Complex* in) +{ + for(size_t i = 0; i < m_window.size(); i++) { + in[i] *= m_window[i]; + } +} + diff --git a/android/app/src/main/cpp/dsp/fftwindow.h b/android/app/src/main/cpp/dsp/fftwindow.h new file mode 100644 index 0000000..7148b21 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fftwindow.h @@ -0,0 +1,146 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FFTWINDOW_H +#define INCLUDE_FFTWINDOW_H + +#include +#include +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API FFTWindow { +public: + enum Function { + Bartlett, + BlackmanHarris, + Flattop, + Hamming, + Hanning, + Rectangle, + Kaiser, + Blackman, + BlackmanHarris7 + }; + + FFTWindow(); + + void create(Function function, int n); + void apply(const std::vector& in, std::vector* out); + void apply(const std::vector& in, std::vector* out); + void apply(std::vector& in); + void apply(const Complex* in, Complex* out); + void apply(Complex* in); + void setKaiserAlpha(Real alpha); //!< set the Kaiser window alpha factor (default 2.15) + void setKaiserBeta(Real beta); //!< set the Kaiser window beta factor = pi * alpha + +private: + std::vector m_window; + Real m_kaiserAlpha; //!< alpha factor for Kaiser window + Real m_kaiserI0Alpha; //!< zeroethOrderBessel of alpha above + + static inline Real flatTop(Real n, Real i) + { + // correction ? + return 1.0 - 1.93 * cos((2.0 * M_PI * i) / n) + 1.29 * cos((4.0 * M_PI * i) / n) - 0.388 * cos((6.0 * M_PI * i) / n) + 0.03222 * cos((8.0 * M_PI * i) / n); + } + + static inline Real bartlett(Real n, Real i) + { + // amplitude correction = 2.0 + return (2.0 / (n - 1.0)) * ( (n - 1.0) / 2.0 - fabs(i - (n - 1.0) / 2.0)) * 2.0; + } + + static inline Real blackmanHarris(Real n, Real i) // 4 term Blackman-Harris + { + // amplitude correction = 2.79 + return (0.35875 + - 0.48829 * cos((2.0 * M_PI * i) / n) + + 0.14128 * cos((4.0 * M_PI * i) / n) + - 0.01168 * cos((6.0 * M_PI * i) / n)) * 2.79; + } + + static inline Real blackmanHarris7(Real n, Real i) // 7 term Blackman-Harris + { + return (0.27105 + - 0.43330 * cos((2.0 * M_PI * i) / n) + + 0.21812 * cos((4.0 * M_PI * i) / n) + - 0.065925 * cos((6.0 * M_PI * i) / n) + + 0.010812 * cos((8.0 * M_PI * i) / n) + - 0.00077658 * cos((10.0 * M_PI * i) / n) + + 0.000013887 * cos((12.0 * M_PI * i) / n)) * 3.72; + } + + static inline Real blackman(Real n, Real i) // 3 term Blackman + { + return (0.42438 + - 0.49734 * cos(2.0 * M_PI * i / n) + + 0.078279 * cos(4.0 * M_PI * i / n)) * 2.37; + } + + static inline Real hamming(Real n, Real i) + { + // amplitude correction = 1.855, energy correction = 1.586 + return (0.54 - 0.46 * cos((2.0 * M_PI * i) / n)) * 1.855; + } + + static inline Real hanning(Real n, Real i) + { + // amplitude correction = 2.0, energy correction = 1.633 + return (0.5 - 0.5 * cos((2.0 * M_PI * i) / n)) * 2.0; + } + + static inline Real rectangle(Real, Real) + { + return 1.0; + } + + // https://raw.githubusercontent.com/johnglover/simpl/master/src/loris/KaiserWindow.C + inline Real kaiser(Real n, Real i) + { + Real K = ((2.0*i) / n) - 1.0; + Real arg = sqrt(1.0 - (K*K)); + return zeroethOrderBessel(m_kaiserAlpha*arg) / m_kaiserI0Alpha; + } + + static inline Real zeroethOrderBessel( Real x ) + { + const Real eps = 0.000001; + + // initialize the series term for m=0 and the result + Real besselValue = 0; + Real term = 1; + Real m = 0; + + // accumulate terms as long as they are significant + while(term > eps * besselValue) + { + besselValue += term; + + // update the term + ++m; + term *= (x*x) / (4*m*m); + } + + return besselValue; + } +}; + +#endif // INCLUDE_FFTWINDOWS_H diff --git a/android/app/src/main/cpp/dsp/filerecord.cpp b/android/app/src/main/cpp/dsp/filerecord.cpp new file mode 100644 index 0000000..e60b054 --- /dev/null +++ b/android/app/src/main/cpp/dsp/filerecord.cpp @@ -0,0 +1,250 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2018 beta-tester // +// Copyright (C) 2020 Felix Schneider // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// Copyright (C) 2021 Andreas Baulig // +// Copyright (C) 2021 Christoph Berg // +// Copyright (C) 2022 CRD716 // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include + +#include "dsp/dspcommands.h" +#include "util/message.h" + +#include "filerecord.h" + +FileRecord::FileRecord(quint32 sampleRate, quint64 centerFrequency) : + FileRecordInterface(), + m_fileBase("test"), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency), + m_recordOn(false), + m_recordStart(false), + m_byteCount(0), + m_msShift(0) +{ + setObjectName("FileRecord"); +} + +FileRecord::FileRecord(const QString& fileBase) : + FileRecordInterface(), + m_fileBase(fileBase), + m_sampleRate(0), + m_centerFrequency(0), + m_recordOn(false), + m_recordStart(false), + m_byteCount(0) +{ + setObjectName("FileRecord"); +} + +FileRecord::~FileRecord() +{ + stopRecording(); +} + +void FileRecord::setFileName(const QString& fileBase) +{ + if (!m_recordOn) + { + m_fileBase = fileBase; + } +} + +void FileRecord::genUniqueFileName(uint deviceUID, int istream) +{ + if (istream < 0) { + setFileName(QString("rec%1_%2.sdriq").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"))); + } else { + setFileName(QString("rec%1_%2_%3.sdriq").arg(deviceUID).arg(istream).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"))); + } +} + +void FileRecord::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) +{ + QMutexLocker mutexLocker(&m_mutex); + + (void) positiveOnly; + + // if no recording is active, send the samples to /dev/null + if(!m_recordOn) + return; + + if (begin < end) // if there is something to put out + { + if (m_recordStart) + { + writeHeader(); + m_recordStart = false; + } + + m_sampleFile.write(reinterpret_cast(&*(begin)), (end - begin)*sizeof(Sample)); + m_byteCount += end - begin; + } +} + +void FileRecord::start() +{ +} + +void FileRecord::stop() +{ + stopRecording(); +} + +bool FileRecord::startRecording() +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_recordOn) { + stopRecording(); + } + +#ifdef ANDROID + if (!m_sampleFile.isOpen()) +#else + if (!m_sampleFile.is_open()) +#endif + { + qDebug() << "FileRecord::startRecording"; +#ifdef ANDROID + // FIXME: No idea how to write to a file where the filename doesn't come from the file picker + m_currentFileName = m_fileBase + ".sdriq"; + m_sampleFile.setFileName(m_currentFileName); + if (!m_sampleFile.open(QIODevice::ReadWrite)) + { + qWarning() << "FileRecord::startRecording: failed to open file: " << m_currentFileName << " error " << m_sampleFile.error(); + return false; + } +#else + m_currentFileName = m_fileBase + "." + QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz") + ".sdriq"; // Don't use QString::arg on Android, as filename can contain %2 + m_sampleFile.open(m_currentFileName.toStdString().c_str(), std::ios::binary); + if (!m_sampleFile.is_open()) + { + qWarning() << "FileRecord::startRecording: failed to open file: " << m_currentFileName; + return false; + } +#endif + m_recordOn = true; + m_recordStart = true; + m_byteCount = 0; + } + return true; +} + +bool FileRecord::stopRecording() +{ + QMutexLocker mutexLocker(&m_mutex); + +#ifdef ANDROID + if (m_sampleFile.isOpen()) +#else + if (m_sampleFile.is_open()) +#endif + { + qDebug() << "FileRecord::stopRecording"; + m_sampleFile.close(); + m_recordOn = false; + m_recordStart = false; +#ifdef ANDROID +#else + if (m_sampleFile.bad()) + { + qWarning() << "FileRecord::stopRecording: an error occurred while writing to " << m_currentFileName; + return false; + } +#endif + } + return true; +} + +bool FileRecord::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) message; + quint32 sampleRate = notif.getSampleRate(); + qint64 centerFrequency = notif.getCenterFrequency(); + qDebug() << "FileRecord::handleMessage: DSPSignalNotification: inputSampleRate: " << sampleRate + << " centerFrequency: " << centerFrequency; + + if (m_recordOn && (m_sampleRate != sampleRate)) { + startRecording(); + } + + m_sampleRate = sampleRate; + m_centerFrequency = centerFrequency; + + return true; + } + else + { + return false; + } +} + +void FileRecord::writeHeader() +{ + Header header; + header.sampleRate = m_sampleRate; + header.centerFrequency = m_centerFrequency; + qint64 ts = QDateTime::currentMSecsSinceEpoch(); + header.startTimeStamp = (quint64)(ts + m_msShift); + header.sampleSize = SDR_RX_SAMP_SZ; + header.filler = 0; + + writeHeader(m_sampleFile, header); +} + +bool FileRecord::readHeader(std::ifstream& sampleFile, Header& header) +{ + sampleFile.read((char *) &header, sizeof(Header)); + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + return header.crc32 == crc32.checksum(); +} + +bool FileRecord::readHeader(QFile& sampleFile, Header& header) +{ + sampleFile.read((char *) &header, sizeof(Header)); + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + return header.crc32 == crc32.checksum(); +} + +void FileRecord::writeHeader(std::ofstream& sampleFile, Header& header) +{ + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + header.crc32 = crc32.checksum(); + sampleFile.write((const char *) &header, sizeof(Header)); +} + +void FileRecord::writeHeader(QFile& sampleFile, Header& header) +{ + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + header.crc32 = crc32.checksum(); + sampleFile.write((const char *) &header, sizeof(Header)); +} diff --git a/android/app/src/main/cpp/dsp/filerecord.h b/android/app/src/main/cpp/dsp/filerecord.h new file mode 100644 index 0000000..8128736 --- /dev/null +++ b/android/app/src/main/cpp/dsp/filerecord.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2016, 2018-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2018 beta-tester // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// Copyright (C) 2022 CRD716 // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FILERECORD_H +#define INCLUDE_FILERECORD_H + +#include + +#include +#include +#include +#include +#include + +#include "dsp/filerecordinterface.h" +#include "export.h" + +class Message; + +class SDRBASE_API FileRecord : public FileRecordInterface { +public: + +#pragma pack(push, 1) + struct Header + { + quint32 sampleRate; + quint64 centerFrequency; + quint64 startTimeStamp; + quint32 sampleSize; + quint32 filler; + quint32 crc32; + }; +#pragma pack(pop) + + FileRecord(quint32 sampleRate=0, quint64 centerFrequency=0); + FileRecord(const QString& fileBase); + virtual ~FileRecord(); + + quint64 getByteCount() const { return m_byteCount; } + void setMsShift(qint64 shift) { m_msShift = shift; } + const QString& getCurrentFileName() { return m_currentFileName; } + + void genUniqueFileName(uint deviceUID, int istream = -1); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& message); + + virtual void setFileName(const QString& fileBase); + virtual bool startRecording(); + virtual bool stopRecording(); + virtual bool isRecording() const { return m_recordOn; } + + static bool readHeader(std::ifstream& samplefile, Header& header); //!< returns true if CRC checksum is correct else false + static bool readHeader(QFile& samplefile, Header& header); //!< returns true if CRC checksum is correct else false + static void writeHeader(std::ofstream& samplefile, Header& header); + static void writeHeader(QFile& samplefile, Header& header); + +private: + QString m_fileBase; + quint32 m_sampleRate; + quint64 m_centerFrequency; + bool m_recordOn; + bool m_recordStart; +#ifdef ANDROID + QFile m_sampleFile; +#else + std::ofstream m_sampleFile; +#endif + QString m_currentFileName; + quint64 m_byteCount; + qint64 m_msShift; + QRecursiveMutex m_mutex; + + void writeHeader(); +}; + +#endif // INCLUDE_FILERECORD_H diff --git a/android/app/src/main/cpp/dsp/filerecordinterface.cpp b/android/app/src/main/cpp/dsp/filerecordinterface.cpp new file mode 100644 index 0000000..4b6c959 --- /dev/null +++ b/android/app/src/main/cpp/dsp/filerecordinterface.cpp @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// File recorder in SigMF format single channel for SI plugins // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "filerecordinterface.h" + +FileRecordInterface::FileRecordInterface() +{ + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +FileRecordInterface::~FileRecordInterface() +{} + +QString FileRecordInterface::genUniqueFileName(unsigned int deviceUID, int istream) +{ + if (istream < 0) { + return QString("rec%1.%2.sdriq").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz")); + } else { + return QString("rec%1_%2.%3.sdriq").arg(deviceUID).arg(istream).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz")); + } +} + +FileRecordInterface::RecordType FileRecordInterface::guessTypeFromFileName(const QString& fileName, QString& fileBase) +{ + QFileInfo fileInfo(fileName); + QString extension = fileInfo.suffix(); + + fileBase = fileName; + if (!extension.isEmpty()) + { + fileBase.chop(extension.size() + 1); + if (extension == "sdriq") + { + return RecordTypeSdrIQ; + } + else if (extension == "sigmf-meta") + { + return RecordTypeSigMF; + } + else if (extension == "wav") + { + return RecordTypeWav; + } + else + { + return RecordTypeUndefined; + } + } + else + { + return RecordTypeUndefined; + } +} + +void FileRecordInterface::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} diff --git a/android/app/src/main/cpp/dsp/filerecordinterface.h b/android/app/src/main/cpp/dsp/filerecordinterface.h new file mode 100644 index 0000000..a3d501b --- /dev/null +++ b/android/app/src/main/cpp/dsp/filerecordinterface.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// File recorder in SigMF format single channel for SI plugins // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FILERECORD_INTERFACE_H +#define INCLUDE_FILERECORD_INTERFACE_H + +#include +#include + +#include "dsp/dsptypes.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "export.h" + +class SDRBASE_API FileRecordInterface : public QObject { + Q_OBJECT +public: + enum RecordType + { + RecordTypeUndefined = 0, + RecordTypeSdrIQ, + RecordTypeSigMF, + RecordTypeWav + }; + + FileRecordInterface(); + virtual ~FileRecordInterface(); + + virtual void start() = 0; + virtual void stop() = 0; + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) = 0; + virtual bool handleMessage(const Message& cmd) = 0; //!< Processing of a message. Returns true if message has actually been processed + + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } + + virtual void setFileName(const QString &filename) = 0; + virtual const QString& getCurrentFileName() = 0; + virtual bool startRecording() = 0; + virtual bool stopRecording() = 0; + virtual bool isRecording() const = 0; + + virtual void setMsShift(qint64 msShift) = 0; + virtual int getBytesPerSample() { return sizeof(Sample); }; + + static QString genUniqueFileName(unsigned int deviceUID, int istream = -1); + static RecordType guessTypeFromFileName(const QString& fileName, QString& fileBase); + +protected: + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI + +protected slots: + void handleInputMessages(); + +}; + + +#endif // INCLUDE_FILERECORD_INTERFACE_H diff --git a/android/app/src/main/cpp/dsp/filtermbe.cpp b/android/app/src/main/cpp/dsp/filtermbe.cpp new file mode 100644 index 0000000..14ea131 --- /dev/null +++ b/android/app/src/main/cpp/dsp/filtermbe.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "filtermbe.h" + +const float MBEAudioInterpolatorFilter::m_lpa[3] = {1.0, 1.392667E+00, -5.474446E-01}; +const float MBEAudioInterpolatorFilter::m_lpb[3] = {3.869430E-02, 7.738860E-02, 3.869430E-02}; + +const float MBEAudioInterpolatorFilter::m_hpa[3] = {1.000000e+00, 1.667871e+00, -7.156964e-01}; +const float MBEAudioInterpolatorFilter::m_hpb[3] = {8.459039e-01, -1.691760e+00, 8.459039e-01}; + +MBEAudioInterpolatorFilter::MBEAudioInterpolatorFilter() : + m_filterLP(m_lpa, m_lpb), + m_filterHP(m_hpa, m_hpb), + m_useHP(false) +{ +} + +MBEAudioInterpolatorFilter::~MBEAudioInterpolatorFilter() +{} + +float MBEAudioInterpolatorFilter::run(const float& sample) +{ + return m_useHP ? m_filterLP.run(m_filterHP.run(sample)) : m_filterLP.run(sample); +} + +float MBEAudioInterpolatorFilter::runHP(const float& sample) +{ + return m_filterHP.run(sample); +} + +float MBEAudioInterpolatorFilter::runLP(const float& sample) +{ + return m_filterLP.run(sample); +} diff --git a/android/app/src/main/cpp/dsp/filtermbe.h b/android/app/src/main/cpp/dsp/filtermbe.h new file mode 100644 index 0000000..f64dc90 --- /dev/null +++ b/android/app/src/main/cpp/dsp/filtermbe.h @@ -0,0 +1,91 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_FILTERMBE_H_ +#define SDRBASE_DSP_FILTERMBE_H_ + +/** + * Uses the generic IIR filter internally + * + * Low pass / High pass: + * + * This is a 2 pole Chebyshev (recursive) filter using coefficients found in table 20-1 (low pass) + * or table 20-2 (high pass) of http://www.analog.com/media/en/technical-documentation/dsp-book/dsp_book_Ch20.pdf + * + * For low pass fc = 0.075 + * For high oass fc = 0.01 + * + * Convention taken here exchanges A and B coefficients as shown in this image: + * https://cdn.mikroe.com/ebooks/img/8/2016/02/digital-filter-design-chapter-03-image-2-9.gif + * So A applies to Y and B to X + * + * At the interpolated sampling frequency of 48 kHz the -3 dB corner is at 48 * .075 = 3.6 kHz which is perfect for voice + * The high pass has a 3 dB corner of 48 * 0.01 = 0.48 kHz + * + * Low pass: + * + * b0 = 3.869430E-02 (a0 = 1.0) + * b1 = 7.738860E-02 a1 = 1.392667E+00 + * b2 = 3.869430E-02 a2 = -5.474446E-01 + * + * High pass: + * + * b0 = 9.567529E-01 (a0 = 1.0) + * b1 = -1.913506E+00 a1 = 1.911437E+00 + * b2 = 9.567529E-01 a2 = -9.155749E-01 + * + * given x[n] is the new input sample and y[n] the returned output sample: + * + * y[n] = b0*x[n] + b1*x[n] + b2*x[n] + a1*y[n-1] + a2*y[n-2] + * + * This one works directly with floats + * + * + */ + +#include "iirfilter.h" +#include "export.h" + +class SDRBASE_API MBEAudioInterpolatorFilter +{ +public: + MBEAudioInterpolatorFilter(); + ~MBEAudioInterpolatorFilter(); + + void useHP(bool useHP) { m_useHP = useHP; } + bool usesHP() const { return m_useHP; } + float run(const float& sample); + float runHP(const float& sample); + float runLP(const float& sample); + +private: + IIRFilter m_filterLP; + IIRFilter m_filterHP; + bool m_useHP; + // low pass coefficients + static const float m_lpa[3]; + static const float m_lpb[3]; + // band pass coefficients + static const float m_hpa[3]; + static const float m_hpb[3]; +}; + + +#endif /* SDRBASE_DSP_FILTERMBE_H_ */ diff --git a/android/app/src/main/cpp/dsp/filterrc.cpp b/android/app/src/main/cpp/dsp/filterrc.cpp new file mode 100644 index 0000000..d6f485a --- /dev/null +++ b/android/app/src/main/cpp/dsp/filterrc.cpp @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "dsp/filterrc.h" + +// Construct 1st order low-pass IIR filter. +LowPassFilterRC::LowPassFilterRC(Real timeconst) : + m_timeconst(timeconst), + m_y1(0) +{ + m_a1 = - exp(-1/m_timeconst); + m_b0 = 1 + m_a1; +} + +// Reconfigure +void LowPassFilterRC::configure(Real timeconst) +{ + m_timeconst = timeconst; + m_y1 = 0; + m_a1 = - exp(-1/m_timeconst); + m_b0 = 1 + m_a1; + + qDebug() << "LowPassFilterRC::configure: t: " << m_timeconst + << " a1: " << m_a1 + << " b0: " << m_b0; +} + +// Process samples. +void LowPassFilterRC::process(const Real& sample_in, Real& sample_out) +{ + /* + * Continuous domain: + * H(s) = 1 / (1 - s * timeconst) + * + * Discrete domain: + * H(z) = (1 - exp(-1/timeconst)) / (1 - exp(-1/timeconst) / z) + */ + + m_y1 = (sample_in * m_b0) - (m_y1 * m_a1); + sample_out = m_y1; +} + +// Construct 1st order high-pass IIR filter. +HighPassFilterRC::HighPassFilterRC(Real timeconst) : + m_timeconst(timeconst), + m_y1(0) +{ + m_a1 = 1 - exp(-1/m_timeconst); + m_b0 = 1 + m_a1; +} + +// Reconfigure +void HighPassFilterRC::configure(Real timeconst) +{ + m_timeconst = timeconst; + m_y1 = 0; + m_a1 = 1 - exp(-1/m_timeconst); + m_b0 = 1 + m_a1; + + qDebug() << "HighPassFilterRC::configure: t: " << m_timeconst + << " a1: " << m_a1 + << " b0: " << m_b0; +} + +// Process samples. +void HighPassFilterRC::process(const Real& sample_in, Real& sample_out) +{ + m_y1 = (sample_in * m_b0) - (m_y1 * m_a1); + sample_out = m_y1; +} diff --git a/android/app/src/main/cpp/dsp/filterrc.h b/android/app/src/main/cpp/dsp/filterrc.h new file mode 100644 index 0000000..ffdcdaa --- /dev/null +++ b/android/app/src/main/cpp/dsp/filterrc.h @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSP_FILTERRC_H_ +#define INCLUDE_DSP_FILTERRC_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +/** First order low-pass IIR filter for real-valued signals. */ +class SDRBASE_API LowPassFilterRC +{ +public: + + /** + * Construct 1st order low-pass IIR filter. + * + * timeconst :: RC time constant in seconds (1 / (2 * PI * cutoff_freq) + */ + LowPassFilterRC(Real timeconst); + + /** + * Reconfigure filter with new time constant + */ + void configure(Real timeconst); + + /** Process samples. */ + void process(const Real& sample_in, Real& sample_out); + +private: + Real m_timeconst; + Real m_y1; + Real m_a1; + Real m_b0; +}; + +/** First order high-pass IIR filter for real-valued signals. */ +class SDRBASE_API HighPassFilterRC +{ +public: + + /** + * Construct 1st order high-pass IIR filter. + * + * timeconst :: RC time constant in seconds (1 / (2 * PI * cutoff_freq) + */ + HighPassFilterRC(Real timeconst); + + /** + * Reconfigure filter with new time constant + */ + void configure(Real timeconst); + + /** Process samples. */ + void process(const Real& sample_in, Real& sample_out); + +private: + Real m_timeconst; + Real m_y1; + Real m_a1; + Real m_b0; +}; + +#endif /* INCLUDE_DSP_FILTERRC_H_ */ diff --git a/android/app/src/main/cpp/dsp/firfilter.cpp b/android/app/src/main/cpp/dsp/firfilter.cpp new file mode 100644 index 0000000..0b1fb83 --- /dev/null +++ b/android/app/src/main/cpp/dsp/firfilter.cpp @@ -0,0 +1,73 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2017, 2019-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "firfilter.h" +#include + +namespace FirFilterGenerators +{ + +void generateLowPassFilter(int nTaps, double sampleRate, double cutoff, std::vector &taps) +{ + if (!(nTaps & 1)) + { + printf("Filter has to have an odd number of taps\n"); + nTaps++; + } + + double Wc = (2.0 * M_PI * cutoff) / sampleRate; + int halfTaps = nTaps / 2 + 1; + taps.resize(halfTaps); + + for (int i = 0; i < halfTaps; ++i) + { + if (i == halfTaps - 1) + { + taps[i] = Wc / M_PI; + } + else + { + int n = i - (nTaps - 1) / 2; + taps[i] = sin(n * Wc) / (n * M_PI); + } + } + + // Blackman window + for (int i = 0; i < halfTaps; i++) + { + int n = i - (nTaps - 1) / 2; + taps[i] *= 0.42 + 0.5 * cos((2.0 * M_PI * n) / nTaps) + 0.08 * cos((4.0 * M_PI * n) / nTaps); + } + + Real sum = 0; + size_t i; + + for (i = 0; i < taps.size() - 1; ++i) { + sum += taps[i] * 2.0; + } + + sum += taps[i]; + + for (i = 0; i < taps.size(); ++i) { + taps[i] /= sum; + } +} + +} diff --git a/android/app/src/main/cpp/dsp/firfilter.h b/android/app/src/main/cpp/dsp/firfilter.h new file mode 100644 index 0000000..25a8e01 --- /dev/null +++ b/android/app/src/main/cpp/dsp/firfilter.h @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/basebandsamplesink.h" +#include "export.h" + +namespace FirFilterGenerators +{ + SDRBASE_API void generateLowPassFilter(int nTaps, double sampleRate, double cutoff, std::vector &taps); +}; + +template +class FirFilter +{ +public: + Type filter(Type sample) + { + Type acc = 0; + unsigned int n_samples = m_samples.size(); + unsigned int n_taps = m_taps.size() - 1; + unsigned int a = m_ptr; + unsigned int b = a == n_samples - 1 ? 0 : a + 1; + + m_samples[m_ptr] = sample; + + for (unsigned int i = 0; i < n_taps; ++i) + { + acc += (m_samples[a] + m_samples[b]) * m_taps[i]; + + a = (a == 0) ? n_samples - 1 : a - 1; + b = (b == n_samples - 1) ? 0 : b + 1; + } + + acc += m_samples[a] * m_taps[n_taps]; + + m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1; + + return acc; + } + + // Print taps as a Matlab vector + // To view: + // h=fvtool(filter); + // h.Fs=... + void printTaps(const char *name) + { + printf("%s = [", name); + for (int i = 0; i <= m_taps.size() - 1; ++i) { + printf("%g ", m_taps[i]); + } + for (int i = m_taps.size() - 2; i >= 0; --i) { + printf("%g ", m_taps[i]); + } + printf("];\n"); + } + +protected: + void init(int nTaps) + { + m_ptr = 0; + m_samples.resize(nTaps); + + for (int i = 0; i < nTaps; i++) { + m_samples[i] = 0; + } + } + +protected: + std::vector m_taps; + std::vector m_samples; + size_t m_ptr; +}; + +template +struct Lowpass : public FirFilter +{ +public: + void create(int nTaps, double sampleRate, double cutoff) + { + this->init(nTaps); + FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, cutoff, this->m_taps); + } +}; + +template +struct Bandpass : public FirFilter +{ + void create(int nTaps, double sampleRate, double lowCutoff, double highCutoff) + { + this->init(nTaps); + FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, highCutoff, this->m_taps); + std::vector highPass; + FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, lowCutoff, highPass); + + for (size_t i = 0; i < highPass.size(); ++i) { + highPass[i] = -highPass[i]; + } + + highPass[highPass.size() - 1] += 1; + + for (size_t i = 0; i < this->m_taps.size(); ++i) { + this->m_taps[i] = -(this->m_taps[i] + highPass[i]); + } + + this->m_taps[this->m_taps.size() - 1] += 1; + } +}; + +template +struct Highpass : public FirFilter +{ + void create(int nTaps, double sampleRate, double cutoff) + { + this->init(nTaps); + FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, cutoff, this->m_taps); + + for (size_t i = 0; i < this->m_taps.size(); ++i) { + this->m_taps[i] = -this->m_taps[i]; + } + + this->m_taps[this->m_taps.size() - 1] += 1; + } +}; diff --git a/android/app/src/main/cpp/dsp/fmpreemphasis.cpp b/android/app/src/main/cpp/dsp/fmpreemphasis.cpp new file mode 100644 index 0000000..82c837f --- /dev/null +++ b/android/app/src/main/cpp/dsp/fmpreemphasis.cpp @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// Copyright (C) 2005,2007,2012 Free Software Foundation, Inc. +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "dsp/fmpreemphasis.h" + +FMPreemphasis::FMPreemphasis(int sampleRate, Real tau, Real highFreq) +{ + configure(sampleRate, tau, highFreq); +} + +void FMPreemphasis::configure(int sampleRate, Real tau, Real highFreq) +{ + // Based on: https://github.com/gnuradio/gnuradio/blob/master/gr-analog/python/analog/fm_emph.py + // Compare to freq response in https://www.mathworks.com/help/comm/ref/comm.fmbroadcastmodulator-system-object.html + + // High frequency corner at which to flatten the gain + double fh = std::min((double)highFreq, 0.925 * sampleRate/2.0); + + // Digital corner frequencies + double w_cl = 1.0 / tau; + double w_ch = 2.0 * M_PI * fh; + + // Prewarped analog corner frequencies + double w_cla = 2.0 * sampleRate * std::tan(w_cl / (2.0 * sampleRate)); + double w_cha = 2.0 * sampleRate * std::tan(w_ch / (2.0 * sampleRate)); + + // Resulting digital pole, zero, and gain term from the bilinear + // transformation of H(s) = (s + w_cla) / (s + w_cha) to + // H(z) = b0 (1 - z1 z^-1)/(1 - p1 z^-1) + double kl = -w_cla / (2.0 * sampleRate); + double kh = -w_cha / (2.0 * sampleRate); + double z1 = (1.0 + kl) / (1.0 - kl); + double p1 = (1.0 + kh) / (1.0 - kh); + double b0 = (1.0 - kl) / (1.0 - kh); + + // Adjust with a gain, g, so 0 dB gain at DC + double g = std::abs(1.0 - p1) / (b0 * std::abs(1.0 - z1)); + + // Calculate IIR taps + m_b0 = (Real)(g * b0 * 1.0); + m_b1 = (Real)(g * b0 * -z1); + m_a1 = (Real)-p1; + // Zero delay line so we get reproducible results + m_z = 0; + + qDebug() << "FMPreemphasis::configure: tau: " << tau + << " sampleRate: " << sampleRate + << " b0: " << m_b0 + << " b1: " << m_b1 + << " a1: " << m_a1; +} + +Real FMPreemphasis::filter(const Real sampleIn) +{ + Real sampleOut; + + // See Transposed Direct form 2 - https://en.wikipedia.org/wiki/Digital_biquad_filter + sampleOut = sampleIn * m_b0 + m_z; + m_z = sampleIn * m_b1 + sampleOut * -m_a1; + + return sampleOut; +} diff --git a/android/app/src/main/cpp/dsp/fmpreemphasis.h b/android/app/src/main/cpp/dsp/fmpreemphasis.h new file mode 100644 index 0000000..f715464 --- /dev/null +++ b/android/app/src/main/cpp/dsp/fmpreemphasis.h @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSP_FMPREEMPHASIS_H_ +#define INCLUDE_DSP_FMPREEMPHASIS_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +#define FMPREEMPHASIS_TAU_EU 50e-6f +#define FMPREEMPHASIS_TAU_US 75e-6f + +/** FM preemphasis filter. + * Amplifies frequencies above ~3.2k (tau=50e-6 in EU) or ~2.1k (tau=75e-6 in US) + * at ~6dB per octave, and then flattens at 12k (highFreq). + * Frequency response: + * highFreq + * ------- + * / + * / + * -------/ + * 1/(2*pi*tau) + */ +class SDRBASE_API FMPreemphasis +{ +public: + + FMPreemphasis(int sampleRate, Real tau = FMPREEMPHASIS_TAU_EU, Real highFreq = 12000.0); + + void configure(int sampleRate, Real tau = FMPREEMPHASIS_TAU_EU, Real highFreq = 12000.0); + + Real filter(Real sampleIn); + +private: + Real m_z; // Delay element + Real m_b0; // IIR numerator taps + Real m_b1; + Real m_a1; // IIR denominator taps +}; + +#endif /* INCLUDE_DSP_FMPREEMPHASIS_H_ */ diff --git a/android/app/src/main/cpp/dsp/freqlockcomplex.cpp b/android/app/src/main/cpp/dsp/freqlockcomplex.cpp new file mode 100644 index 0000000..542da59 --- /dev/null +++ b/android/app/src/main/cpp/dsp/freqlockcomplex.cpp @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "freqlockcomplex.h" +#include + +FreqLockComplex::FreqLockComplex() : + m_a0(0.998), + m_a1(0.002), + m_y(1.0, 0.0), + m_yRe(1.0), + m_yIm(0.0), + m_freq(0.0), + m_phi(0.0), + m_phiX0(0.0), + m_phiX1(0.0), + m_y1(0.0f) +{ +} + +FreqLockComplex::~FreqLockComplex() +{ +} + +void FreqLockComplex::reset() +{ + m_y.real(1.0); + m_y.imag(0.0); + m_yRe = 1.0f; + m_yIm = 0.0f; + m_freq = 0.0f; + m_phi = 0.0f; + m_phiX0 = 0.0f; + m_phiX1 = 0.0f; + m_y1 = 0.0f; +} + +void FreqLockComplex::setSampleRate(unsigned int sampleRate) +{ + m_a1 = 10.0f / sampleRate; // 1 - alpha + m_a0 = 1.0f - m_a1; // alpha + reset(); +} + +void FreqLockComplex::feed(float re, float im) +{ + m_yRe = cos(m_phi); + m_yIm = sin(m_phi); + m_y.real(m_yRe); + m_y.imag(m_yIm); + std::complex x(re, im); + m_phiX0 = std::arg(x); + + float eF = normalizeAngle(m_phiX0 - m_phiX1); + float fHat = m_a1*eF + m_a0*m_y1; + m_y1 = fHat; + + m_freq = fHat; // correct instantaneous frequency + m_phi += fHat; // advance phase with instantaneous frequency + m_phiX1 = m_phiX0; +} + +float FreqLockComplex::normalizeAngle(float angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} + diff --git a/android/app/src/main/cpp/dsp/freqlockcomplex.h b/android/app/src/main/cpp/dsp/freqlockcomplex.h new file mode 100644 index 0000000..8e97343 --- /dev/null +++ b/android/app/src/main/cpp/dsp/freqlockcomplex.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_FREQLOCKCOMPLEX_H_ +#define SDRBASE_DSP_FREQLOCKCOMPLEX_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +/** General purpose Phase-locked loop using complex analytic signal input. */ +class SDRBASE_API FreqLockComplex +{ +public: + FreqLockComplex(); + ~FreqLockComplex(); + + void reset(); + void setSampleRate(unsigned int sampleRate); + /** Feed PLL with a new signa sample */ + void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_yRe; } + float getImag() const { return m_yIm; } + float getFreq() const { return m_freq; } + +private: + /** Normalize angle in radians into the [-pi,+pi] region */ + static float normalizeAngle(float angle); + + float m_a0; + float m_a1; + std::complex m_y; + float m_yRe; + float m_yIm; + float m_freq; + float m_phi; + float m_phiX0; + float m_phiX1; + float m_y1; +}; + + +#endif /* SDRBASE_DSP_FREQLOCKCOMPLEX_H_ */ diff --git a/android/app/src/main/cpp/dsp/gaussian.h b/android/app/src/main/cpp/dsp/gaussian.h new file mode 100644 index 0000000..6907440 --- /dev/null +++ b/android/app/src/main/cpp/dsp/gaussian.h @@ -0,0 +1,122 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GAUSSIAN_H +#define INCLUDE_GAUSSIAN_H + +#include +#include "dsp/dsptypes.h" + +// Standard values for bt +#define GAUSSIAN_BT_BLUETOOTH 0.5 +#define GAUSSIAN_BT_GSM 0.3 +#define GAUSSIAN_BT_CCSDS 0.25 +#define GAUSSIAN_BT_802_15_4 0.5 +#define GAUSSIAN_BT_AIS 0.5 + +// Gaussian low-pass filter for pulse shaping +// https://onlinelibrary.wiley.com/doi/pdf/10.1002/9780470041956.app2 +// Unlike raisedcosine.h, this should be feed NRZ values rather than impulse stream, as described here: +// https://www.mathworks.com/matlabcentral/answers/107231-why-does-the-pulse-shape-generated-by-gaussdesign-differ-from-that-used-in-the-comm-gmskmodulator-ob +template class Gaussian { +public: + Gaussian() : m_ptr(0) { } + + // bt - 3dB bandwidth symbol time product + // symbolSpan - number of symbols over which the filter is spread + // samplesPerSymbol - number of samples per symbol + void create(double bt, int symbolSpan, int samplesPerSymbol) + { + int nTaps = symbolSpan * samplesPerSymbol + 1; + int i; + + // check constraints + if(!(nTaps & 1)) { + qDebug("Gaussian filter has to have an odd number of taps"); + nTaps++; + } + + // make room + m_samples.resize(nTaps); + for(int i = 0; i < nTaps; i++) + m_samples[i] = 0; + m_ptr = 0; + m_taps.resize(nTaps / 2 + 1); + + // See eq B.2 - this is alpha over Ts + double alpha_t = std::sqrt(std::log(2.0) / 2.0) / (bt); + double sqrt_pi_alpha_t = std::sqrt(M_PI) / alpha_t; + + // calculate filter taps + for(i = 0; i < nTaps / 2 + 1; i++) + { + double t = (i - (nTaps / 2)) / (double)samplesPerSymbol; + + // See eq B.5 + m_taps[i] = sqrt_pi_alpha_t * std::exp(-std::pow(t * M_PI / alpha_t, 2.0)); + } + + // normalize + double sum = 0; + for(i = 0; i < (int)m_taps.size() - 1; i++) + sum += m_taps[i] * 2; + sum += m_taps[i]; + for(i = 0; i < (int)m_taps.size(); i++) + m_taps[i] /= sum; + } + + Type filter(Type sample) + { + Type acc = 0; + unsigned int n_samples = m_samples.size(); + unsigned int n_taps = m_taps.size() - 1; + unsigned int a = m_ptr; + unsigned int b = a == n_samples - 1 ? 0 : a + 1; + + m_samples[m_ptr] = sample; + + for (unsigned int i = 0; i < n_taps; ++i) + { + acc += (m_samples[a] + m_samples[b]) * m_taps[i]; + + a = (a == 0) ? n_samples - 1 : a - 1; + b = (b == n_samples - 1) ? 0 : b + 1; + } + + acc += m_samples[a] * m_taps[n_taps]; + + m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1; + + return acc; + } +/* + void printTaps() + { + for (int i = 0; i < m_taps.size(); i++) + printf("%.4f ", m_taps[i]); + printf("\n"); + } +*/ +private: + std::vector m_taps; + std::vector m_samples; + unsigned int m_ptr; +}; + +#endif // INCLUDE_GAUSSIAN_H diff --git a/android/app/src/main/cpp/dsp/gfft.h b/android/app/src/main/cpp/dsp/gfft.h new file mode 100644 index 0000000..673284b --- /dev/null +++ b/android/app/src/main/cpp/dsp/gfft.h @@ -0,0 +1,3403 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +//============================================================================== +// g_fft.h: +// +// FFT library +// Copyright (C) 2013 +// Dave Freese, W1HKJ +// +// based on public domain code by John Green +// original version is available at +// http://hyperarchive.lcs.mit.edu/ +// /HyperArchive/Archive/dev/src/ffts-for-risc-2-c.hqx +// +// ported to C++ for fldigi by Dave Freese, W1HKJ +// +// This file is part of fldigi. +// +// Fldigi is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Fldigi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with fldigi. If not, see . +//============================================================================== + +#ifndef CGREEN_FFT_H +#define CGREEN_FFT_H + +#include + +template +class g_fft { + +#define FFT_RECIPLN2 1.442695040888963407359924681001892137426 // 1.0/log(2) + +// some useful conversions between a number and its power of 2 +#define LOG2(a) (FFT_RECIPLN2*log(a)) // floating point logarithm base 2 +#define POW2(m) ((unsigned int) 1 << (m)) // integer power of 2 for m<32 + +// fft's with M bigger than this bust primary cache +#define MCACHE (11 - (sizeof(FFT_TYPE) / 8)) + +// some math constants to 40 decimal places +#define FFT_PI 3.141592653589793238462643383279502884197 // pi +#define FFT_ROOT2 1.414213562373095048801688724209698078569 // sqrt(2) +#define FFT_COSPID8 0.9238795325112867561281831893967882868224 // cos(pi/8) +#define FFT_SINPID8 0.3826834323650897717284599840303988667613 // sin(pi/8) + +private: + int FFT_size; + int FFT_N; + FFT_TYPE *FFT_table_1[32]; + short int *FFT_table_2[32]; + + FFT_TYPE *Utbl; + short *BRLow; + + void fftInit(); + int ConvertFFTSize(int); + +// base fft methods + void riffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow); + void ifrstage(FFT_TYPE *ioptr, int M, FFT_TYPE *inUtbl); + void rifft8pt(FFT_TYPE *ioptr, FFT_TYPE scale); + void rifft4pt(FFT_TYPE *ioptr, FFT_TYPE scale); + void rifft2pt(FFT_TYPE *ioptr, FFT_TYPE scale); + void rifft1pt(FFT_TYPE *ioptr, FFT_TYPE scale); + void rffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow); + void frstage(FFT_TYPE *ioptr, int M, FFT_TYPE *inUtbl); + void rfft8pt(FFT_TYPE *ioptr); + void rfft4pt(FFT_TYPE *ioptr); + void rfft2pt(FFT_TYPE *ioptr); + void rfft1pt(FFT_TYPE *ioptr); + void iffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow); + void ifftrecurs(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, int Ustride, + int NDiffU, int StageCnt); + void ibfstages(FFT_TYPE *ioptr, int M, FFT_TYPE *inUtbl, int Ustride, + int NDiffU, int StageCnt); + void ibfR4(FFT_TYPE *ioptr, int M, int NDiffU); + void ibfR2(FFT_TYPE *ioptr, int M, int NDiffU); + void ifft8pt(FFT_TYPE *ioptr, FFT_TYPE scale); + void ifft4pt(FFT_TYPE *ioptr, FFT_TYPE scale); + void ifft2pt(FFT_TYPE *ioptr, FFT_TYPE scale); + void scbitrevR2(FFT_TYPE *ioptr, int M, short *inBRLow, FFT_TYPE scale); + void ffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow); + void fftrecurs(FFT_TYPE *ioptr, int M, + FFT_TYPE *Utbl, int Ustride, int NDiffU, + int StageCnt); + void bfstages(FFT_TYPE *ioptr, int M, + FFT_TYPE *inUtbl, int Ustride, + int NDiffU, int StageCnt); + void bfR4(FFT_TYPE *ioptr, int M, int NDiffU); + void bfR2(FFT_TYPE *ioptr, int M, int NDiffU); + void fft8pt(FFT_TYPE *ioptr); + void fft4pt(FFT_TYPE *ioptr); + void fft2pt(FFT_TYPE *ioptr); + void bitrevR2(FFT_TYPE *ioptr, int M, short *inBRLow); + void fftBRInit(int M, short *BRLow); + void fftCosInit(int M, FFT_TYPE *Utbl); + +public: + g_fft (int M = 8192) + { + if (M < 16) M = 16; + if (M > 268435456) M = 268435456; + FFT_size = M; + fftInit(); + } + + ~g_fft() + { + for (int i = 0; i < 32; i++) + { + if (FFT_table_1[i] != 0) delete [] FFT_table_1[i]; + if (FFT_table_2[i] != 0) delete [] FFT_table_2[i]; + } + } + + void ComplexFFT(std::complex *buf); + void InverseComplexFFT(std::complex *buf); + void RealFFT(std::complex *buf); + void InverseRealFFT(std::complex *buf); + FFT_TYPE GetInverseComplexFFTScale(); + FFT_TYPE GetInverseRealFFTScale(); +}; + +//------------------------------------------------------------------------------ +// Compute Utbl, the cosine table for ffts +// of size (pow(2,M)/4 +1) +// INPUTS +// M = log2 of fft size +// OUTPUTS +// *Utbl = cosine table +//------------------------------------------------------------------------------ +template +void g_fft::fftCosInit(int M, FFT_TYPE *Utbl) +{ + unsigned int fftN = POW2(M); + unsigned int i1; + + Utbl[0] = FFT_TYPE(1.0); + for (i1 = 1; i1 < fftN/4; i1++) + Utbl[i1] = (FFT_TYPE)cos((2.0 * FFT_PI * (float)i1) / (float)fftN); + Utbl[fftN/4] = FFT_TYPE(0.0); +} + +//------------------------------------------------------------------------------ +// Compute BRLow, the bit reversed table for ffts +// of size pow(2,M/2 -1) +// INPUTS +// M = log2 of fft size +// OUTPUTS +// *BRLow = bit reversed counter table +//------------------------------------------------------------------------------ +template +void g_fft::fftBRInit(int M, short *BRLow) +{ + int Mroot_1 = M / 2 - 1; + int Nroot_1 = POW2(Mroot_1); + int i1; + int bitsum; + int bitmask; + int bit; + + for (i1 = 0; i1 < Nroot_1; i1++) { + bitsum = 0; + bitmask = 1; + for (bit = 1; bit <= Mroot_1; bitmask <<= 1, bit++) + if (i1 & bitmask) + bitsum = bitsum + (Nroot_1 >> bit); + BRLow[i1] = bitsum; + } +} + +//------------------------------------------------------------------------------ +// parts of ffts1 +// bit reverse and first radix 2 stage of forward or inverse fft +//------------------------------------------------------------------------------ +template +void g_fft::bitrevR2(FFT_TYPE *ioptr, int M, short *inBRLow) +{ + FFT_TYPE f0r; + FFT_TYPE f0i; + FFT_TYPE f1r; + FFT_TYPE f1i; + FFT_TYPE f2r; + FFT_TYPE f2i; + FFT_TYPE f3r; + FFT_TYPE f3i; + FFT_TYPE f4r; + FFT_TYPE f4i; + FFT_TYPE f5r; + FFT_TYPE f5i; + FFT_TYPE f6r; + FFT_TYPE f6i; + FFT_TYPE f7r; + FFT_TYPE f7i; + FFT_TYPE t0r; + FFT_TYPE t0i; + FFT_TYPE t1r; + FFT_TYPE t1i; + FFT_TYPE *p0r; + FFT_TYPE *p1r; + FFT_TYPE *IOP; + FFT_TYPE *iolimit; + int Colstart; + int iCol; + unsigned int posA; + unsigned int posAi; + unsigned int posB; + unsigned int posBi; + + const unsigned int Nrems2 = POW2((M + 3) / 2); + const unsigned int Nroot_1_ColInc = POW2(M) - Nrems2; + const unsigned int Nroot_1 = POW2(M / 2 - 1) - 1; + const unsigned int ColstartShift = (M + 1) / 2 + 1; + + posA = POW2(M); // 1/2 of POW2(M) complex + posAi = posA + 1; + posB = posA + 2; + posBi = posB + 1; + + iolimit = ioptr + Nrems2; + for (; ioptr < iolimit; ioptr += POW2(M / 2 + 1)) { + for (Colstart = Nroot_1; Colstart >= 0; Colstart--) { + iCol = Nroot_1; + p0r = ioptr + Nroot_1_ColInc + inBRLow[Colstart] * 4; + IOP = ioptr + (Colstart << ColstartShift); + p1r = IOP + inBRLow[iCol] * 4; + f0r = *(p0r); + f0i = *(p0r + 1); + f1r = *(p0r + posA); + f1i = *(p0r + posAi); + for (; iCol > Colstart;) { + f2r = *(p0r + 2); + f2i = *(p0r + (2 + 1)); + f3r = *(p0r + posB); + f3i = *(p0r + posBi); + f4r = *(p1r); + f4i = *(p1r + 1); + f5r = *(p1r + posA); + f5i = *(p1r + posAi); + f6r = *(p1r + 2); + f6i = *(p1r + (2 + 1)); + f7r = *(p1r + posB); + f7i = *(p1r + posBi); + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + t1r = f2r + f3r; + t1i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + f0r = f4r + f5r; + f0i = f4i + f5i; + f5r = f4r - f5r; + f5i = f4i - f5i; + f2r = f6r + f7r; + f2i = f6i + f7i; + f7r = f6r - f7r; + f7i = f6i - f7i; + + *(p1r) = t0r; + *(p1r + 1) = t0i; + *(p1r + 2) = f1r; + *(p1r + (2 + 1)) = f1i; + *(p1r + posA) = t1r; + *(p1r + posAi) = t1i; + *(p1r + posB) = f3r; + *(p1r + posBi) = f3i; + *(p0r) = f0r; + *(p0r + 1) = f0i; + *(p0r + 2) = f5r; + *(p0r + (2 + 1)) = f5i; + *(p0r + posA) = f2r; + *(p0r + posAi) = f2i; + *(p0r + posB) = f7r; + *(p0r + posBi) = f7i; + + p0r -= Nrems2; + f0r = *(p0r); + f0i = *(p0r + 1); + f1r = *(p0r + posA); + f1i = *(p0r + posAi); + iCol -= 1; + p1r = IOP + inBRLow[iCol] * 4; + } + f2r = *(p0r + 2); + f2i = *(p0r + (2 + 1)); + f3r = *(p0r + posB); + f3i = *(p0r + posBi); + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + t1r = f2r + f3r; + t1i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + + *(p0r) = t0r; + *(p0r + 1) = t0i; + *(p0r + 2) = f1r; + *(p0r + (2 + 1)) = f1i; + *(p0r + posA) = t1r; + *(p0r + posAi) = t1i; + *(p0r + posB) = f3r; + *(p0r + posBi) = f3i; + } + } +} + +//------------------------------------------------------------------------------ +// RADIX 2 fft +//------------------------------------------------------------------------------ +template +void g_fft::fft2pt(FFT_TYPE *ioptr) +{ + FFT_TYPE f0r, f0i, f1r, f1i; + FFT_TYPE t0r, t0i; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[2]; + f1i = ioptr[3]; + +// Butterflys +// f0 - - - t0 +// f1 - 1 - f1 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + +// store result + ioptr[0] = t0r; + ioptr[1] = t0i; + ioptr[2] = f1r; + ioptr[3] = f1i; +} + +//------------------------------------------------------------------------------ +// RADIX 4 fft +//------------------------------------------------------------------------------ +template +void g_fft::fft4pt(FFT_TYPE *ioptr) +{ + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE t0r, t0i, t1r, t1i; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[4]; + f1i = ioptr[5]; + f2r = ioptr[2]; + f2i = ioptr[3]; + f3r = ioptr[6]; + f3i = ioptr[7]; + +// Butterflys +// f0 - - t0 - - f0 +// f1 - 1 - f1 - - f1 +// f2 - - f2 - 1 - f2 +// f3 - 1 - t1 - -i - f3 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = t0i - f2i; + + f3r = f1r - t1i; + f3i = f1i + t1r; + f1r = f1r + t1i; + f1i = f1i - t1r; + +// store result + ioptr[0] = f0r; + ioptr[1] = f0i; + ioptr[2] = f1r; + ioptr[3] = f1i; + ioptr[4] = f2r; + ioptr[5] = f2i; + ioptr[6] = f3r; + ioptr[7] = f3i; +} + +//------------------------------------------------------------------------------ +// RADIX 8 fft +//------------------------------------------------------------------------------ +template +void g_fft::fft8pt(FFT_TYPE *ioptr) +{ + FFT_TYPE w0r = 1.0 / FFT_ROOT2; // cos(pi/4) + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = 2.0; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[8]; + f1i = ioptr[9]; + f2r = ioptr[4]; + f2i = ioptr[5]; + f3r = ioptr[12]; + f3i = ioptr[13]; + f4r = ioptr[2]; + f4i = ioptr[3]; + f5r = ioptr[10]; + f5i = ioptr[11]; + f6r = ioptr[6]; + f6i = ioptr[7]; + f7r = ioptr[14]; + f7i = ioptr[15]; + +// Butterflys +// f0 - - t0 - - f0 - - f0 +// f1 - 1 - f1 - - f1 - - f1 +// f2 - - f2 - 1 - f2 - - f2 +// f3 - 1 - t1 - -i - f3 - - f3 +// f4 - - t0 - - f4 - 1 - t0 +// f5 - 1 - f5 - - f5 - w3 - f4 +// f6 - - f6 - 1 - f6 - -i - t1 +// f7 - 1 - t1 - -i - f7 - iw3- f6 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = t0i - f2i; + + f3r = f1r - t1i; + f3i = f1i + t1r; + f1r = f1r + t1i; + f1i = f1i - t1r; + + t0r = f4r + f5r; + t0i = f4i + f5i; + f5r = f4r - f5r; + f5i = f4i - f5i; + + t1r = f6r - f7r; + t1i = f6i - f7i; + f6r = f6r + f7r; + f6i = f6i + f7i; + + f4r = t0r + f6r; + f4i = t0i + f6i; + f6r = t0r - f6r; + f6i = t0i - f6i; + + f7r = f5r - t1i; + f7i = f5i + t1r; + f5r = f5r + t1i; + f5i = f5i - t1r; + + t0r = f0r - f4r; + t0i = f0i - f4i; + f0r = f0r + f4r; + f0i = f0i + f4i; + + t1r = f2r - f6i; + t1i = f2i + f6r; + f2r = f2r + f6i; + f2i = f2i - f6r; + + f4r = f1r - f5r * w0r - f5i * w0r; + f4i = f1i + f5r * w0r - f5i * w0r; + f1r = f1r * Two - f4r; + f1i = f1i * Two - f4i; + + f6r = f3r + f7r * w0r - f7i * w0r; + f6i = f3i + f7r * w0r + f7i * w0r; + f3r = f3r * Two - f6r; + f3i = f3i * Two - f6i; + +// store result + ioptr[0] = f0r; + ioptr[1] = f0i; + ioptr[2] = f1r; + ioptr[3] = f1i; + ioptr[4] = f2r; + ioptr[5] = f2i; + ioptr[6] = f3r; + ioptr[7] = f3i; + ioptr[8] = t0r; + ioptr[9] = t0i; + ioptr[10] = f4r; + ioptr[11] = f4i; + ioptr[12] = t1r; + ioptr[13] = t1i; + ioptr[14] = f6r; + ioptr[15] = f6i; +} + +//------------------------------------------------------------------------------ +// 2nd radix 2 stage +//------------------------------------------------------------------------------ +template +void g_fft::bfR2(FFT_TYPE *ioptr, int M, int NDiffU) +{ + unsigned int pos; + unsigned int posi; + unsigned int pinc; + unsigned int pnext; + unsigned int NSameU; + unsigned int SameUCnt; + + FFT_TYPE *pstrt; + FFT_TYPE *p0r, *p1r, *p2r, *p3r; + + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + + pinc = NDiffU * 2; // 2 floats per complex + pnext = pinc * 4; + pos = 2; + posi = pos + 1; + NSameU = POW2(M) / 4 / NDiffU; // 4 Us at a time + pstrt = ioptr; + p0r = pstrt; + p1r = pstrt + pinc; + p2r = p1r + pinc; + p3r = p2r + pinc; + +// Butterflys +// f0 - - f4 +// f1 - 1 - f5 +// f2 - - f6 +// f3 - 1 - f7 +// Butterflys +// f0 - - f4 +// f1 - 1 - f5 +// f2 - - f6 +// f3 - 1 - f7 + + for (SameUCnt = NSameU; SameUCnt > 0; SameUCnt--) { + + f0r = *p0r; + f1r = *p1r; + f0i = *(p0r + 1); + f1i = *(p1r + 1); + f2r = *p2r; + f3r = *p3r; + f2i = *(p2r + 1); + f3i = *(p3r + 1); + + f4r = f0r + f1r; + f4i = f0i + f1i; + f5r = f0r - f1r; + f5i = f0i - f1i; + + f6r = f2r + f3r; + f6i = f2i + f3i; + f7r = f2r - f3r; + f7i = f2i - f3i; + + *p0r = f4r; + *(p0r + 1) = f4i; + *p1r = f5r; + *(p1r + 1) = f5i; + *p2r = f6r; + *(p2r + 1) = f6i; + *p3r = f7r; + *(p3r + 1) = f7i; + + f0r = *(p0r + pos); + f1i = *(p1r + posi); + f0i = *(p0r + posi); + f1r = *(p1r + pos); + f2r = *(p2r + pos); + f3i = *(p3r + posi); + f2i = *(p2r + posi); + f3r = *(p3r + pos); + + f4r = f0r + f1i; + f4i = f0i - f1r; + f5r = f0r - f1i; + f5i = f0i + f1r; + + f6r = f2r + f3i; + f6i = f2i - f3r; + f7r = f2r - f3i; + f7i = f2i + f3r; + + *(p0r + pos) = f4r; + *(p0r + posi) = f4i; + *(p1r + pos) = f5r; + *(p1r + posi) = f5i; + *(p2r + pos) = f6r; + *(p2r + posi) = f6i; + *(p3r + pos) = f7r; + *(p3r + posi) = f7i; + + p0r += pnext; + p1r += pnext; + p2r += pnext; + p3r += pnext; + } +} + +//------------------------------------------------------------------------------ +// 1 radix 4 stage +//------------------------------------------------------------------------------ +template +void g_fft::bfR4(FFT_TYPE *ioptr, int M, int NDiffU) +{ + unsigned int pos; + unsigned int posi; + unsigned int pinc; + unsigned int pnext; + unsigned int pnexti; + unsigned int NSameU; + unsigned int SameUCnt; + + FFT_TYPE *pstrt; + FFT_TYPE *p0r, *p1r, *p2r, *p3r; + + FFT_TYPE w1r = 1.0 / FFT_ROOT2; // cos(pi/4) + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t1r, t1i; + const FFT_TYPE Two = 2.0; + + pinc = NDiffU * 2; // 2 floats per complex + pnext = pinc * 4; + pnexti = pnext + 1; + pos = 2; + posi = pos + 1; + NSameU = POW2(M) / 4 / NDiffU; // 4 pts per butterfly + pstrt = ioptr; + p0r = pstrt; + p1r = pstrt + pinc; + p2r = p1r + pinc; + p3r = p2r + pinc; + +// Butterflys +// f0 - - f0 - - f4 +// f1 - 1 - f5 - - f5 +// f2 - - f6 - 1 - f6 +// f3 - 1 - f3 - -i - f7 +// Butterflys +// f0 - - f4 - - f4 +// f1 - -i - t1 - - f5 +// f2 - - f2 - w1 - f6 +// f3 - -i - f7 - iw1- f7 + + f0r = *p0r; + f1r = *p1r; + f2r = *p2r; + f3r = *p3r; + f0i = *(p0r + 1); + f1i = *(p1r + 1); + f2i = *(p2r + 1); + f3i = *(p3r + 1); + + f5r = f0r - f1r; + f5i = f0i - f1i; + f0r = f0r + f1r; + f0i = f0i + f1i; + + f6r = f2r + f3r; + f6i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + + for (SameUCnt = NSameU - 1; SameUCnt > 0; SameUCnt--) { + + f7r = f5r - f3i; + f7i = f5i + f3r; + f5r = f5r + f3i; + f5i = f5i - f3r; + + f4r = f0r + f6r; + f4i = f0i + f6i; + f6r = f0r - f6r; + f6i = f0i - f6i; + + f2r = *(p2r + pos); + f2i = *(p2r + posi); + f1r = *(p1r + pos); + f1i = *(p1r + posi); + f3i = *(p3r + posi); + f0r = *(p0r + pos); + f3r = *(p3r + pos); + f0i = *(p0r + posi); + + *p3r = f7r; + *p0r = f4r; + *(p3r + 1) = f7i; + *(p0r + 1) = f4i; + *p1r = f5r; + *p2r = f6r; + *(p1r + 1) = f5i; + *(p2r + 1) = f6i; + + f7r = f2r - f3i; + f7i = f2i + f3r; + f2r = f2r + f3i; + f2i = f2i - f3r; + + f4r = f0r + f1i; + f4i = f0i - f1r; + t1r = f0r - f1i; + t1i = f0i + f1r; + + f5r = t1r - f7r * w1r + f7i * w1r; + f5i = t1i - f7r * w1r - f7i * w1r; + f7r = t1r * Two - f5r; + f7i = t1i * Two - f5i; + + f6r = f4r - f2r * w1r - f2i * w1r; + f6i = f4i + f2r * w1r - f2i * w1r; + f4r = f4r * Two - f6r; + f4i = f4i * Two - f6i; + + f3r = *(p3r + pnext); + f0r = *(p0r + pnext); + f3i = *(p3r + pnexti); + f0i = *(p0r + pnexti); + f2r = *(p2r + pnext); + f2i = *(p2r + pnexti); + f1r = *(p1r + pnext); + f1i = *(p1r + pnexti); + + *(p2r + pos) = f6r; + *(p1r + pos) = f5r; + *(p2r + posi) = f6i; + *(p1r + posi) = f5i; + *(p3r + pos) = f7r; + *(p0r + pos) = f4r; + *(p3r + posi) = f7i; + *(p0r + posi) = f4i; + + f6r = f2r + f3r; + f6i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + + f5r = f0r - f1r; + f5i = f0i - f1i; + f0r = f0r + f1r; + f0i = f0i + f1i; + + p3r += pnext; + p0r += pnext; + p1r += pnext; + p2r += pnext; + } + f7r = f5r - f3i; + f7i = f5i + f3r; + f5r = f5r + f3i; + f5i = f5i - f3r; + + f4r = f0r + f6r; + f4i = f0i + f6i; + f6r = f0r - f6r; + f6i = f0i - f6i; + + f2r = *(p2r + pos); + f2i = *(p2r + posi); + f1r = *(p1r + pos); + f1i = *(p1r + posi); + f3i = *(p3r + posi); + f0r = *(p0r + pos); + f3r = *(p3r + pos); + f0i = *(p0r + posi); + + *p3r = f7r; + *p0r = f4r; + *(p3r + 1) = f7i; + *(p0r + 1) = f4i; + *p1r = f5r; + *p2r = f6r; + *(p1r + 1) = f5i; + *(p2r + 1) = f6i; + + f7r = f2r - f3i; + f7i = f2i + f3r; + f2r = f2r + f3i; + f2i = f2i - f3r; + + f4r = f0r + f1i; + f4i = f0i - f1r; + t1r = f0r - f1i; + t1i = f0i + f1r; + + f5r = t1r - f7r * w1r + f7i * w1r; + f5i = t1i - f7r * w1r - f7i * w1r; + f7r = t1r * Two - f5r; + f7i = t1i * Two - f5i; + + f6r = f4r - f2r * w1r - f2i * w1r; + f6i = f4i + f2r * w1r - f2i * w1r; + f4r = f4r * Two - f6r; + f4i = f4i * Two - f6i; + + *(p2r + pos) = f6r; + *(p1r + pos) = f5r; + *(p2r + posi) = f6i; + *(p1r + posi) = f5i; + *(p3r + pos) = f7r; + *(p0r + pos) = f4r; + *(p3r + posi) = f7i; + *(p0r + posi) = f4i; +} + +//------------------------------------------------------------------------------ +// RADIX 8 Stages +//------------------------------------------------------------------------------ +template +void g_fft::bfstages(FFT_TYPE *ioptr, int M, FFT_TYPE *inUtbl, int Ustride, + int NDiffU, int StageCnt) +{ + unsigned int pos; + unsigned int posi; + unsigned int pinc; + unsigned int pnext; + unsigned int NSameU; + int Uinc; + int Uinc2; + int Uinc4; + unsigned int DiffUCnt; + unsigned int SameUCnt; + unsigned int U2toU3; + + FFT_TYPE *pstrt; + FFT_TYPE *p0r, *p1r, *p2r, *p3r; + FFT_TYPE *u0r, *u0i, *u1r, *u1i, *u2r, *u2i; + + FFT_TYPE w0r, w0i, w1r, w1i, w2r, w2i, w3r, w3i; + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = FFT_TYPE(2.0); + + pinc = NDiffU * 2; // 2 floats per complex + pnext = pinc * 8; + pos = pinc * 4; + posi = pos + 1; + NSameU = POW2(M) / 8 / NDiffU; // 8 pts per butterfly + Uinc = (int) NSameU * Ustride; + Uinc2 = Uinc * 2; + Uinc4 = Uinc * 4; + U2toU3 = (POW2(M) / 8) * Ustride; + for (; StageCnt > 0; StageCnt--) { + + u0r = &inUtbl[0]; + u0i = &inUtbl[POW2(M - 2) * Ustride]; + u1r = u0r; + u1i = u0i; + u2r = u0r; + u2i = u0i; + + w0r = *u0r; + w0i = *u0i; + w1r = *u1r; + w1i = *u1i; + w2r = *u2r; + w2i = *u2i; + w3r = *(u2r + U2toU3); + w3i = *(u2i - U2toU3); + + pstrt = ioptr; + + p0r = pstrt; + p1r = pstrt + pinc; + p2r = p1r + pinc; + p3r = p2r + pinc; + +// Butterflys +// f0 - - t0 - - f0 - - f0 +// f1 - w0- f1 - - f1 - - f1 +// f2 - - f2 - w1- f2 - - f4 +// f3 - w0- t1 - iw1- f3 - - f5 +// +// f4 - - t0 - - f4 - w2- t0 +// f5 - w0- f5 - - f5 - w3- t1 +// f6 - - f6 - w1- f6 - iw2- f6 +// f7 - w0- t1 - iw1- f7 - iw3- f7 + + for (DiffUCnt = NDiffU; DiffUCnt > 0; DiffUCnt--) { + f0r = *p0r; + f0i = *(p0r + 1); + f1r = *p1r; + f1i = *(p1r + 1); + for (SameUCnt = NSameU - 1; SameUCnt > 0; SameUCnt--) { + f2r = *p2r; + f2i = *(p2r + 1); + f3r = *p3r; + f3i = *(p3r + 1); + + t0r = f0r + f1r * w0r + f1i * w0i; + t0i = f0i - f1r * w0i + f1i * w0r; + f1r = f0r * Two - t0r; + f1i = f0i * Two - t0i; + + f4r = *(p0r + pos); + f4i = *(p0r + posi); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + + f6r = *(p2r + pos); + f6i = *(p2r + posi); + f7r = *(p3r + pos); + f7i = *(p3r + posi); + + t1r = f2r - f3r * w0r - f3i * w0i; + t1i = f2i + f3r * w0i - f3i * w0r; + f2r = f2r * Two - t1r; + f2i = f2i * Two - t1i; + + f0r = t0r + f2r * w1r + f2i * w1i; + f0i = t0i - f2r * w1i + f2i * w1r; + f2r = t0r * Two - f0r; + f2i = t0i * Two - f0i; + + f3r = f1r + t1r * w1i - t1i * w1r; + f3i = f1i + t1r * w1r + t1i * w1i; + f1r = f1r * Two - f3r; + f1i = f1i * Two - f3i; + + t0r = f4r + f5r * w0r + f5i * w0i; + t0i = f4i - f5r * w0i + f5i * w0r; + f5r = f4r * Two - t0r; + f5i = f4i * Two - t0i; + + t1r = f6r - f7r * w0r - f7i * w0i; + t1i = f6i + f7r * w0i - f7i * w0r; + f6r = f6r * Two - t1r; + f6i = f6i * Two - t1i; + + f4r = t0r + f6r * w1r + f6i * w1i; + f4i = t0i - f6r * w1i + f6i * w1r; + f6r = t0r * Two - f4r; + f6i = t0i * Two - f4i; + + f7r = f5r + t1r * w1i - t1i * w1r; + f7i = f5i + t1r * w1r + t1i * w1i; + f5r = f5r * Two - f7r; + f5i = f5i * Two - f7i; + + t0r = f0r - f4r * w2r - f4i * w2i; + t0i = f0i + f4r * w2i - f4i * w2r; + f0r = f0r * Two - t0r; + f0i = f0i * Two - t0i; + + t1r = f1r - f5r * w3r - f5i * w3i; + t1i = f1i + f5r * w3i - f5i * w3r; + f1r = f1r * Two - t1r; + f1i = f1i * Two - t1i; + + *(p0r + pos) = t0r; + *(p1r + pos) = t1r; + *(p0r + posi) = t0i; + *(p1r + posi) = t1i; + *p0r = f0r; + *p1r = f1r; + *(p0r + 1) = f0i; + *(p1r + 1) = f1i; + + p0r += pnext; + f0r = *p0r; + f0i = *(p0r + 1); + + p1r += pnext; + + f1r = *p1r; + f1i = *(p1r + 1); + + f4r = f2r - f6r * w2i + f6i * w2r; + f4i = f2i - f6r * w2r - f6i * w2i; + f6r = f2r * Two - f4r; + f6i = f2i * Two - f4i; + + f5r = f3r - f7r * w3i + f7i * w3r; + f5i = f3i - f7r * w3r - f7i * w3i; + f7r = f3r * Two - f5r; + f7i = f3i * Two - f5i; + + *p2r = f4r; + *p3r = f5r; + *(p2r + 1) = f4i; + *(p3r + 1) = f5i; + *(p2r + pos) = f6r; + *(p3r + pos) = f7r; + *(p2r + posi) = f6i; + *(p3r + posi) = f7i; + + p2r += pnext; + p3r += pnext; + } + + f2r = *p2r; + f2i = *(p2r + 1); + f3r = *p3r; + f3i = *(p3r + 1); + + t0r = f0r + f1r * w0r + f1i * w0i; + t0i = f0i - f1r * w0i + f1i * w0r; + f1r = f0r * Two - t0r; + f1i = f0i * Two - t0i; + + f4r = *(p0r + pos); + f4i = *(p0r + posi); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + + f6r = *(p2r + pos); + f6i = *(p2r + posi); + f7r = *(p3r + pos); + f7i = *(p3r + posi); + + t1r = f2r - f3r * w0r - f3i * w0i; + t1i = f2i + f3r * w0i - f3i * w0r; + f2r = f2r * Two - t1r; + f2i = f2i * Two - t1i; + + f0r = t0r + f2r * w1r + f2i * w1i; + f0i = t0i - f2r * w1i + f2i * w1r; + f2r = t0r * Two - f0r; + f2i = t0i * Two - f0i; + + f3r = f1r + t1r * w1i - t1i * w1r; + f3i = f1i + t1r * w1r + t1i * w1i; + f1r = f1r * Two - f3r; + f1i = f1i * Two - f3i; + + if ((int) DiffUCnt == NDiffU / 2) + Uinc4 = -Uinc4; + + u0r += Uinc4; + u0i -= Uinc4; + u1r += Uinc2; + u1i -= Uinc2; + u2r += Uinc; + u2i -= Uinc; + + pstrt += 2; + + t0r = f4r + f5r * w0r + f5i * w0i; + t0i = f4i - f5r * w0i + f5i * w0r; + f5r = f4r * Two - t0r; + f5i = f4i * Two - t0i; + + t1r = f6r - f7r * w0r - f7i * w0i; + t1i = f6i + f7r * w0i - f7i * w0r; + f6r = f6r * Two - t1r; + f6i = f6i * Two - t1i; + + f4r = t0r + f6r * w1r + f6i * w1i; + f4i = t0i - f6r * w1i + f6i * w1r; + f6r = t0r * Two - f4r; + f6i = t0i * Two - f4i; + + f7r = f5r + t1r * w1i - t1i * w1r; + f7i = f5i + t1r * w1r + t1i * w1i; + f5r = f5r * Two - f7r; + f5i = f5i * Two - f7i; + + w0r = *u0r; + w0i = *u0i; + w1r = *u1r; + w1i = *u1i; + + if ((int) DiffUCnt <= NDiffU / 2) + w0r = -w0r; + + t0r = f0r - f4r * w2r - f4i * w2i; + t0i = f0i + f4r * w2i - f4i * w2r; + f0r = f0r * Two - t0r; + f0i = f0i * Two - t0i; + + f4r = f2r - f6r * w2i + f6i * w2r; + f4i = f2i - f6r * w2r - f6i * w2i; + f6r = f2r * Two - f4r; + f6i = f2i * Two - f4i; + + *(p0r + pos) = t0r; + *p2r = f4r; + *(p0r + posi) = t0i; + *(p2r + 1) = f4i; + w2r = *u2r; + w2i = *u2i; + *p0r = f0r; + *(p2r + pos) = f6r; + *(p0r + 1) = f0i; + *(p2r + posi) = f6i; + + p0r = pstrt; + p2r = pstrt + pinc + pinc; + + t1r = f1r - f5r * w3r - f5i * w3i; + t1i = f1i + f5r * w3i - f5i * w3r; + f1r = f1r * Two - t1r; + f1i = f1i * Two - t1i; + + f5r = f3r - f7r * w3i + f7i * w3r; + f5i = f3i - f7r * w3r - f7i * w3i; + f7r = f3r * Two - f5r; + f7i = f3i * Two - f5i; + + *(p1r + pos) = t1r; + *p3r = f5r; + *(p1r + posi) = t1i; + *(p3r + 1) = f5i; + w3r = *(u2r + U2toU3); + w3i = *(u2i - U2toU3); + *p1r = f1r; + *(p3r + pos) = f7r; + *(p1r + 1) = f1i; + *(p3r + posi) = f7i; + + p1r = pstrt + pinc; + p3r = p2r + pinc; + } + NSameU /= 8; + Uinc /= 8; + Uinc2 /= 8; + Uinc4 = Uinc * 4; + NDiffU *= 8; + pinc *= 8; + pnext *= 8; + pos *= 8; + posi = pos + 1; + } +} + +template +void g_fft::fftrecurs(FFT_TYPE *ioptr, int M, + FFT_TYPE *Utbl, int Ustride, int NDiffU, + int StageCnt) +{ +// recursive bfstages calls to maximize on chip cache efficiency + int i1; + + if (M <= (int) MCACHE) // fits on chip ? + bfstages(ioptr, M, Utbl, Ustride, NDiffU, StageCnt); // RADIX 8 Stages + else { + for (i1 = 0; i1 < 8; i1++) { + fftrecurs(&ioptr[i1 * POW2(M - 3) * 2], M - 3, Utbl, 8 * Ustride, + NDiffU, StageCnt - 1); // RADIX 8 Stages + } + bfstages(ioptr, M, Utbl, Ustride, POW2(M - 3), 1); // RADIX 8 Stage + } +} + +//------------------------------------------------------------------------------ +// Compute in-place complex fft on the rows of the input array +// INPUTS +// *ioptr = input data array +// M = log2 of fft size (ex M=10 for 1024 point fft) +// *Utbl = cosine table +// *BRLow = bit reversed counter table +// OUTPUTS +// *ioptr = output data array +//------------------------------------------------------------------------------ +template +void g_fft::ffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow) +{ + int StageCnt; + int NDiffU; + + switch (M) { + case 0: + break; + case 1: + fft2pt(ioptr); // a 2 pt fft + break; + case 2: + fft4pt(ioptr); // a 4 pt fft + break; + case 3: + fft8pt(ioptr); // an 8 pt fft + break; + default: + bitrevR2(ioptr, M, BRLow); // bit reverse and first radix 2 stage + StageCnt = (M - 1) / 3; // number of radix 8 stages + NDiffU = 2; // one radix 2 stage already complete + if ((M - 1 - (StageCnt * 3)) == 1) { + bfR2(ioptr, M, NDiffU); // 1 radix 2 stage + NDiffU *= 2; + } + if ((M - 1 - (StageCnt * 3)) == 2) { + bfR4(ioptr, M, NDiffU); // 1 radix 4 stage + NDiffU *= 4; + } + if (M <= (int) MCACHE) + bfstages(ioptr, M, Utbl, 1, NDiffU, StageCnt); // RADIX 8 Stages + else + fftrecurs(ioptr, M, Utbl, 1, NDiffU, StageCnt); // RADIX 8 Stages + } +} + +//------------------------------------------------------------------------------ +// parts of iffts1 +// scaled bit reverse and first radix 2 stage forward or inverse fft +//------------------------------------------------------------------------------ +template +void g_fft::scbitrevR2(FFT_TYPE *ioptr, int M, short *inBRLow, FFT_TYPE scale) +{ + FFT_TYPE f0r; + FFT_TYPE f0i; + FFT_TYPE f1r; + FFT_TYPE f1i; + FFT_TYPE f2r; + FFT_TYPE f2i; + FFT_TYPE f3r; + FFT_TYPE f3i; + FFT_TYPE f4r; + FFT_TYPE f4i; + FFT_TYPE f5r; + FFT_TYPE f5i; + FFT_TYPE f6r; + FFT_TYPE f6i; + FFT_TYPE f7r; + FFT_TYPE f7i; + FFT_TYPE t0r; + FFT_TYPE t0i; + FFT_TYPE t1r; + FFT_TYPE t1i; + FFT_TYPE *p0r; + FFT_TYPE *p1r; + FFT_TYPE *IOP; + FFT_TYPE *iolimit; + int Colstart; + int iCol; + unsigned int posA; + unsigned int posAi; + unsigned int posB; + unsigned int posBi; + + const unsigned int Nrems2 = POW2((M + 3) / 2); + const unsigned int Nroot_1_ColInc = POW2(M) - Nrems2; + const unsigned int Nroot_1 = POW2(M / 2 - 1) - 1; + const unsigned int ColstartShift = (M + 1) / 2 + 1; + + posA = POW2(M); // 1/2 of POW2(M) complexes + posAi = posA + 1; + posB = posA + 2; + posBi = posB + 1; + + iolimit = ioptr + Nrems2; + for (; ioptr < iolimit; ioptr += POW2(M / 2 + 1)) { + for (Colstart = Nroot_1; Colstart >= 0; Colstart--) { + iCol = Nroot_1; + p0r = ioptr + Nroot_1_ColInc + inBRLow[Colstart] * 4; + IOP = ioptr + (Colstart << ColstartShift); + p1r = IOP + inBRLow[iCol] * 4; + f0r = *(p0r); + f0i = *(p0r + 1); + f1r = *(p0r + posA); + f1i = *(p0r + posAi); + for (; iCol > Colstart;) { + f2r = *(p0r + 2); + f2i = *(p0r + (2 + 1)); + f3r = *(p0r + posB); + f3i = *(p0r + posBi); + f4r = *(p1r); + f4i = *(p1r + 1); + f5r = *(p1r + posA); + f5i = *(p1r + posAi); + f6r = *(p1r + 2); + f6i = *(p1r + (2 + 1)); + f7r = *(p1r + posB); + f7i = *(p1r + posBi); + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + t1r = f2r + f3r; + t1i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + f0r = f4r + f5r; + f0i = f4i + f5i; + f5r = f4r - f5r; + f5i = f4i - f5i; + f2r = f6r + f7r; + f2i = f6i + f7i; + f7r = f6r - f7r; + f7i = f6i - f7i; + + *(p1r) = scale * t0r; + *(p1r + 1) = scale * t0i; + *(p1r + 2) = scale * f1r; + *(p1r + (2 + 1)) = scale * f1i; + *(p1r + posA) = scale * t1r; + *(p1r + posAi) = scale * t1i; + *(p1r + posB) = scale * f3r; + *(p1r + posBi) = scale * f3i; + *(p0r) = scale * f0r; + *(p0r + 1) = scale * f0i; + *(p0r + 2) = scale * f5r; + *(p0r + (2 + 1)) = scale * f5i; + *(p0r + posA) = scale * f2r; + *(p0r + posAi) = scale * f2i; + *(p0r + posB) = scale * f7r; + *(p0r + posBi) = scale * f7i; + + p0r -= Nrems2; + f0r = *(p0r); + f0i = *(p0r + 1); + f1r = *(p0r + posA); + f1i = *(p0r + posAi); + iCol -= 1; + p1r = IOP + inBRLow[iCol] * 4; + } + f2r = *(p0r + 2); + f2i = *(p0r + (2 + 1)); + f3r = *(p0r + posB); + f3i = *(p0r + posBi); + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + t1r = f2r + f3r; + t1i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + + *(p0r) = scale * t0r; + *(p0r + 1) = scale * t0i; + *(p0r + 2) = scale * f1r; + *(p0r + (2 + 1)) = scale * f1i; + *(p0r + posA) = scale * t1r; + *(p0r + posAi) = scale * t1i; + *(p0r + posB) = scale * f3r; + *(p0r + posBi) = scale * f3i; + } + } +} + +//------------------------------------------------------------------------------ +// RADIX 2 ifft +//------------------------------------------------------------------------------ +template +void g_fft::ifft2pt(FFT_TYPE *ioptr, FFT_TYPE scale) +{ + FFT_TYPE f0r, f0i, f1r, f1i; + FFT_TYPE t0r, t0i; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[2]; + f1i = ioptr[3]; + +// Butterflys +// f0 - - t0 +// f1 - 1 - f1 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + +// store result + ioptr[0] = scale * t0r; + ioptr[1] = scale * t0i; + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; +} + +//------------------------------------------------------------------------------ +// RADIX 4 ifft +//------------------------------------------------------------------------------ +template +void g_fft::ifft4pt(FFT_TYPE *ioptr, FFT_TYPE scale) +{ + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE t0r, t0i, t1r, t1i; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[4]; + f1i = ioptr[5]; + f2r = ioptr[2]; + f2i = ioptr[3]; + f3r = ioptr[6]; + f3i = ioptr[7]; + +// Butterflys +// f0 - - t0 - - f0 +// f1 - 1 - f1 - - f1 +// f2 - - f2 - 1 - f2 +// f3 - 1 - t1 - i - f3 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = t0i - f2i; + + f3r = f1r + t1i; + f3i = f1i - t1r; + f1r = f1r - t1i; + f1i = f1i + t1r; + +// store result + ioptr[0] = scale * f0r; + ioptr[1] = scale * f0i; + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; + ioptr[4] = scale * f2r; + ioptr[5] = scale * f2i; + ioptr[6] = scale * f3r; + ioptr[7] = scale * f3i; +} + +//------------------------------------------------------------------------------ +// RADIX 8 ifft +//------------------------------------------------------------------------------ +template +void g_fft::ifft8pt(FFT_TYPE *ioptr, FFT_TYPE scale) +{ + FFT_TYPE w0r = 1.0 / FFT_ROOT2; // cos(pi/4) + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = 2.0; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[8]; + f1i = ioptr[9]; + f2r = ioptr[4]; + f2i = ioptr[5]; + f3r = ioptr[12]; + f3i = ioptr[13]; + f4r = ioptr[2]; + f4i = ioptr[3]; + f5r = ioptr[10]; + f5i = ioptr[11]; + f6r = ioptr[6]; + f6i = ioptr[7]; + f7r = ioptr[14]; + f7i = ioptr[15]; + +// Butterflys +// f0 - - t0 - - f0 - - f0 +// f1 - 1 - f1 - - f1 - - f1 +// f2 - - f2 - 1 - f2 - - f2 +// f3 - 1 - t1 - i - f3 - - f3 +// f4 - - t0 - - f4 - 1 - t0 +// f5 - 1 - f5 - - f5 - w3 - f4 +// f6 - - f6 - 1 - f6 - i - t1 +// f7 - 1 - t1 - i - f7 - iw3- f6 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = t0i - f2i; + + f3r = f1r + t1i; + f3i = f1i - t1r; + f1r = f1r - t1i; + f1i = f1i + t1r; + + t0r = f4r + f5r; + t0i = f4i + f5i; + f5r = f4r - f5r; + f5i = f4i - f5i; + + t1r = f6r - f7r; + t1i = f6i - f7i; + f6r = f6r + f7r; + f6i = f6i + f7i; + + f4r = t0r + f6r; + f4i = t0i + f6i; + f6r = t0r - f6r; + f6i = t0i - f6i; + + f7r = f5r + t1i; + f7i = f5i - t1r; + f5r = f5r - t1i; + f5i = f5i + t1r; + + t0r = f0r - f4r; + t0i = f0i - f4i; + f0r = f0r + f4r; + f0i = f0i + f4i; + + t1r = f2r + f6i; + t1i = f2i - f6r; + f2r = f2r - f6i; + f2i = f2i + f6r; + + f4r = f1r - f5r * w0r + f5i * w0r; + f4i = f1i - f5r * w0r - f5i * w0r; + f1r = f1r * Two - f4r; + f1i = f1i * Two - f4i; + + f6r = f3r + f7r * w0r + f7i * w0r; + f6i = f3i - f7r * w0r + f7i * w0r; + f3r = f3r * Two - f6r; + f3i = f3i * Two - f6i; + +// store result + ioptr[0] = scale * f0r; + ioptr[1] = scale * f0i; + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; + ioptr[4] = scale * f2r; + ioptr[5] = scale * f2i; + ioptr[6] = scale * f3r; + ioptr[7] = scale * f3i; + ioptr[8] = scale * t0r; + ioptr[9] = scale * t0i; + ioptr[10] = scale * f4r; + ioptr[11] = scale * f4i; + ioptr[12] = scale * t1r; + ioptr[13] = scale * t1i; + ioptr[14] = scale * f6r; + ioptr[15] = scale * f6i; +} + +//------------------------------------------------------------------------------ +// 2nd radix 2 stage +//------------------------------------------------------------------------------ +template +void g_fft::ibfR2(FFT_TYPE *ioptr, int M, int NDiffU) +{ + unsigned int pos; + unsigned int posi; + unsigned int pinc; + unsigned int pnext; + unsigned int NSameU; + unsigned int SameUCnt; + + FFT_TYPE *pstrt; + FFT_TYPE *p0r, *p1r, *p2r, *p3r; + + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + + pinc = NDiffU * 2; // 2 floats per complex + pnext = pinc * 4; + pos = 2; + posi = pos + 1; + NSameU = POW2(M) / 4 / NDiffU; // 4 Us at a time + pstrt = ioptr; + p0r = pstrt; + p1r = pstrt + pinc; + p2r = p1r + pinc; + p3r = p2r + pinc; + +// Butterflys +// f0 - - f4 +// f1 - 1 - f5 +// f2 - - f6 +// f3 - 1 - f7 +// Butterflys +// f0 - - f4 +// f1 - 1 - f5 +// f2 - - f6 +// f3 - 1 - f7 + + for (SameUCnt = NSameU; SameUCnt > 0; SameUCnt--) { + + f0r = *p0r; + f1r = *p1r; + f0i = *(p0r + 1); + f1i = *(p1r + 1); + f2r = *p2r; + f3r = *p3r; + f2i = *(p2r + 1); + f3i = *(p3r + 1); + + f4r = f0r + f1r; + f4i = f0i + f1i; + f5r = f0r - f1r; + f5i = f0i - f1i; + + f6r = f2r + f3r; + f6i = f2i + f3i; + f7r = f2r - f3r; + f7i = f2i - f3i; + + *p0r = f4r; + *(p0r + 1) = f4i; + *p1r = f5r; + *(p1r + 1) = f5i; + *p2r = f6r; + *(p2r + 1) = f6i; + *p3r = f7r; + *(p3r + 1) = f7i; + + f0r = *(p0r + pos); + f1i = *(p1r + posi); + f0i = *(p0r + posi); + f1r = *(p1r + pos); + f2r = *(p2r + pos); + f3i = *(p3r + posi); + f2i = *(p2r + posi); + f3r = *(p3r + pos); + + f4r = f0r - f1i; + f4i = f0i + f1r; + f5r = f0r + f1i; + f5i = f0i - f1r; + + f6r = f2r - f3i; + f6i = f2i + f3r; + f7r = f2r + f3i; + f7i = f2i - f3r; + + *(p0r + pos) = f4r; + *(p0r + posi) = f4i; + *(p1r + pos) = f5r; + *(p1r + posi) = f5i; + *(p2r + pos) = f6r; + *(p2r + posi) = f6i; + *(p3r + pos) = f7r; + *(p3r + posi) = f7i; + + p0r += pnext; + p1r += pnext; + p2r += pnext; + p3r += pnext; + } +} + +//------------------------------------------------------------------------------ +// 1 radix 4 stage +//------------------------------------------------------------------------------ +template +void g_fft::ibfR4(FFT_TYPE *ioptr, int M, int NDiffU) +{ + unsigned int pos; + unsigned int posi; + unsigned int pinc; + unsigned int pnext; + unsigned int pnexti; + unsigned int NSameU; + unsigned int SameUCnt; + + FFT_TYPE *pstrt; + FFT_TYPE *p0r, *p1r, *p2r, *p3r; + + FFT_TYPE w1r = 1.0 / FFT_ROOT2; // cos(pi/4) + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t1r, t1i; + const FFT_TYPE Two = 2.0; + + pinc = NDiffU * 2; // 2 floats per complex + pnext = pinc * 4; + pnexti = pnext + 1; + pos = 2; + posi = pos + 1; + NSameU = POW2(M) / 4 / NDiffU; // 4 pts per butterfly + pstrt = ioptr; + p0r = pstrt; + p1r = pstrt + pinc; + p2r = p1r + pinc; + p3r = p2r + pinc; + +// Butterflys +// f0 - - f0 - - f4 +// f1 - 1 - f5 - - f5 +// f2 - - f6 - 1 - f6 +// f3 - 1 - f3 - -i - f7 +// Butterflys +// f0 - - f4 - - f4 +// f1 - -i - t1 - - f5 +// f2 - - f2 - w1 - f6 +// f3 - -i - f7 - iw1- f7 + + f0r = *p0r; + f1r = *p1r; + f2r = *p2r; + f3r = *p3r; + f0i = *(p0r + 1); + f1i = *(p1r + 1); + f2i = *(p2r + 1); + f3i = *(p3r + 1); + + f5r = f0r - f1r; + f5i = f0i - f1i; + f0r = f0r + f1r; + f0i = f0i + f1i; + + f6r = f2r + f3r; + f6i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + + for (SameUCnt = NSameU - 1; SameUCnt > 0; SameUCnt--) { + + f7r = f5r + f3i; + f7i = f5i - f3r; + f5r = f5r - f3i; + f5i = f5i + f3r; + + f4r = f0r + f6r; + f4i = f0i + f6i; + f6r = f0r - f6r; + f6i = f0i - f6i; + + f2r = *(p2r + pos); + f2i = *(p2r + posi); + f1r = *(p1r + pos); + f1i = *(p1r + posi); + f3i = *(p3r + posi); + f0r = *(p0r + pos); + f3r = *(p3r + pos); + f0i = *(p0r + posi); + + *p3r = f7r; + *p0r = f4r; + *(p3r + 1) = f7i; + *(p0r + 1) = f4i; + *p1r = f5r; + *p2r = f6r; + *(p1r + 1) = f5i; + *(p2r + 1) = f6i; + + f7r = f2r + f3i; + f7i = f2i - f3r; + f2r = f2r - f3i; + f2i = f2i + f3r; + + f4r = f0r - f1i; + f4i = f0i + f1r; + t1r = f0r + f1i; + t1i = f0i - f1r; + + f5r = t1r - f7r * w1r - f7i * w1r; + f5i = t1i + f7r * w1r - f7i * w1r; + f7r = t1r * Two - f5r; + f7i = t1i * Two - f5i; + + f6r = f4r - f2r * w1r + f2i * w1r; + f6i = f4i - f2r * w1r - f2i * w1r; + f4r = f4r * Two - f6r; + f4i = f4i * Two - f6i; + + f3r = *(p3r + pnext); + f0r = *(p0r + pnext); + f3i = *(p3r + pnexti); + f0i = *(p0r + pnexti); + f2r = *(p2r + pnext); + f2i = *(p2r + pnexti); + f1r = *(p1r + pnext); + f1i = *(p1r + pnexti); + + *(p2r + pos) = f6r; + *(p1r + pos) = f5r; + *(p2r + posi) = f6i; + *(p1r + posi) = f5i; + *(p3r + pos) = f7r; + *(p0r + pos) = f4r; + *(p3r + posi) = f7i; + *(p0r + posi) = f4i; + + f6r = f2r + f3r; + f6i = f2i + f3i; + f3r = f2r - f3r; + f3i = f2i - f3i; + + f5r = f0r - f1r; + f5i = f0i - f1i; + f0r = f0r + f1r; + f0i = f0i + f1i; + + p3r += pnext; + p0r += pnext; + p1r += pnext; + p2r += pnext; + } + f7r = f5r + f3i; + f7i = f5i - f3r; + f5r = f5r - f3i; + f5i = f5i + f3r; + + f4r = f0r + f6r; + f4i = f0i + f6i; + f6r = f0r - f6r; + f6i = f0i - f6i; + + f2r = *(p2r + pos); + f2i = *(p2r + posi); + f1r = *(p1r + pos); + f1i = *(p1r + posi); + f3i = *(p3r + posi); + f0r = *(p0r + pos); + f3r = *(p3r + pos); + f0i = *(p0r + posi); + + *p3r = f7r; + *p0r = f4r; + *(p3r + 1) = f7i; + *(p0r + 1) = f4i; + *p1r = f5r; + *p2r = f6r; + *(p1r + 1) = f5i; + *(p2r + 1) = f6i; + + f7r = f2r + f3i; + f7i = f2i - f3r; + f2r = f2r - f3i; + f2i = f2i + f3r; + + f4r = f0r - f1i; + f4i = f0i + f1r; + t1r = f0r + f1i; + t1i = f0i - f1r; + + f5r = t1r - f7r * w1r - f7i * w1r; + f5i = t1i + f7r * w1r - f7i * w1r; + f7r = t1r * Two - f5r; + f7i = t1i * Two - f5i; + + f6r = f4r - f2r * w1r + f2i * w1r; + f6i = f4i - f2r * w1r - f2i * w1r; + f4r = f4r * Two - f6r; + f4i = f4i * Two - f6i; + + *(p2r + pos) = f6r; + *(p1r + pos) = f5r; + *(p2r + posi) = f6i; + *(p1r + posi) = f5i; + *(p3r + pos) = f7r; + *(p0r + pos) = f4r; + *(p3r + posi) = f7i; + *(p0r + posi) = f4i; +} + +//------------------------------------------------------------------------------ +// RADIX 8 Stages +//------------------------------------------------------------------------------ +template +void g_fft::ibfstages(FFT_TYPE *ioptr, int M, FFT_TYPE *inUtbl, int Ustride, + int NDiffU, int StageCnt) +{ + unsigned int pos; + unsigned int posi; + unsigned int pinc; + unsigned int pnext; + unsigned int NSameU; + int Uinc; + int Uinc2; + int Uinc4; + unsigned int DiffUCnt; + unsigned int SameUCnt; + unsigned int U2toU3; + + FFT_TYPE *pstrt; + FFT_TYPE *p0r, *p1r, *p2r, *p3r; + FFT_TYPE *u0r, *u0i, *u1r, *u1i, *u2r, *u2i; + + FFT_TYPE w0r, w0i, w1r, w1i, w2r, w2i, w3r, w3i; + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = 2.0; + + pinc = NDiffU * 2; // 2 floats per complex + pnext = pinc * 8; + pos = pinc * 4; + posi = pos + 1; + NSameU = POW2(M) / 8 / NDiffU; // 8 pts per butterfly + Uinc = (int) NSameU * Ustride; + Uinc2 = Uinc * 2; + Uinc4 = Uinc * 4; + U2toU3 = (POW2(M) / 8) * Ustride; + for (; StageCnt > 0; StageCnt--) { + + u0r = &inUtbl[0]; + u0i = &inUtbl[POW2(M - 2) * Ustride]; + u1r = u0r; + u1i = u0i; + u2r = u0r; + u2i = u0i; + + w0r = *u0r; + w0i = *u0i; + w1r = *u1r; + w1i = *u1i; + w2r = *u2r; + w2i = *u2i; + w3r = *(u2r + U2toU3); + w3i = *(u2i - U2toU3); + + pstrt = ioptr; + + p0r = pstrt; + p1r = pstrt + pinc; + p2r = p1r + pinc; + p3r = p2r + pinc; + +// Butterflys +// f0 - - t0 - - f0 - - f0 +// f1 - w0- f1 - - f1 - - f1 +// f2 - - f2 - w1- f2 - - f4 +// f3 - w0- t1 - iw1- f3 - - f5 +// f4 - - t0 - - f4 - w2- t0 +// f5 - w0- f5 - - f5 - w3- t1 +// f6 - - f6 - w1- f6 - iw2- f6 +// f7 - w0- t1 - iw1- f7 - iw3- f7 + + for (DiffUCnt = NDiffU; DiffUCnt > 0; DiffUCnt--) { + f0r = *p0r; + f0i = *(p0r + 1); + f1r = *p1r; + f1i = *(p1r + 1); + for (SameUCnt = NSameU - 1; SameUCnt > 0; SameUCnt--) { + f2r = *p2r; + f2i = *(p2r + 1); + f3r = *p3r; + f3i = *(p3r + 1); + + t0r = f0r + f1r * w0r - f1i * w0i; + t0i = f0i + f1r * w0i + f1i * w0r; + f1r = f0r * Two - t0r; + f1i = f0i * Two - t0i; + + f4r = *(p0r + pos); + f4i = *(p0r + posi); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + + f6r = *(p2r + pos); + f6i = *(p2r + posi); + f7r = *(p3r + pos); + f7i = *(p3r + posi); + + t1r = f2r - f3r * w0r + f3i * w0i; + t1i = f2i - f3r * w0i - f3i * w0r; + f2r = f2r * Two - t1r; + f2i = f2i * Two - t1i; + + f0r = t0r + f2r * w1r - f2i * w1i; + f0i = t0i + f2r * w1i + f2i * w1r; + f2r = t0r * Two - f0r; + f2i = t0i * Two - f0i; + + f3r = f1r + t1r * w1i + t1i * w1r; + f3i = f1i - t1r * w1r + t1i * w1i; + f1r = f1r * Two - f3r; + f1i = f1i * Two - f3i; + + t0r = f4r + f5r * w0r - f5i * w0i; + t0i = f4i + f5r * w0i + f5i * w0r; + f5r = f4r * Two - t0r; + f5i = f4i * Two - t0i; + + t1r = f6r - f7r * w0r + f7i * w0i; + t1i = f6i - f7r * w0i - f7i * w0r; + f6r = f6r * Two - t1r; + f6i = f6i * Two - t1i; + + f4r = t0r + f6r * w1r - f6i * w1i; + f4i = t0i + f6r * w1i + f6i * w1r; + f6r = t0r * Two - f4r; + f6i = t0i * Two - f4i; + + f7r = f5r + t1r * w1i + t1i * w1r; + f7i = f5i - t1r * w1r + t1i * w1i; + f5r = f5r * Two - f7r; + f5i = f5i * Two - f7i; + + t0r = f0r - f4r * w2r + f4i * w2i; + t0i = f0i - f4r * w2i - f4i * w2r; + f0r = f0r * Two - t0r; + f0i = f0i * Two - t0i; + + t1r = f1r - f5r * w3r + f5i * w3i; + t1i = f1i - f5r * w3i - f5i * w3r; + f1r = f1r * Two - t1r; + f1i = f1i * Two - t1i; + + *(p0r + pos) = t0r; + *(p0r + posi) = t0i; + *p0r = f0r; + *(p0r + 1) = f0i; + + p0r += pnext; + f0r = *p0r; + f0i = *(p0r + 1); + + *(p1r + pos) = t1r; + *(p1r + posi) = t1i; + *p1r = f1r; + *(p1r + 1) = f1i; + + p1r += pnext; + + f1r = *p1r; + f1i = *(p1r + 1); + + f4r = f2r - f6r * w2i - f6i * w2r; + f4i = f2i + f6r * w2r - f6i * w2i; + f6r = f2r * Two - f4r; + f6i = f2i * Two - f4i; + + f5r = f3r - f7r * w3i - f7i * w3r; + f5i = f3i + f7r * w3r - f7i * w3i; + f7r = f3r * Two - f5r; + f7i = f3i * Two - f5i; + + *p2r = f4r; + *(p2r + 1) = f4i; + *(p2r + pos) = f6r; + *(p2r + posi) = f6i; + + p2r += pnext; + + *p3r = f5r; + *(p3r + 1) = f5i; + *(p3r + pos) = f7r; + *(p3r + posi) = f7i; + + p3r += pnext; + } + + f2r = *p2r; + f2i = *(p2r + 1); + f3r = *p3r; + f3i = *(p3r + 1); + + t0r = f0r + f1r * w0r - f1i * w0i; + t0i = f0i + f1r * w0i + f1i * w0r; + f1r = f0r * Two - t0r; + f1i = f0i * Two - t0i; + + f4r = *(p0r + pos); + f4i = *(p0r + posi); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + + f6r = *(p2r + pos); + f6i = *(p2r + posi); + f7r = *(p3r + pos); + f7i = *(p3r + posi); + + t1r = f2r - f3r * w0r + f3i * w0i; + t1i = f2i - f3r * w0i - f3i * w0r; + f2r = f2r * Two - t1r; + f2i = f2i * Two - t1i; + + f0r = t0r + f2r * w1r - f2i * w1i; + f0i = t0i + f2r * w1i + f2i * w1r; + f2r = t0r * Two - f0r; + f2i = t0i * Two - f0i; + + f3r = f1r + t1r * w1i + t1i * w1r; + f3i = f1i - t1r * w1r + t1i * w1i; + f1r = f1r * Two - f3r; + f1i = f1i * Two - f3i; + + if ((int) DiffUCnt == NDiffU / 2) + Uinc4 = -Uinc4; + + u0r += Uinc4; + u0i -= Uinc4; + u1r += Uinc2; + u1i -= Uinc2; + u2r += Uinc; + u2i -= Uinc; + + pstrt += 2; + + t0r = f4r + f5r * w0r - f5i * w0i; + t0i = f4i + f5r * w0i + f5i * w0r; + f5r = f4r * Two - t0r; + f5i = f4i * Two - t0i; + + t1r = f6r - f7r * w0r + f7i * w0i; + t1i = f6i - f7r * w0i - f7i * w0r; + f6r = f6r * Two - t1r; + f6i = f6i * Two - t1i; + + f4r = t0r + f6r * w1r - f6i * w1i; + f4i = t0i + f6r * w1i + f6i * w1r; + f6r = t0r * Two - f4r; + f6i = t0i * Two - f4i; + + f7r = f5r + t1r * w1i + t1i * w1r; + f7i = f5i - t1r * w1r + t1i * w1i; + f5r = f5r * Two - f7r; + f5i = f5i * Two - f7i; + + w0r = *u0r; + w0i = *u0i; + w1r = *u1r; + w1i = *u1i; + + if ((int) DiffUCnt <= NDiffU / 2) + w0r = -w0r; + + t0r = f0r - f4r * w2r + f4i * w2i; + t0i = f0i - f4r * w2i - f4i * w2r; + f0r = f0r * Two - t0r; + f0i = f0i * Two - t0i; + + f4r = f2r - f6r * w2i - f6i * w2r; + f4i = f2i + f6r * w2r - f6i * w2i; + f6r = f2r * Two - f4r; + f6i = f2i * Two - f4i; + + *(p0r + pos) = t0r; + *p2r = f4r; + *(p0r + posi) = t0i; + *(p2r + 1) = f4i; + w2r = *u2r; + w2i = *u2i; + *p0r = f0r; + *(p2r + pos) = f6r; + *(p0r + 1) = f0i; + *(p2r + posi) = f6i; + + p0r = pstrt; + p2r = pstrt + pinc + pinc; + + t1r = f1r - f5r * w3r + f5i * w3i; + t1i = f1i - f5r * w3i - f5i * w3r; + f1r = f1r * Two - t1r; + f1i = f1i * Two - t1i; + + f5r = f3r - f7r * w3i - f7i * w3r; + f5i = f3i + f7r * w3r - f7i * w3i; + f7r = f3r * Two - f5r; + f7i = f3i * Two - f5i; + + *(p1r + pos) = t1r; + *p3r = f5r; + *(p1r + posi) = t1i; + *(p3r + 1) = f5i; + w3r = *(u2r + U2toU3); + w3i = *(u2i - U2toU3); + *p1r = f1r; + *(p3r + pos) = f7r; + *(p1r + 1) = f1i; + *(p3r + posi) = f7i; + + p1r = pstrt + pinc; + p3r = p2r + pinc; + } + NSameU /= 8; + Uinc /= 8; + Uinc2 /= 8; + Uinc4 = Uinc * 4; + NDiffU *= 8; + pinc *= 8; + pnext *= 8; + pos *= 8; + posi = pos + 1; + } +} + +//------------------------------------------------------------------------------ +// recursive bfstages calls to maximize on chip cache efficiency +//------------------------------------------------------------------------------ +template +void g_fft::ifftrecurs(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, int Ustride, + int NDiffU, int StageCnt) +{ + int i1; + + if (M <= (int) MCACHE) + ibfstages(ioptr, M, Utbl, Ustride, NDiffU, StageCnt); // RADIX 8 Stages + else { + for (i1 = 0; i1 < 8; i1++) { + ifftrecurs(&ioptr[i1 * POW2(M - 3) * 2], M - 3, Utbl, 8 * Ustride, + NDiffU, StageCnt - 1); // RADIX 8 Stages + } + ibfstages(ioptr, M, Utbl, Ustride, POW2(M - 3), 1); // RADIX 8 Stage + } +} + +//------------------------------------------------------------------------------ +// Compute in-place inverse complex fft on the rows of the input array +// INPUTS +// *ioptr = input data array +// M = log2 of fft size +// *Utbl = cosine table +// *BRLow = bit reversed counter table +// OUTPUTS +// *ioptr = output data array +//------------------------------------------------------------------------------ +template +void g_fft::iffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow) +{ + int StageCnt; + int NDiffU; + const FFT_TYPE scale = 1.0 / POW2(M); + + switch (M) { + case 0: + break; + case 1: + ifft2pt(ioptr, scale); // a 2 pt fft + break; + case 2: + ifft4pt(ioptr, scale); // a 4 pt fft + break; + case 3: + ifft8pt(ioptr, scale); // an 8 pt fft + break; + default: +// bit reverse and first radix 2 stage + scbitrevR2(ioptr, M, BRLow, scale); + StageCnt = (M - 1) / 3; // number of radix 8 stages + NDiffU = 2; // one radix 2 stage already complete + if ((M - 1 - (StageCnt * 3)) == 1) { + ibfR2(ioptr, M, NDiffU); // 1 radix 2 stage + NDiffU *= 2; + } + if ((M - 1 - (StageCnt * 3)) == 2) { + ibfR4(ioptr, M, NDiffU); // 1 radix 4 stage + NDiffU *= 4; + } + if (M <= (int) MCACHE) + ibfstages(ioptr, M, Utbl, 1, NDiffU, StageCnt); // RADIX 8 Stages + else + ifftrecurs(ioptr, M, Utbl, 1, NDiffU, StageCnt); // RADIX 8 Stages + } +} + +//------------------------------------------------------------------------------ +// parts of rffts1 +// RADIX 2 rfft +//------------------------------------------------------------------------------ +template +void g_fft::rfft1pt(FFT_TYPE *ioptr) +{ + FFT_TYPE f0r, f0i; + FFT_TYPE t0r, t0i; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + +// finish rfft + t0r = f0r + f0i; + t0i = f0r - f0i; + +// store result + ioptr[0] = t0r; + ioptr[1] = t0i; +} + +//------------------------------------------------------------------------------ +// RADIX 4 rfft +//------------------------------------------------------------------------------ +template +void g_fft::rfft2pt(FFT_TYPE *ioptr) +{ + FFT_TYPE f0r, f0i, f1r, f1i; + FFT_TYPE t0r, t0i; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[2]; + f1i = ioptr[3]; + +// Butterflys +// f0 - - t0 +// f1 - 1 - f1 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f1i - f0i; +// finish rfft + f0r = t0r + t0i; + f0i = t0r - t0i; + +// store result + ioptr[0] = f0r; + ioptr[1] = f0i; + ioptr[2] = f1r; + ioptr[3] = f1i; +} + +//------------------------------------------------------------------------------ +// RADIX 8 rfft +//------------------------------------------------------------------------------ +template +void g_fft::rfft4pt(FFT_TYPE *ioptr) +{ + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE t0r, t0i, t1r, t1i; + FFT_TYPE w0r = 1.0 / FFT_ROOT2; // cos(pi/4) + const FFT_TYPE Two = 2.0; + const FFT_TYPE scale = 0.5; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[4]; + f1i = ioptr[5]; + f2r = ioptr[2]; + f2i = ioptr[3]; + f3r = ioptr[6]; + f3i = ioptr[7]; + +// Butterflys +// f0 - - t0 - - f0 +// f1 - 1 - f1 - - f1 +// f2 - - f2 - 1 - f2 +// f3 - 1 - t1 - -i - f3 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = f2i - t0i; // neg for rfft + + f3r = f1r - t1i; + f3i = f1i + t1r; + f1r = f1r + t1i; + f1i = f1i - t1r; + +// finish rfft + t0r = f0r + f0i; // compute Re(x[0]) + t0i = f0r - f0i; // compute Re(x[N/2]) + + t1r = f1r + f3r; + t1i = f1i - f3i; + f0r = f1i + f3i; + f0i = f3r - f1r; + + f1r = t1r + w0r * f0r + w0r * f0i; + f1i = t1i - w0r * f0r + w0r * f0i; + f3r = Two * t1r - f1r; + f3i = f1i - Two * t1i; + +// store result + ioptr[4] = f2r; + ioptr[5] = f2i; + ioptr[0] = t0r; + ioptr[1] = t0i; + + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; + ioptr[6] = scale * f3r; + ioptr[7] = scale * f3i; +} + +//------------------------------------------------------------------------------ +// RADIX 16 rfft +//------------------------------------------------------------------------------ +template +void g_fft::rfft8pt(FFT_TYPE *ioptr) +{ + FFT_TYPE w0r = 1.0 / FFT_ROOT2; // cos(pi/4) + FFT_TYPE w1r = FFT_COSPID8; // cos(pi/8) + FFT_TYPE w1i = FFT_SINPID8; // sin(pi/8) + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = 2.0; + const FFT_TYPE scale = 0.5; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + f1r = ioptr[8]; + f1i = ioptr[9]; + f2r = ioptr[4]; + f2i = ioptr[5]; + f3r = ioptr[12]; + f3i = ioptr[13]; + f4r = ioptr[2]; + f4i = ioptr[3]; + f5r = ioptr[10]; + f5i = ioptr[11]; + f6r = ioptr[6]; + f6i = ioptr[7]; + f7r = ioptr[14]; + f7i = ioptr[15]; + +// Butterflys +// f0 - - t0 - - f0 - - f0 +// f1 - 1 - f1 - - f1 - - f1 +// f2 - - f2 - 1 - f2 - - f2 +// f3 - 1 - t1 - -i - f3 - - f3 +// f4 - - t0 - - f4 - 1 - t0 +// f5 - 1 - f5 - - f5 - w3 - f4 +// f6 - - f6 - 1 - f6 - -i - t1 +// f7 - 1 - t1 - -i - f7 - iw3- f6 + + t0r = f0r + f1r; + t0i = f0i + f1i; + f1r = f0r - f1r; + f1i = f0i - f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = t0i - f2i; + + f3r = f1r - t1i; + f3i = f1i + t1r; + f1r = f1r + t1i; + f1i = f1i - t1r; + + t0r = f4r + f5r; + t0i = f4i + f5i; + f5r = f4r - f5r; + f5i = f4i - f5i; + + t1r = f6r - f7r; + t1i = f6i - f7i; + f6r = f6r + f7r; + f6i = f6i + f7i; + + f4r = t0r + f6r; + f4i = t0i + f6i; + f6r = t0r - f6r; + f6i = t0i - f6i; + + f7r = f5r - t1i; + f7i = f5i + t1r; + f5r = f5r + t1i; + f5i = f5i - t1r; + + t0r = f0r - f4r; + t0i = f4i - f0i; // neg for rfft + f0r = f0r + f4r; + f0i = f0i + f4i; + + t1r = f2r - f6i; + t1i = f2i + f6r; + f2r = f2r + f6i; + f2i = f2i - f6r; + + f4r = f1r - f5r * w0r - f5i * w0r; + f4i = f1i + f5r * w0r - f5i * w0r; + f1r = f1r * Two - f4r; + f1i = f1i * Two - f4i; + + f6r = f3r + f7r * w0r - f7i * w0r; + f6i = f3i + f7r * w0r + f7i * w0r; + f3r = f3r * Two - f6r; + f3i = f3i * Two - f6i; + +// finish rfft + f5r = f0r + f0i; // compute Re(x[0]) + f5i = f0r - f0i; // compute Re(x[N/2]) + + f0r = f2r + t1r; + f0i = f2i - t1i; + f7r = f2i + t1i; + f7i = t1r - f2r; + + f2r = f0r + w0r * f7r + w0r * f7i; + f2i = f0i - w0r * f7r + w0r * f7i; + t1r = Two * f0r - f2r; + t1i = f2i - Two * f0i; + + f0r = f1r + f6r; + f0i = f1i - f6i; + f7r = f1i + f6i; + f7i = f6r - f1r; + + f1r = f0r + w1r * f7r + w1i * f7i; + f1i = f0i - w1i * f7r + w1r * f7i; + f6r = Two * f0r - f1r; + f6i = f1i - Two * f0i; + + f0r = f3r + f4r; + f0i = f3i - f4i; + f7r = f3i + f4i; + f7i = f4r - f3r; + + f3r = f0r + w1i * f7r + w1r * f7i; + f3i = f0i - w1r * f7r + w1i * f7i; + f4r = Two * f0r - f3r; + f4i = f3i - Two * f0i; + +// store result + ioptr[8] = t0r; + ioptr[9] = t0i; + ioptr[0] = f5r; + ioptr[1] = f5i; + + ioptr[4] = scale * f2r; + ioptr[5] = scale * f2i; + ioptr[12] = scale * t1r; + ioptr[13] = scale * t1i; + + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; + ioptr[6] = scale * f3r; + ioptr[7] = scale * f3i; + ioptr[10] = scale * f4r; + ioptr[11] = scale * f4i; + ioptr[14] = scale * f6r; + ioptr[15] = scale * f6i; +} + +//------------------------------------------------------------------------------ +// Finish RFFT +//------------------------------------------------------------------------------ +template +void g_fft::frstage(FFT_TYPE *ioptr, int M, FFT_TYPE *inUtbl) +{ + unsigned int pos; + unsigned int posi; + unsigned int diffUcnt; + + FFT_TYPE *p0r, *p1r; + FFT_TYPE *u0r, *u0i; + + FFT_TYPE w0r, w0i; + FFT_TYPE f0r, f0i, f1r, f1i, f4r, f4i, f5r, f5i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = 2.0; + + pos = POW2(M - 1); + posi = pos + 1; + + p0r = ioptr; + p1r = ioptr + pos / 2; + + u0r = inUtbl + POW2(M - 3); + + w0r = *u0r, f0r = *(p0r); + f0i = *(p0r + 1); + f4r = *(p0r + pos); + f4i = *(p0r + posi); + f1r = *(p1r); + f1i = *(p1r + 1); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + + t0r = Two * f0r + Two * f0i; // compute Re(x[0]) + t0i = Two * f0r - Two * f0i; // compute Re(x[N/2]) + t1r = f4r + f4r; + t1i = -f4i - f4i; + + f0r = f1r + f5r; + f0i = f1i - f5i; + f4r = f1i + f5i; + f4i = f5r - f1r; + + f1r = f0r + w0r * f4r + w0r * f4i; + f1i = f0i - w0r * f4r + w0r * f4i; + f5r = Two * f0r - f1r; + f5i = f1i - Two * f0i; + + *(p0r) = t0r; + *(p0r + 1) = t0i; + *(p0r + pos) = t1r; + *(p0r + posi) = t1i; + *(p1r) = f1r; + *(p1r + 1) = f1i; + *(p1r + pos) = f5r; + *(p1r + posi) = f5i; + + u0r = inUtbl + 1; + u0i = inUtbl + (POW2(M - 2) - 1); + + w0r = *u0r, w0i = *u0i; + + p0r = (ioptr + 2); + p1r = (ioptr + (POW2(M - 2) - 1) * 2); + +// Butterflys +// f0 - t0 - - f0 +// f5 - t1 - w0 - f5 +// f1 - t0 - - f1 +// f4 - t1 -iw0 - f4 + + for (diffUcnt = POW2(M - 3) - 1; diffUcnt > 0; diffUcnt--) { + + f0r = *(p0r); + f0i = *(p0r + 1); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + f1r = *(p1r); + f1i = *(p1r + 1); + f4r = *(p0r + pos); + f4i = *(p0r + posi); + + t0r = f0r + f5r; + t0i = f0i - f5i; + t1r = f0i + f5i; + t1i = f5r - f0r; + + f0r = t0r + w0r * t1r + w0i * t1i; + f0i = t0i - w0i * t1r + w0r * t1i; + f5r = Two * t0r - f0r; + f5i = f0i - Two * t0i; + + t0r = f1r + f4r; + t0i = f1i - f4i; + t1r = f1i + f4i; + t1i = f4r - f1r; + + f1r = t0r + w0i * t1r + w0r * t1i; + f1i = t0i - w0r * t1r + w0i * t1i; + f4r = Two * t0r - f1r; + f4i = f1i - Two * t0i; + + *(p0r) = f0r; + *(p0r + 1) = f0i; + *(p1r + pos) = f5r; + *(p1r + posi) = f5i; + + w0r = *++u0r; + w0i = *--u0i; + + *(p1r) = f1r; + *(p1r + 1) = f1i; + *(p0r + pos) = f4r; + *(p0r + posi) = f4i; + + p0r += 2; + p1r -= 2; + } +} + +//------------------------------------------------------------------------------ +// Compute in-place real fft on the rows of the input array +// The result is the complex spectra of the positive frequencies +// except the location for the first complex number contains the real +// values for DC and Nyquest +// INPUTS +// *ioptr = real input data array +// M = log2 of fft size +// *Utbl = cosine table +// *BRLow = bit reversed counter table +// OUTPUTS +// *ioptr = output data array in the following order +// Re(x[0]), Re(x[N/2]), Re(x[1]), Im(x[1]), Re(x[2]), Im(x[2]), +// ... Re(x[N/2-1]), Im(x[N/2-1]). +//------------------------------------------------------------------------------ +template +void g_fft::rffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow) +{ + FFT_TYPE scale; + int StageCnt; + int NDiffU; + + M = M - 1; + switch (M) { + case -1: + break; + case 0: + rfft1pt(ioptr); // a 2 pt fft + break; + case 1: + rfft2pt(ioptr); // a 4 pt fft + break; + case 2: + rfft4pt(ioptr); // an 8 pt fft + break; + case 3: + rfft8pt(ioptr); // a 16 pt fft + break; + default: + scale = 0.5; +// bit reverse and first radix 2 stage + scbitrevR2(ioptr, M, BRLow, scale); + StageCnt = (M - 1) / 3; // number of radix 8 stages + NDiffU = 2; // one radix 2 stage already complete + if ((M - 1 - (StageCnt * 3)) == 1) { + bfR2(ioptr, M, NDiffU); // 1 radix 2 stage + NDiffU *= 2; + } + if ((M - 1 - (StageCnt * 3)) == 2) { + bfR4(ioptr, M, NDiffU); // 1 radix 4 stage + NDiffU *= 4; + } + if (M <= (int) MCACHE) + bfstages(ioptr, M, Utbl, 2, NDiffU, StageCnt); // RADIX 8 Stages + else + fftrecurs(ioptr, M, Utbl, 2, NDiffU, StageCnt); // RADIX 8 Stages + frstage(ioptr, M + 1, Utbl); + } +} + +//------------------------------------------------------------------------------ +// parts of riffts1 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// RADIX 2 rifft +//------------------------------------------------------------------------------ +template +void g_fft::rifft1pt(FFT_TYPE *ioptr, FFT_TYPE scale) +{ + FFT_TYPE f0r, f0i; + FFT_TYPE t0r, t0i; + +// bit reversed load + f0r = ioptr[0]; + f0i = ioptr[1]; + +// finish rfft + t0r = f0r + f0i; + t0i = f0r - f0i; + +// store result + ioptr[0] = scale * t0r; + ioptr[1] = scale * t0i; +} + +//------------------------------------------------------------------------------ +// RADIX 4 rifft +//------------------------------------------------------------------------------ +template +void g_fft::rifft2pt(FFT_TYPE *ioptr, FFT_TYPE scale) +{ + FFT_TYPE f0r, f0i, f1r, f1i; + FFT_TYPE t0r, t0i; + const FFT_TYPE Two = FFT_TYPE(2.0); + +// bit reversed load + t0r = ioptr[0]; + t0i = ioptr[1]; + f1r = Two * ioptr[2]; + f1i = Two * ioptr[3]; + +// start rifft + f0r = t0r + t0i; + f0i = t0r - t0i; + +// Butterflys +// f0 - - t0 +// f1 - 1 - f1 + + t0r = f0r + f1r; + t0i = f0i - f1i; + f1r = f0r - f1r; + f1i = f0i + f1i; + +// store result + ioptr[0] = scale * t0r; + ioptr[1] = scale * t0i; + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; +} + +//------------------------------------------------------------------------------ +// RADIX 8 rifft +//------------------------------------------------------------------------------ +template +void g_fft::rifft4pt(FFT_TYPE *ioptr, FFT_TYPE scale) +{ + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE t0r, t0i, t1r, t1i; + FFT_TYPE w0r = 1.0 / FFT_ROOT2; // cos(pi/4) + const FFT_TYPE Two = FFT_TYPE(2.0); + +// bit reversed load + t0r = ioptr[0]; + t0i = ioptr[1]; + f2r = ioptr[2]; + f2i = ioptr[3]; + f1r = Two * ioptr[4]; + f1i = Two * ioptr[5]; + f3r = ioptr[6]; + f3i = ioptr[7]; + +// start rfft + f0r = t0r + t0i; // compute Re(x[0]) + f0i = t0r - t0i; // compute Re(x[N/2]) + + t1r = f2r + f3r; + t1i = f2i - f3i; + t0r = f2r - f3r; + t0i = f2i + f3i; + + f2r = t1r - w0r * t0r - w0r * t0i; + f2i = t1i + w0r * t0r - w0r * t0i; + f3r = Two * t1r - f2r; + f3i = f2i - Two * t1i; + +// Butterflys +// f0 - - t0 - - f0 +// f1 - 1 - f1 - - f1 +// f2 - - f2 - 1 - f2 +// f3 - 1 - t1 - i - f3 + + t0r = f0r + f1r; + t0i = f0i - f1i; + f1r = f0r - f1r; + f1i = f0i + f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = t0i - f2i; + + f3r = f1r + t1i; + f3i = f1i - t1r; + f1r = f1r - t1i; + f1i = f1i + t1r; + +// store result + ioptr[0] = scale * f0r; + ioptr[1] = scale * f0i; + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; + ioptr[4] = scale * f2r; + ioptr[5] = scale * f2i; + ioptr[6] = scale * f3r; + ioptr[7] = scale * f3i; +} + +//------------------------------------------------------------------------------ +// RADIX 16 rifft +//------------------------------------------------------------------------------ +template +void g_fft::rifft8pt(FFT_TYPE *ioptr, FFT_TYPE scale) +{ + FFT_TYPE w0r = (FFT_TYPE) (1.0 / FFT_ROOT2); // cos(pi/4) + FFT_TYPE w1r = FFT_COSPID8; // cos(pi/8) + FFT_TYPE w1i = FFT_SINPID8; // sin(pi/8) + FFT_TYPE f0r, f0i, f1r, f1i, f2r, f2i, f3r, f3i; + FFT_TYPE f4r, f4i, f5r, f5i, f6r, f6i, f7r, f7i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = FFT_TYPE(2.0); + +// bit reversed load + t0r = ioptr[0]; + t0i = ioptr[1]; + f4r = ioptr[2]; + f4i = ioptr[3]; + f2r = ioptr[4]; + f2i = ioptr[5]; + f6r = ioptr[6]; + f6i = ioptr[7]; + f1r = Two * ioptr[8]; + f1i = Two * ioptr[9]; + f5r = ioptr[10]; + f5i = ioptr[11]; + f3r = ioptr[12]; + f3i = ioptr[13]; + f7r = ioptr[14]; + f7i = ioptr[15]; + +// start rfft + f0r = t0r + t0i; // compute Re(x[0]) + f0i = t0r - t0i; // compute Re(x[N/2]) + + t0r = f2r + f3r; + t0i = f2i - f3i; + t1r = f2r - f3r; + t1i = f2i + f3i; + + f2r = t0r - w0r * t1r - w0r * t1i; + f2i = t0i + w0r * t1r - w0r * t1i; + f3r = Two * t0r - f2r; + f3i = f2i - Two * t0i; + + t0r = f4r + f7r; + t0i = f4i - f7i; + t1r = f4r - f7r; + t1i = f4i + f7i; + + f4r = t0r - w1i * t1r - w1r * t1i; + f4i = t0i + w1r * t1r - w1i * t1i; + f7r = Two * t0r - f4r; + f7i = f4i - Two * t0i; + + t0r = f6r + f5r; + t0i = f6i - f5i; + t1r = f6r - f5r; + t1i = f6i + f5i; + + f6r = t0r - w1r * t1r - w1i * t1i; + f6i = t0i + w1i * t1r - w1r * t1i; + f5r = Two * t0r - f6r; + f5i = f6i - Two * t0i; + +// Butterflys +// f0 - - t0 - - f0 - - f0 +// f1* - 1 - f1 - - f1 - - f1 +// f2 - - f2 - 1 - f2 - - f2 +// f3 - 1 - t1 - i - f3 - - f3 +// f4 - - t0 - - f4 - 1 - t0 +// f5 - 1 - f5 - - f5 - w3 - f4 +// f6 - - f6 - 1 - f6 - i - t1 +// f7 - 1 - t1 - i - f7 - iw3- f6 + + t0r = f0r + f1r; + t0i = f0i - f1i; + f1r = f0r - f1r; + f1i = f0i + f1i; + + t1r = f2r - f3r; + t1i = f2i - f3i; + f2r = f2r + f3r; + f2i = f2i + f3i; + + f0r = t0r + f2r; + f0i = t0i + f2i; + f2r = t0r - f2r; + f2i = t0i - f2i; + + f3r = f1r + t1i; + f3i = f1i - t1r; + f1r = f1r - t1i; + f1i = f1i + t1r; + + t0r = f4r + f5r; + t0i = f4i + f5i; + f5r = f4r - f5r; + f5i = f4i - f5i; + + t1r = f6r - f7r; + t1i = f6i - f7i; + f6r = f6r + f7r; + f6i = f6i + f7i; + + f4r = t0r + f6r; + f4i = t0i + f6i; + f6r = t0r - f6r; + f6i = t0i - f6i; + + f7r = f5r + t1i; + f7i = f5i - t1r; + f5r = f5r - t1i; + f5i = f5i + t1r; + + t0r = f0r - f4r; + t0i = f0i - f4i; + f0r = f0r + f4r; + f0i = f0i + f4i; + + t1r = f2r + f6i; + t1i = f2i - f6r; + f2r = f2r - f6i; + f2i = f2i + f6r; + + f4r = f1r - f5r * w0r + f5i * w0r; + f4i = f1i - f5r * w0r - f5i * w0r; + f1r = f1r * Two - f4r; + f1i = f1i * Two - f4i; + + f6r = f3r + f7r * w0r + f7i * w0r; + f6i = f3i - f7r * w0r + f7i * w0r; + f3r = f3r * Two - f6r; + f3i = f3i * Two - f6i; + +// store result + ioptr[0] = scale * f0r; + ioptr[1] = scale * f0i; + ioptr[2] = scale * f1r; + ioptr[3] = scale * f1i; + ioptr[4] = scale * f2r; + ioptr[5] = scale * f2i; + ioptr[6] = scale * f3r; + ioptr[7] = scale * f3i; + ioptr[8] = scale * t0r; + ioptr[9] = scale * t0i; + ioptr[10] = scale * f4r; + ioptr[11] = scale * f4i; + ioptr[12] = scale * t1r; + ioptr[13] = scale * t1i; + ioptr[14] = scale * f6r; + ioptr[15] = scale * f6i; +} + +//------------------------------------------------------------------------------ +// Start RIFFT +//------------------------------------------------------------------------------ +template +void g_fft::ifrstage(FFT_TYPE *ioptr, int M, FFT_TYPE *inUtbl) +{ + unsigned int pos; + unsigned int posi; + unsigned int diffUcnt; + + FFT_TYPE *p0r, *p1r; + FFT_TYPE *u0r, *u0i; + + FFT_TYPE w0r, w0i; + FFT_TYPE f0r, f0i, f1r, f1i, f4r, f4i, f5r, f5i; + FFT_TYPE t0r, t0i, t1r, t1i; + const FFT_TYPE Two = FFT_TYPE(2.0); + + pos = POW2(M - 1); + posi = pos + 1; + + p0r = ioptr; + p1r = ioptr + pos / 2; + + u0r = inUtbl + POW2(M - 3); + + w0r = *u0r, f0r = *(p0r); + f0i = *(p0r + 1); + f4r = *(p0r + pos); + f4i = *(p0r + posi); + f1r = *(p1r); + f1i = *(p1r + 1); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + + t0r = f0r + f0i; + t0i = f0r - f0i; + t1r = f4r + f4r; + t1i = -f4i - f4i; + + f0r = f1r + f5r; + f0i = f1i - f5i; + f4r = f1r - f5r; + f4i = f1i + f5i; + + f1r = f0r - w0r * f4r - w0r * f4i; + f1i = f0i + w0r * f4r - w0r * f4i; + f5r = Two * f0r - f1r; + f5i = f1i - Two * f0i; + + *(p0r) = t0r; + *(p0r + 1) = t0i; + *(p0r + pos) = t1r; + *(p0r + posi) = t1i; + *(p1r) = f1r; + *(p1r + 1) = f1i; + *(p1r + pos) = f5r; + *(p1r + posi) = f5i; + + u0r = inUtbl + 1; + u0i = inUtbl + (POW2(M - 2) - 1); + + w0r = *u0r, w0i = *u0i; + + p0r = (ioptr + 2); + p1r = (ioptr + (POW2(M - 2) - 1) * 2); + +// Butterflys +// f0 - t0 - f0 +// f1 - t1 -w0- f1 +// f2 - t0 - f2 +// f3 - t1 -iw0- f3 + + for (diffUcnt = POW2(M - 3) - 1; diffUcnt > 0; diffUcnt--) { + + f0r = *(p0r); + f0i = *(p0r + 1); + f5r = *(p1r + pos); + f5i = *(p1r + posi); + f1r = *(p1r); + f1i = *(p1r + 1); + f4r = *(p0r + pos); + f4i = *(p0r + posi); + + t0r = f0r + f5r; + t0i = f0i - f5i; + t1r = f0r - f5r; + t1i = f0i + f5i; + + f0r = t0r - w0i * t1r - w0r * t1i; + f0i = t0i + w0r * t1r - w0i * t1i; + f5r = Two * t0r - f0r; + f5i = f0i - Two * t0i; + + t0r = f1r + f4r; + t0i = f1i - f4i; + t1r = f1r - f4r; + t1i = f1i + f4i; + + f1r = t0r - w0r * t1r - w0i * t1i; + f1i = t0i + w0i * t1r - w0r * t1i; + f4r = Two * t0r - f1r; + f4i = f1i - Two * t0i; + + *(p0r) = f0r; + *(p0r + 1) = f0i; + *(p1r + pos) = f5r; + *(p1r + posi) = f5i; + + w0r = *++u0r; + w0i = *--u0i; + + *(p1r) = f1r; + *(p1r + 1) = f1i; + *(p0r + pos) = f4r; + *(p0r + posi) = f4i; + + p0r += 2; + p1r -= 2; + } +} + +//------------------------------------------------------------------------------ +// Compute in-place real ifft on the rows of the input array +// data order as from rffts1 +// INPUTS +// *ioptr = input data array in the following order +// M = log2 of fft size +// Re(x[0]), Re(x[N/2]), Re(x[1]), Im(x[1]), +// Re(x[2]), Im(x[2]), ... Re(x[N/2-1]), Im(x[N/2-1]). +// *Utbl = cosine table +// *BRLow = bit reversed counter table +// OUTPUTS +// *ioptr = real output data array +//------------------------------------------------------------------------------ +template +void g_fft::riffts1(FFT_TYPE *ioptr, int M, FFT_TYPE *Utbl, short *BRLow) +{ + FFT_TYPE scale; + int StageCnt; + int NDiffU; + + scale = (FFT_TYPE)(1.0 / (float)((int)POW2(M))); + M = M - 1; + switch (M) { + case -1: + break; + case 0: + rifft1pt(ioptr, scale); // a 2 pt fft + break; + case 1: + rifft2pt(ioptr, scale); // a 4 pt fft + break; + case 2: + rifft4pt(ioptr, scale); // an 8 pt fft + break; + case 3: + rifft8pt(ioptr, scale); // a 16 pt fft + break; + default: + ifrstage(ioptr, M + 1, Utbl); +// bit reverse and first radix 2 stage + scbitrevR2(ioptr, M, BRLow, scale); + StageCnt = (M - 1) / 3; // number of radix 8 stages + NDiffU = 2; // one radix 2 stage already complete + if ((M - 1 - (StageCnt * 3)) == 1) { + ibfR2(ioptr, M, NDiffU); // 1 radix 2 stage + NDiffU *= 2; + } + if ((M - 1 - (StageCnt * 3)) == 2) { + ibfR4(ioptr, M, NDiffU); // 1 radix 4 stage + NDiffU *= 4; + } + if (M <= (int) MCACHE) + ibfstages(ioptr, M, Utbl, 2, NDiffU, StageCnt); // RADIX 8 Stages + else + ifftrecurs(ioptr, M, Utbl, 2, NDiffU, StageCnt); // RADIX 8 Stages + } +} + +//============================================================================== +// End of original C functions +// +// Wrapper methods for simple class access +//============================================================================== + +//------------------------------------------------------------------------------ +// malloc and init cosine and bit reversed tables for a given size +// fft, ifft, rfft, rifft +// INPUTS +// M = log2 of fft size (ex M=10 for 1024 point fft) +// OUTPUTS +// private cosine and bit reversed tables +//------------------------------------------------------------------------------ +template +void g_fft::fftInit() +{ + for (int i = 0; i < 32; i++) + { + FFT_table_1[i] = (FFT_TYPE*)0; + FFT_table_2[i] = (short int*)0; + } + + FFT_N = ConvertFFTSize(FFT_size); + +// create and initialize cos table + FFT_table_1[FFT_N] = new FFT_TYPE[(POW2(FFT_N) / 4 + 1)]; + fftCosInit(FFT_N, FFT_table_1[FFT_N]); + +// create and initialize bit reverse tables + FFT_table_2[FFT_N/2] = new short[POW2(FFT_N/2 - 1)]; + fftBRInit(FFT_N, FFT_table_2[FFT_N/2]); + + if ((FFT_N % 2) == 0) { // FFT_N/2 = (FFT_N-1)/2 if FFT_N is odd. Prevents memory leak + FFT_table_2[(FFT_N - 1) / 2] = new short[POW2((FFT_N - 1) / 2 - 1)]; + } + fftBRInit(FFT_N - 1, FFT_table_2[(FFT_N - 1) / 2]); + + Utbl = ((FFT_TYPE**) FFT_table_1)[FFT_N]; + BRLow = ((short**) FFT_table_2)[FFT_N / 2]; + +} + +//------------------------------------------------------------------------------ +// convert from N to LOG2(N) +//------------------------------------------------------------------------------ +template +int g_fft::ConvertFFTSize(int N) +{ + if (N <= 0) N = -N; + + switch (N) { + case 0x00000001: return 0; // 1 + case 0x00000002: return 1; // 2 + case 0x00000004: return 2; // 4 + case 0x00000008: return 3; // 8 + case 0x00000010: return 4; // 16 + case 0x00000020: return 5; // 32 + case 0x00000040: return 6; // 64 + case 0x00000080: return 7; // 128 + case 0x00000100: return 8; // 256 + case 0x00000200: return 9; // 512 + case 0x00000400: return 10; // 1024 + case 0x00000800: return 11; // 2048 + case 0x00001000: return 12; // 4096 + case 0x00002000: return 13; // 8192 + case 0x00004000: return 14; // 16384 + case 0x00008000: return 15; // 32768 + case 0x00010000: return 16; // 65536 + case 0x00020000: return 17; // 131072 + case 0x00040000: return 18; // 262144 + case 0x00080000: return 19; // 525288 + case 0x00100000: return 20; // 1048576 + case 0x00200000: return 21; // 2097152 + case 0x00400000: return 22; // 4194304 + case 0x00800000: return 23; // 8388608 + case 0x01000000: return 24; // 16777216 + case 0x02000000: return 25; // 33554432 + case 0x04000000: return 26; // 67108864 + case 0x08000000: return 27; // 134217728 + case 0x10000000: return 28; // 268435456 + } + return 0; +} + +//------------------------------------------------------------------------------ +// Compute in-place complex FFT +// FFTsize: FFT length in samples +// buf: array of FFTsize*2 FFT_TYPE values, +// in interleaved real/imaginary format +//------------------------------------------------------------------------------ +template +void g_fft::ComplexFFT(std::complex *buf) +{ + void *ptr = buf; + FFT_TYPE *nbuf = static_cast(ptr); + ffts1(nbuf, FFT_N, Utbl, BRLow); +} + +//------------------------------------------------------------------------------ +// Compute in-place inverse complex FFT +// FFTsize: FFT length in samples +// buf: array of FFTsize*2 FFT_TYPE values, +// in interleaved real/imaginary format +// Output should be scaled by the return value of +// GetInverseComplexFFTScale(fft_struct, FFTsize). +//------------------------------------------------------------------------------ +template +void g_fft::InverseComplexFFT(std::complex *buf) +{ + void *ptr = buf; + FFT_TYPE *nbuf = static_cast(ptr); + iffts1(nbuf, FFT_N, Utbl, BRLow); +} + +//------------------------------------------------------------------------------ +// Compute in-place real FFT +// FFTsize: FFT length in samples +// buf: array of FFTsize FFT_TYPE values; output is in interleaved +// real/imaginary format, except for buf[1] which is the real +// part for the Nyquist frequency +//------------------------------------------------------------------------------ +template +void g_fft::RealFFT(std::complex *buf) +{ + void *ptr = buf; + FFT_TYPE *nbuf = static_cast(ptr); + rffts1(nbuf, FFT_N, Utbl, BRLow); +} + +//------------------------------------------------------------------------------ +// Compute in-place inverse real FFT +// FFTsize: FFT length in samples +// buf: array of FFTsize FFT_TYPE values; input is expected to be in +// interleaved real/imaginary format, except for buf[1] which +// is the real part for the Nyquist frequency +// Output should be scaled by the return value of +// GetInverseRealFFTScale(fft_struct, FFTsize). +//------------------------------------------------------------------------------ +template +void g_fft::InverseRealFFT(std::complex *buf) +{ + void *ptr = buf; + FFT_TYPE *nbuf = static_cast(ptr); + riffts1(nbuf, FFT_N, Utbl, BRLow); +} + +//------------------------------------------------------------------------------ +// Returns the amplitude scale that should be applied to the result of +// an inverse complex FFT with a length of 'FFTsize' samples. +//------------------------------------------------------------------------------ +template +FFT_TYPE g_fft::GetInverseComplexFFTScale() +{ + return FFT_TYPE(1.0); +} + +//------------------------------------------------------------------------------ +// Returns the amplitude scale that should be applied to the result of +// an inverse real FFT with a length of 'FFTsize' samples. +//------------------------------------------------------------------------------ +template +FFT_TYPE g_fft::GetInverseRealFFTScale() +{ + return FFT_TYPE(1.0); +} + +#endif + diff --git a/android/app/src/main/cpp/dsp/glscopeinterface.h b/android/app/src/main/cpp/dsp/glscopeinterface.h new file mode 100644 index 0000000..d982665 --- /dev/null +++ b/android/app/src/main/cpp/dsp/glscopeinterface.h @@ -0,0 +1,44 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_GLSCOPEINTERFACE_H_ +#define SDRBASE_DSP_GLSCOPEINTERFACE_H_ + +#include "glscopesettings.h" + +class GLScopeInterface +{ +public: + GLScopeInterface() {} + virtual ~GLScopeInterface() {} + virtual void setTraces(std::vector* tracesData, std::vector* traces) = 0; + virtual void newTraces(std::vector* traces, int traceIndex, std::vector* projectionTypes) = 0; + virtual void setSampleRate(int sampleRate) = 0; + virtual void setTraceSize(int trceSize, bool emitSignal = false) = 0; + virtual void setTriggerPre(uint32_t triggerPre, bool emitSignal = false) = 0; + virtual const QAtomicInt& getProcessingTraceIndex() const = 0; + virtual void setTimeBase(int timeBase) = 0; + virtual void setTimeOfsProMill(int timeOfsProMill) = 0; + virtual void setFocusedTriggerData(GLScopeSettings::TriggerData& triggerData) = 0; + virtual void setFocusedTraceIndex(uint32_t traceIndex) = 0; + virtual void setConfigChanged() = 0; + virtual void updateDisplay() = 0; +}; + +#endif // SDRBASE_DSP_GLSCOPEINTERFACE_H_ diff --git a/android/app/src/main/cpp/dsp/glscopesettings.cpp b/android/app/src/main/cpp/dsp/glscopesettings.cpp new file mode 100644 index 0000000..7716c0f --- /dev/null +++ b/android/app/src/main/cpp/dsp/glscopesettings.cpp @@ -0,0 +1,498 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" +#include "glscopesettings.h" + +#include "SWGGLScope.h" + +const double GLScopeSettings::AMPS[27] = { + 2e-1, 1e-1, 5e-2, + 2e-2, 1e-2, 5e-3, + 2e-3, 1e-3, 5e-4, + 2e-4, 1e-4, 5e-5, + 2e-5, 1e-5, 5e-6, + 2e-6, 1e-6, 5e-7, + 2e-7, 1e-7, 5e-8, + 2e-8, 1e-8, 5e-9, + 2e-9, 1e-9, 5e-10, +}; + +GLScopeSettings::GLScopeSettings() +{ + resetToDefaults(); +} + +GLScopeSettings::GLScopeSettings(const GLScopeSettings& t) +{ + resetToDefaults(); + + m_tracesData = t.m_tracesData; + m_triggersData = t.m_triggersData; + m_displayMode = t.m_displayMode; + m_traceIntensity = t.m_traceIntensity; + m_gridIntensity = t.m_gridIntensity; + m_time = t.m_time; + m_timeOfs = t.m_timeOfs; + m_traceLenMult = t.m_traceLenMult; + m_trigPre = t.m_trigPre; +} + +GLScopeSettings::~GLScopeSettings() +{} + +void GLScopeSettings::resetToDefaults() +{ + m_displayMode = DisplayX; + m_traceIntensity = 50; + m_gridIntensity = 10; + m_time = 1; + m_timeOfs = 0; + m_traceLenMult = 1; + m_trigPre = 0; + m_freerun = true; +} + +QByteArray GLScopeSettings::serialize() const +{ + SimpleSerializer s(1); + + // first row + s.writeS32(1, (int) m_displayMode); + s.writeS32(2, m_traceIntensity); + s.writeS32(3, m_gridIntensity); + s.writeS32(4, m_time); + // s.writeS32(5, m_timeOfs); + s.writeS32(6, m_traceLenMult); + s.writeBool(7, m_freerun); + + std::vector::const_iterator traceDataIt = m_tracesData.begin(); + unsigned int i = 0; + + for (; traceDataIt != m_tracesData.end(); ++traceDataIt, i++) + { + if (20 + 16*i > 200) { + break; + } + + s.writeS32(20 + 16*i, (int) traceDataIt->m_projectionType); + s.writeFloat(21 + 16*i, traceDataIt->m_amp); + s.writeFloat(22 + 16*i, traceDataIt->m_ofs); + s.writeS32(24 + 16*i, traceDataIt->m_traceDelayCoarse); + s.writeS32(25 + 16*i, traceDataIt->m_traceDelayFine); + s.writeFloat(26 + 16*i, traceDataIt->m_traceColorR); + s.writeFloat(27 + 16*i, traceDataIt->m_traceColorG); + s.writeFloat(28 + 16*i, traceDataIt->m_traceColorB); + s.writeU32(29 + 16*i, traceDataIt->m_streamIndex); + } + + s.writeU32(10, i); + s.writeU32(200, m_triggersData.size()); + s.writeS32(201, m_trigPre); + std::vector::const_iterator triggerDataIt = m_triggersData.begin(); + i = 0; + + for (; triggerDataIt != m_triggersData.end(); ++triggerDataIt, i++) + { + s.writeS32(210 + 16*i, (int) triggerDataIt->m_projectionType); + s.writeS32(211 + 16*i, triggerDataIt->m_triggerRepeat); + s.writeBool(212 + 16*i, triggerDataIt->m_triggerPositiveEdge); + s.writeBool(213 + 16*i, triggerDataIt->m_triggerBothEdges); + s.writeS32(214 + 16*i, triggerDataIt->m_triggerLevelCoarse); + s.writeS32(215 + 16*i, triggerDataIt->m_triggerLevelFine); + s.writeS32(216 + 16*i, triggerDataIt->m_triggerDelayCoarse); + s.writeS32(217 + 16*i, triggerDataIt->m_triggerDelayFine); + s.writeFloat(218 + 16*i, triggerDataIt->m_triggerColorR); + s.writeFloat(219 + 16*i, triggerDataIt->m_triggerColorG); + s.writeFloat(220 + 16*i, triggerDataIt->m_triggerColorB); + s.writeU32(221 + 16*i, triggerDataIt->m_triggerHoldoff); + s.writeU32(222 + 16*i, triggerDataIt->m_streamIndex); + } + + return s.final(); +} + +bool GLScopeSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intValue; + uint32_t uintValue; + bool boolValue; + + d.readS32(1, &intValue, (int) DisplayX); + m_displayMode = (DisplayMode) intValue; + d.readS32(2, &m_traceIntensity, 50); + d.readS32(3, &m_gridIntensity, 10); + d.readS32(4, &m_time, 1); + // d.readS32(5, &m_timeOfs, 0); + d.readS32(6, &m_traceLenMult, 1); + d.readBool(7, &m_freerun, true); + d.readS32(201, &m_trigPre, 0); + + uint32_t nbTracesSaved; + d.readU32(10, &nbTracesSaved, 1); + m_tracesData.clear(); + float r, g, b; + + for (unsigned int iTrace = 0; iTrace < nbTracesSaved; iTrace++) + { + if (20 + 16*iTrace > 200) { + break; + } + + m_tracesData.push_back(TraceData()); + + d.readS32(20 + 16*iTrace, &intValue, 0); + m_tracesData.back().m_projectionType = (Projector::ProjectionType) intValue; + d.readFloat(21 + 16*iTrace, &m_tracesData.back().m_amp, 1.0f); + d.readFloat(22 + 16*iTrace, &m_tracesData.back().m_ofs, 0.0f); + d.readS32(24 + 16*iTrace, &intValue, 0); + m_tracesData.back().m_traceDelayCoarse = intValue; + d.readS32(25 + 16*iTrace, &intValue, 0); + m_tracesData.back().m_traceDelayFine = intValue; + d.readFloat(26 + 16*iTrace, &r, 1.0f); + d.readFloat(27 + 16*iTrace, &g, 1.0f); + d.readFloat(28 + 16*iTrace, &b, 1.0f); + m_tracesData.back().m_traceColorR = r; + m_tracesData.back().m_traceColorG = g; + m_tracesData.back().m_traceColorB = b; + m_tracesData.back().m_traceColor.setRedF(r); + m_tracesData.back().m_traceColor.setGreenF(g); + m_tracesData.back().m_traceColor.setBlueF(b); + d.readU32(29 + 16*iTrace, &uintValue, 0); + m_tracesData.back().m_streamIndex = uintValue; + } + + uint32_t nbTriggersSaved; + d.readU32(200, &nbTriggersSaved, 1); + m_triggersData.clear(); + + for (unsigned int iTrigger = 0; iTrigger < nbTriggersSaved; iTrigger++) + { + m_triggersData.push_back(TriggerData()); + + d.readS32(210 + 16*iTrigger, &intValue, 0); + m_triggersData.back().m_projectionType = (Projector::ProjectionType) intValue; + d.readS32(211 + 16*iTrigger, &intValue, 1); + m_triggersData.back().m_triggerRepeat = intValue; + d.readBool(212 + 16*iTrigger, &boolValue, true); + m_triggersData.back().m_triggerPositiveEdge = boolValue; + d.readBool(213 + 16*iTrigger, &boolValue, false); + m_triggersData.back().m_triggerBothEdges = boolValue; + d.readS32(214 + 16*iTrigger, &intValue, 1); + m_triggersData.back().m_triggerLevelCoarse = intValue; + d.readS32(215 + 16*iTrigger, &intValue, 1); + m_triggersData.back().m_triggerLevelFine = intValue; + d.readS32(216 + 16*iTrigger, &intValue, 1); + m_triggersData.back().m_triggerDelayCoarse = intValue; + d.readS32(217 + 16*iTrigger, &intValue, 1); + m_triggersData.back().m_triggerDelayFine = intValue; + d.readFloat(218 + 16*iTrigger, &r, 1.0f); + d.readFloat(219 + 16*iTrigger, &g, 1.0f); + d.readFloat(220 + 16*iTrigger, &b, 1.0f); + m_triggersData.back().m_triggerColorR = r; + m_triggersData.back().m_triggerColorG = g; + m_triggersData.back().m_triggerColorB = b; + m_triggersData.back().m_triggerColor.setRedF(r); + m_triggersData.back().m_triggerColor.setGreenF(g); + m_triggersData.back().m_triggerColor.setBlueF(b); + d.readU32(221 + 16*iTrigger, &uintValue, 1); + m_triggersData.back().m_triggerHoldoff = uintValue; + d.readU32(222 + 16*iTrigger, &uintValue, 0); + m_triggersData.back().m_streamIndex = uintValue; + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +void GLScopeSettings::formatTo(SWGSDRangel::SWGObject *swgObject) const +{ + SWGSDRangel::SWGGLScope *swgScope = static_cast(swgObject); + + swgScope->setDisplayMode(m_displayMode); + swgScope->setGridIntensity(m_gridIntensity); + swgScope->setTime(m_time); + swgScope->setTimeOfs(m_timeOfs); + swgScope->setTraceIntensity(m_traceIntensity); + swgScope->setTraceLenMult(m_traceLenMult); + swgScope->setTrigPre(m_trigPre); + + // array of traces + swgScope->setTracesData(new QList); + std::vector::const_iterator traceIt = m_tracesData.begin(); + + for (; traceIt != m_tracesData.end(); ++traceIt) + { + swgScope->getTracesData()->append(new SWGSDRangel::SWGTraceData); + swgScope->getTracesData()->back()->setStreamIndex(traceIt->m_streamIndex); + swgScope->getTracesData()->back()->setAmp(traceIt->m_amp); + swgScope->getTracesData()->back()->setHasTextOverlay(traceIt->m_hasTextOverlay ? 1 : 0); + swgScope->getTracesData()->back()->setStreamIndex(traceIt->m_streamIndex); + swgScope->getTracesData()->back()->setOfs(traceIt->m_ofs); + swgScope->getTracesData()->back()->setProjectionType((int) traceIt->m_projectionType); + swgScope->getTracesData()->back()->setTextOverlay(new QString(traceIt->m_textOverlay)); + swgScope->getTracesData()->back()->setTraceColor(qColorToInt(traceIt->m_traceColor)); + swgScope->getTracesData()->back()->setTraceColorB(traceIt->m_traceColorB); + swgScope->getTracesData()->back()->setTraceColorG(traceIt->m_traceColorG); + swgScope->getTracesData()->back()->setTraceColorR(traceIt->m_traceColorR); + swgScope->getTracesData()->back()->setTraceDelay(traceIt->m_traceDelay); + swgScope->getTracesData()->back()->setTraceDelayCoarse(traceIt->m_traceDelayCoarse); + swgScope->getTracesData()->back()->setTraceDelayFine(traceIt->m_traceDelayFine); + swgScope->getTracesData()->back()->setTriggerDisplayLevel(traceIt->m_triggerDisplayLevel); + swgScope->getTracesData()->back()->setViewTrace(traceIt->m_viewTrace ? 1 : 0); + } + + // array of triggers + swgScope->setTriggersData(new QList); + std::vector::const_iterator triggerIt = m_triggersData.begin(); + + for (; triggerIt != m_triggersData.end(); ++triggerIt) + { + swgScope->getTriggersData()->append(new SWGSDRangel::SWGTriggerData); + swgScope->getTriggersData()->back()->setStreamIndex(triggerIt->m_streamIndex); + swgScope->getTriggersData()->back()->setInputIndex(triggerIt->m_inputIndex); + swgScope->getTriggersData()->back()->setProjectionType((int) triggerIt->m_projectionType); + swgScope->getTriggersData()->back()->setTriggerBothEdges(triggerIt->m_triggerBothEdges ? 1 : 0); + swgScope->getTriggersData()->back()->setTriggerColor(qColorToInt(triggerIt->m_triggerColor)); + swgScope->getTriggersData()->back()->setTriggerColorB(triggerIt->m_triggerColorB); + swgScope->getTriggersData()->back()->setTriggerColorG(triggerIt->m_triggerColorG); + swgScope->getTriggersData()->back()->setTriggerColorR(triggerIt->m_triggerColorR); + swgScope->getTriggersData()->back()->setTriggerDelay(triggerIt->m_triggerDelay); + swgScope->getTriggersData()->back()->setTriggerDelayCoarse(triggerIt->m_triggerDelayCoarse); + swgScope->getTriggersData()->back()->setTriggerDelayFine(triggerIt->m_triggerDelayFine); + swgScope->getTriggersData()->back()->setTriggerDelayMult(triggerIt->m_triggerDelayMult); + swgScope->getTriggersData()->back()->setTriggerHoldoff(triggerIt->m_triggerHoldoff ? 1 : 0); + swgScope->getTriggersData()->back()->setTriggerLevel(triggerIt->m_triggerLevel); + swgScope->getTriggersData()->back()->setTriggerLevelCoarse(triggerIt->m_triggerLevelCoarse); + swgScope->getTriggersData()->back()->setTriggerLevelFine(triggerIt->m_triggerLevelFine); + swgScope->getTriggersData()->back()->setTriggerPositiveEdge(triggerIt->m_triggerPositiveEdge ? 1 : 0); + swgScope->getTriggersData()->back()->setTriggerRepeat(triggerIt->m_triggerRepeat); + } +} + +void GLScopeSettings::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject) +{ + SWGSDRangel::SWGGLScope *swgScope = + static_cast(const_cast(swgObject)); + + if (keys.contains("scopeConfig.displayMode")) { + m_displayMode = (GLScopeSettings::DisplayMode) swgScope->getDisplayMode(); + } + if (keys.contains("scopeConfig.gridIntensity")) { + m_gridIntensity = swgScope->getGridIntensity(); + } + if (keys.contains("scopeConfig.time")) { + m_time = swgScope->getTime(); + } + if (keys.contains("scopeConfig.timeOfs")) { + m_timeOfs = swgScope->getTimeOfs(); + } + if (keys.contains("scopeConfig.traceIntensity")) { + m_traceIntensity = swgScope->getTraceIntensity(); + } + if (keys.contains("scopeConfig.traceLenMult")) { + m_traceLenMult = swgScope->getTraceLenMult(); + } + if (keys.contains("scopeConfig.trigPre")) { + m_trigPre = swgScope->getTrigPre(); + } + // traces + if (keys.contains("scopeConfig.tracesData")) + { + QList *tracesData = swgScope->getTracesData(); + m_tracesData.clear(); + + for (int i = 0; i < 10; i++) // no more than 10 traces anyway + { + if (keys.contains(QString("scopeConfig.tracesData[%1]").arg(i))) + { + SWGSDRangel::SWGTraceData *traceData = tracesData->at(i); + m_tracesData.push_back(GLScopeSettings::TraceData()); + + if (keys.contains(QString("scopeConfig.tracesData[%1].streamIndex").arg(i))) { + m_tracesData.back().m_streamIndex = traceData->getStreamIndex(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].amp").arg(i))) { + m_tracesData.back().m_amp = traceData->getAmp(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].hasTextOverlay").arg(i))) { + m_tracesData.back().m_hasTextOverlay = traceData->getHasTextOverlay() != 0; + } + if (keys.contains(QString("scopeConfig.tracesData[%1].inputIndex").arg(i))) { + m_tracesData.back().m_streamIndex = traceData->getStreamIndex(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].ofs").arg(i))) { + m_tracesData.back().m_ofs = traceData->getOfs(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].projectionType").arg(i))) { + m_tracesData.back().m_projectionType = (Projector::ProjectionType) traceData->getProjectionType(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].traceColor").arg(i))) { + m_tracesData.back().m_traceColor = intToQColor(traceData->getTraceColor()); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].traceColorB").arg(i))) { + m_tracesData.back().m_traceColorB = traceData->getTraceColorB(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].traceColorG").arg(i))) { + m_tracesData.back().m_traceColorG = traceData->getTraceColorG(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].traceColorR").arg(i))) { + m_tracesData.back().m_traceColorR = traceData->getTraceColorR(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].traceDelay").arg(i))) { + m_tracesData.back().m_traceDelay = traceData->getTraceDelay(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].traceDelayCoarse").arg(i))) { + m_tracesData.back().m_traceDelayCoarse = traceData->getTraceDelayCoarse(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].traceDelayFine").arg(i))) { + m_tracesData.back().m_traceDelayFine = traceData->getTraceDelayFine(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].triggerDisplayLevel").arg(i))) { + m_tracesData.back().m_triggerDisplayLevel = traceData->getTriggerDisplayLevel(); + } + if (keys.contains(QString("scopeConfig.tracesData[%1].viewTrace").arg(i))) { + m_tracesData.back().m_viewTrace = traceData->getViewTrace() != 0; + } + } + else + { + break; + } + } + } + // triggers + if (keys.contains("scopeConfig.triggersData")) + { + QList *triggersData = swgScope->getTriggersData(); + m_triggersData.clear(); + + for (int i = 0; i < 10; i++) // no more than 10 triggers anyway + { + if (keys.contains(QString("scopeConfig.triggersData[%1]").arg(i))) + { + SWGSDRangel::SWGTriggerData *triggerData = triggersData->at(i); + m_triggersData.push_back(GLScopeSettings::TriggerData()); + + if (keys.contains(QString("scopeConfig.triggersData[%1].streamIndex").arg(i))) { + m_triggersData.back().m_streamIndex = triggerData->getStreamIndex(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].inputIndex").arg(i))) { + m_triggersData.back().m_inputIndex = triggerData->getInputIndex(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].projectionType").arg(i))) { + m_triggersData.back().m_projectionType = (Projector::ProjectionType) triggerData->getProjectionType(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerBothEdges").arg(i))) { + m_triggersData.back().m_triggerBothEdges = triggerData->getTriggerBothEdges() != 0; + } + if (keys.contains(QString("scopeConfig.tracesData[%1].triggerColor").arg(i))) { + m_tracesData.back().m_traceColor = intToQColor(triggerData->getTriggerColor()); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerColorB").arg(i))) { + m_triggersData.back().m_triggerColorB = triggerData->getTriggerColorB(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerColorG").arg(i))) { + m_triggersData.back().m_triggerColorG = triggerData->getTriggerColorG(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerColorR").arg(i))) { + m_triggersData.back().m_triggerColorR = triggerData->getTriggerColorR(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelay").arg(i))) { + m_triggersData.back().m_triggerDelay = triggerData->getTriggerDelay(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelayCoarse").arg(i))) { + m_triggersData.back().m_triggerDelayCoarse = triggerData->getTriggerDelayCoarse(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelayFine").arg(i))) { + m_triggersData.back().m_triggerDelayFine = triggerData->getTriggerDelayFine(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerDelayMult").arg(i))) { + m_triggersData.back().m_triggerDelayMult = triggerData->getTriggerDelayMult(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerHoldoff").arg(i))) { + m_triggersData.back().m_triggerHoldoff = triggerData->getTriggerHoldoff(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerLevel").arg(i))) { + m_triggersData.back().m_triggerLevel = triggerData->getTriggerLevel(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerLevelCoarse").arg(i))) { + m_triggersData.back().m_triggerLevelCoarse = triggerData->getTriggerLevelCoarse(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerLevelFine").arg(i))) { + m_triggersData.back().m_triggerLevelFine = triggerData->getTriggerLevelFine(); + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerPositiveEdge").arg(i))) { + m_triggersData.back().m_triggerPositiveEdge = triggerData->getTriggerPositiveEdge() != 0; + } + if (keys.contains(QString("scopeConfig.triggersData[%1].triggerRepeat").arg(i))) { + m_triggersData.back().m_triggerRepeat = triggerData->getTriggerRepeat() != 0; + } + } + } + } +} + +GLScopeSettings& GLScopeSettings::operator=(const GLScopeSettings& t) +{ + // Check for self assignment + if (this != &t) + { + m_tracesData = t.m_tracesData; + m_triggersData = t.m_triggersData; + m_displayMode = t.m_displayMode; + m_traceIntensity = t.m_traceIntensity; + m_gridIntensity = t.m_gridIntensity; + m_time = t.m_time; + m_timeOfs = t.m_timeOfs; + m_traceLenMult = t.m_traceLenMult; + m_trigPre = t.m_trigPre; + } + + return *this; +} + +int GLScopeSettings::qColorToInt(const QColor& color) +{ + return 256*256*color.blue() + 256*color.green() + color.red(); +} + +QColor GLScopeSettings::intToQColor(int intColor) +{ + int r = intColor % 256; + int bg = intColor / 256; + int g = bg % 256; + int b = bg / 256; + return QColor(r, g, b); +} diff --git a/android/app/src/main/cpp/dsp/glscopesettings.h b/android/app/src/main/cpp/dsp/glscopesettings.h new file mode 100644 index 0000000..fc38038 --- /dev/null +++ b/android/app/src/main/cpp/dsp/glscopesettings.h @@ -0,0 +1,191 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019, 2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_GLSCOPESETTINGS_H +#define SDRBASE_DSP_GLSCOPESETTINGS_H + +#include + +#include +#include + +#include "export.h" +#include "dsp/dsptypes.h" +#include "dsp/projector.h" +#include "settings/serializable.h" + +class SDRBASE_API GLScopeSettings : public Serializable +{ +public: + enum DisplayMode // TODO: copy of GLScope::DisplayMode => unify + { + DisplayXYH, + DisplayXYV, + DisplayX, + DisplayY, + DisplayPol + }; + + struct TraceData // TODO: copy of ScopeVis::TraceData => unify + { + uint32_t m_streamIndex; //!< I/Q stream index + Projector::ProjectionType m_projectionType; //!< Complex to real projection type + float m_amp; //!< Amplification factor + float m_ofs; //!< Offset factor + int m_traceDelay; //!< Trace delay in number of samples + int m_traceDelayCoarse; //!< Coarse delay slider value + int m_traceDelayFine; //!< Fine delay slider value + float m_triggerDisplayLevel; //!< Displayable trigger display level in -1:+1 scale. Off scale if not displayable. + QColor m_traceColor; //!< Trace display color + float m_traceColorR; //!< Trace display color - red shortcut + float m_traceColorG; //!< Trace display color - green shortcut + float m_traceColorB; //!< Trace display color - blue shortcut + bool m_hasTextOverlay; //!< True if a text overlay has to be displayed + QString m_textOverlay; //!< Text overlay to display + bool m_viewTrace; //!< Trace visibility + + TraceData() + { + resetToDefaults(); + } + + void setColor(QColor color) + { + m_traceColor = color; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + float r,g,b,a; +#else + qreal r,g,b,a; +#endif + m_traceColor.getRgbF(&r, &g, &b, &a); + m_traceColorR = r; + m_traceColorG = g; + m_traceColorB = b; + } + + void resetToDefaults() + { + m_streamIndex = 0; + m_projectionType = Projector::ProjectionReal; + m_amp = 1.0f; + m_ofs = 0.0f; + m_traceDelay = 0; + m_traceDelayCoarse = 0; + m_traceDelayFine = 0; + m_triggerDisplayLevel = 2.0; // Over scale by default (2.0) + m_traceColor = QColor(255,255,64); + m_hasTextOverlay = false; + m_viewTrace = true; + setColor(m_traceColor); + } + }; + + struct TriggerData // TODO: copy of ScopeVis::TriggerData => unify + { + uint32_t m_streamIndex; //!< I/Q stream index + Projector::ProjectionType m_projectionType; //!< Complex to real projection type + uint32_t m_inputIndex; //!< Input or feed index this trigger is associated with + Real m_triggerLevel; //!< Level in real units + int m_triggerLevelCoarse; + int m_triggerLevelFine; + bool m_triggerPositiveEdge; //!< Trigger on the positive edge (else negative) + bool m_triggerBothEdges; //!< Trigger on both edges (else only one) + uint32_t m_triggerHoldoff; //!< Trigger holdoff in number of samples + uint32_t m_triggerDelay; //!< Delay before the trigger is kicked off in number of samples (trigger delay) + double m_triggerDelayMult; //!< Trigger delay as a multiplier of trace length + int m_triggerDelayCoarse; + int m_triggerDelayFine; + uint32_t m_triggerRepeat; //!< Number of trigger conditions before the final decisive trigger + QColor m_triggerColor; //!< Trigger line display color + float m_triggerColorR; //!< Trigger line display color - red shortcut + float m_triggerColorG; //!< Trigger line display color - green shortcut + float m_triggerColorB; //!< Trigger line display color - blue shortcut + + TriggerData() + { + resetToDefaults(); + } + + void setColor(QColor color) + { + m_triggerColor = color; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + float r,g,b,a; +#else + qreal r,g,b,a; +#endif + m_triggerColor.getRgbF(&r, &g, &b, &a); + m_triggerColorR = r; + m_triggerColorG = g; + m_triggerColorB = b; + } + + void resetToDefaults() + { + m_streamIndex = 0; + m_projectionType = Projector::ProjectionReal; + m_inputIndex = 0; + m_triggerLevel = 0.0f; + m_triggerLevelCoarse = 0; + m_triggerLevelFine = 0; + m_triggerPositiveEdge = true; + m_triggerBothEdges = false; + m_triggerHoldoff = 1; + m_triggerDelay = 0; + m_triggerDelayMult = 0.0; + m_triggerDelayCoarse = 0; + m_triggerDelayFine = 0; + m_triggerRepeat = 0; + m_triggerColor = QColor(0,255,0); + setColor(m_triggerColor); + } + }; + + DisplayMode m_displayMode; + int m_traceIntensity; + int m_gridIntensity; + int m_time; + int m_timeOfs; + int m_traceLenMult; + int m_trigPre; + bool m_freerun; + std::vector m_tracesData; + std::vector m_triggersData; + static const double AMPS[27]; + static const uint32_t m_traceChunkDefaultSize = 4800; + static const uint32_t m_maxNbTriggers = 10; + static const uint32_t m_maxNbTraces = 10; + static const uint32_t m_nbTraceMemories = 50; + static const uint32_t m_nbTraceBuffers = 2; + + GLScopeSettings(); + GLScopeSettings(const GLScopeSettings& t); + virtual ~GLScopeSettings(); + + void resetToDefaults(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const; + virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject); + GLScopeSettings& operator=(const GLScopeSettings& t); + static int qColorToInt(const QColor& color); + static QColor intToQColor(int intColor); +}; + +#endif // SDRBASE_DSP_GLSCOPESETTINGS_H diff --git a/android/app/src/main/cpp/dsp/glspectruminterface.h b/android/app/src/main/cpp/dsp/glspectruminterface.h new file mode 100644 index 0000000..1644ef9 --- /dev/null +++ b/android/app/src/main/cpp/dsp/glspectruminterface.h @@ -0,0 +1,39 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_GLSPECTRUMINTERFACE_H_ +#define SDRBASE_DSP_GLSPECTRUMINTERFACE_H_ + +#include +#include "dsp/dsptypes.h" + +class GLSpectrumInterface +{ +public: + GLSpectrumInterface() {} + virtual ~GLSpectrumInterface() {} + virtual void newSpectrum(const Real* spectrum, int nbBins, int fftSize) + { + (void) spectrum; + (void) nbBins; + (void) fftSize; + } +}; + +#endif // SDRBASE_DSP_GLSPECTRUMINTERFACE_H_ diff --git a/android/app/src/main/cpp/dsp/goertzel.cpp b/android/app/src/main/cpp/dsp/goertzel.cpp new file mode 100644 index 0000000..468749d --- /dev/null +++ b/android/app/src/main/cpp/dsp/goertzel.cpp @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "goertzel.h" + +Goertzel::Goertzel(double frequency, int sampleRate) : + m_s0(0.0), + m_s1(0.0), + m_s2(0.0), + m_z(0.0), + m_sampleCount(0) +{ + m_a = 2.0 * M_PI * frequency / sampleRate; + m_b = 2.0 * cos(m_a); + m_c = std::complex(cos(-m_a), sin(-m_a)); +} + +void Goertzel::reset() +{ + m_s0 = 0.0; + m_s1 = 0.0; + m_s2 = 0.0; + m_z = 0.0; + m_sampleCount = 0; +} + +void Goertzel::filter(double sample) +{ + m_s0 = m_b * m_s1 - m_s2 + sample; + m_s2 = m_s1; + m_s1 = m_s0; + m_sampleCount++; +} + +std::complex Goertzel::goertzel(double lastSample) +{ + m_s0 = m_b * m_s1 - m_s2 + lastSample; + m_sampleCount++; + + std::complex y = m_s0 - m_s1 * m_c; + double x = -m_a * (m_sampleCount - 1.0); + std::complex d(cos(x), sin(x)); + + double scale = m_sampleCount / 2.0; + m_z = y * d / scale; + return m_z; +} + +double Goertzel::mag() +{ + return std::abs(m_z); +} + +double Goertzel::phase() +{ + return std::arg(m_z); +} diff --git a/android/app/src/main/cpp/dsp/goertzel.h b/android/app/src/main/cpp/dsp/goertzel.h new file mode 100644 index 0000000..1f6f85b --- /dev/null +++ b/android/app/src/main/cpp/dsp/goertzel.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GOERTZEL_H +#define INCLUDE_GOERTZEL_H + +#include + +#include "export.h" + +// Goertzel filter for calculating discrete Fourier transform for a single frequency +// Implementation supports non-integer multiples of fundamental frequency, see: +// https://asp-eurasipjournals.springeropen.com/track/pdf/10.1186/1687-6180-2012-56.pdf +class SDRBASE_API Goertzel +{ +public: + Goertzel(double frequency, int sampleRate); + void reset(); + void filter(double sample); + std::complex goertzel(double lastSample); + double mag(); + double phase(); + int size() { return m_sampleCount; } + +private: + double m_s0, m_s1, m_s2; + double m_a, m_b; + std::complex m_c, m_z; + int m_sampleCount; +}; + +#endif // INCLUDE_GOERTZEL_H diff --git a/android/app/src/main/cpp/dsp/hbfilterchainconverter.cpp b/android/app/src/main/cpp/dsp/hbfilterchainconverter.cpp new file mode 100644 index 0000000..143125c --- /dev/null +++ b/android/app/src/main/cpp/dsp/hbfilterchainconverter.cpp @@ -0,0 +1,151 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "hbfilterchainconverter.h" + +double HBFilterChainConverter::convertToIndexes(unsigned int log2, unsigned int chainHash, std::vector& chainIndexes) +{ + chainIndexes.clear(); + + if (log2 == 0) { + return 0.0; + } + + unsigned int s = 1; + unsigned int u = chainHash; + + for (unsigned int i = 0; i < log2; i++) { + s *= 3; + } + + u %= s; // scale + unsigned int ix = log2; + double shift = 0.0; + double shift_stage = 1.0 / (1<<(log2+1)); + + // base3 conversion + do + { + int r = u % 3; + chainIndexes.push_back(r); + shift += (r-1)*shift_stage; + shift_stage *= 2; + u /= 3; + ix--; + } while (u); + + // continue shift with leading zeroes. ix has the number of leading zeroes. + for (unsigned int i = 0; i < ix; i++) + { + chainIndexes.push_back(0); + shift -= shift_stage; + shift_stage *= 2; + } + + return shift; +} + +double HBFilterChainConverter::convertToString(unsigned int log2, unsigned int chainHash, QString& chainString) +{ + if (log2 == 0) + { + chainString = "C"; + return 0.0; + } + + unsigned int s = 1; + unsigned int u = chainHash; + chainString = ""; + + for (unsigned int i = 0; i < log2; i++) { + s *= 3; + } + + u %= s; // scale + unsigned int ix = log2; + double shift = 0.0; + double shift_stage = 1.0 / (1<<(log2+1)); + + // base3 conversion + do + { + int r = u % 3; + + if (r == 0) { + chainString = "L" + chainString; + } else if (r == 1) { + chainString = "C" + chainString; + } else if (r == 2) { + chainString = "H" + chainString; + } + + shift += (r-1)*shift_stage; + shift_stage *= 2; + u /= 3; + ix--; + } while (u); + + // continue shift with leading zeroes. ix has the number of leading zeroes. + for (unsigned int i = 0; i < ix; i++) + { + chainString = "L" + chainString; + shift -= shift_stage; + shift_stage *= 2; + } + + return shift; +} + +double HBFilterChainConverter::getShiftFactor(unsigned int log2, unsigned int chainHash) +{ + if (log2 == 0) + { + return 0.0; + } + + unsigned int s = 1; + unsigned int u = chainHash; + + for (unsigned int i = 0; i < log2; i++) { + s *= 3; + } + + u %= s; // scale + unsigned int ix = log2; + double shift = 0.0; + double shift_stage = 1.0 / (1<<(log2+1)); + + // base3 conversion + do + { + int r = u % 3; + shift += (r-1)*shift_stage; + shift_stage *= 2; + u /= 3; + ix--; + } while (u); + + // continue shift with leading zeroes. ix has the number of leading zeroes. + for (unsigned int i = 0; i < ix; i++) + { + shift -= shift_stage; + shift_stage *= 2; + } + + return shift; +} diff --git a/android/app/src/main/cpp/dsp/hbfilterchainconverter.h b/android/app/src/main/cpp/dsp/hbfilterchainconverter.h new file mode 100644 index 0000000..c6883dc --- /dev/null +++ b/android/app/src/main/cpp/dsp/hbfilterchainconverter.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_HBFILTERCHAINCONVERTER_H +#define SDRBASE_DSP_HBFILTERCHAINCONVERTER_H + +#include +#include "export.h" + +class QString; + +class SDRBASE_API HBFilterChainConverter +{ +public: + // Converts the chain hash as a base3 number each digit representing a filter stage from lower (LSD) to upper level (MSD) + // The corresponding log2 of decimation or interpolation factor is also the number of filter stages + // A vector of indexes as base3 digits is filled in (0: low band, 1: center band, : high band) + // The shift factor of center frequency is returned. The actual shift is obtained by multiplying this factor by the sample rate. + static double convertToIndexes(unsigned int log2, unsigned int chainHash, std::vector& chainIndexes); + // Same but used only for display giving a string representation of the filter chain + static double convertToString(unsigned int log2, unsigned int chainHash, QString& chainString); + // Just calculate the frequency shift factor relative to sample rate + static double getShiftFactor(unsigned int log2, unsigned int chainHash); +}; + +#endif // SDRBASE_DSP_HBFILTERCHAINCONVERTER_H diff --git a/android/app/src/main/cpp/dsp/hbfiltertraits.cpp b/android/app/src/main/cpp/dsp/hbfiltertraits.cpp new file mode 100644 index 0000000..3fdbd40 --- /dev/null +++ b/android/app/src/main/cpp/dsp/hbfiltertraits.cpp @@ -0,0 +1,466 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include "dsp/hbfiltertraits.h" + +const int16_t HBFIRFilterTraits<16>::hbMod[16+6] = { + 15,16,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,0,1,2 +}; + +const int32_t HBFIRFilterTraits<16>::hbCoeffs[4] = { + //* Firwin as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0052391810630145274965685509016566356877 * (1 << hbShift)), // /190 => hbShift = 8 + (int32_t)(0.0232111017863650750947535073009930783883 * (1 << hbShift)), + (int32_t)(-0.0761058457486735451258397233686991967261 * (1 << hbShift)), + (int32_t)(0.3076987787367443383246268240327481180429 * (1 << hbShift)), +}; + +const double HBFIRFilterTraits<16>::hbCoeffsF[4] = { + -0.0052391810630145274965685509016566356877, + 0.0232111017863650750947535073009930783883, + -0.0761058457486735451258397233686991967261, + 0.3076987787367443383246268240327481180429, +}; + +const int16_t HBFIRFilterTraits<32>::hbMod[32+6] = { + 31,32,0,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,0,1,2 +}; + + +const int32_t HBFIRFilterTraits<32>::hbCoeffs[8] = { + //* Firwin as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0018878783958199373254477348993418672762 * (1 << hbShift)), // ~1/529 => hbShift = 10 + (int32_t)( 0.0038624783041994003966734805288751886110 * (1 << hbShift)), + (int32_t)(-0.0082424665965482504098593707908548822161 * (1 << hbShift)), + (int32_t)( 0.0159471139705940345709933581019868142903 * (1 << hbShift)), + (int32_t)(-0.0286765592339759019246958615667608682998 * (1 << hbShift)), + (int32_t)( 0.0507185615622293764492845014046906726435 * (1 << hbShift)), + (int32_t)(-0.0980159074728618323613105189906491432339 * (1 << hbShift)), + (int32_t)( 0.3159417644358786247948955860920250415802 * (1 << hbShift)), + +// (qint32)(-0.015956912844043127236437484839370881673 * (1 << hbShift)), +// (qint32)( 0.013023031678944928940522274274371739011 * (1 << hbShift)), +// (qint32)(-0.01866942273717486777684371190844103694 * (1 << hbShift)), +// (qint32)( 0.026550887571157304190005987720724078827 * (1 << hbShift)), +// (qint32)(-0.038350314277854319344740474662103224546 * (1 << hbShift)), +// (qint32)( 0.058429248652825838128421764849917963147 * (1 << hbShift)), +// (qint32)(-0.102889802028955756885153505209018476307 * (1 << hbShift)), +// (qint32)( 0.317237706405931241260276465254719369113 * (1 << hbShift)) +}; + +const double HBFIRFilterTraits<32>::hbCoeffsF[8] = { + -0.0018878783958199373254477348993418672762, + 0.0038624783041994003966734805288751886110, + -0.0082424665965482504098593707908548822161, + 0.0159471139705940345709933581019868142903, + -0.0286765592339759019246958615667608682998, + 0.0507185615622293764492845014046906726435, + -0.0980159074728618323613105189906491432339, + 0.3159417644358786247948955860920250415802, +}; + +const int16_t HBFIRFilterTraits<48>::hbMod[48+6] = { + 47,48,0,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,0,1,2 +}; + + +const int32_t HBFIRFilterTraits<48>::hbCoeffs[12] = { + //* Firwin as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0011627994808655962074434020436797254661 * (1 << hbShift)), // ~1/859 => hbShift = 10 + (int32_t)( 0.0017451165792459334517860991553561689216 * (1 << hbShift)), + (int32_t)(-0.0029357205890606303047563052643909031758 * (1 << hbShift)), + (int32_t)( 0.0048726090910227891003780875678330630763 * (1 << hbShift)), + (int32_t)(-0.0077313759655872928144848543752232217230 * (1 << hbShift)), + (int32_t)( 0.0117637971494846688830238079503942572046 * (1 << hbShift)), + (int32_t)(-0.0173810771817523163074170611253066454083 * (1 << hbShift)), + (int32_t)( 0.0253500636065296450216699497559602605179 * (1 << hbShift)), + (int32_t)(-0.0373266939135983855102551842719549313188 * (1 << hbShift)), + (int32_t)( 0.0576685041500848358242414803953579394147 * (1 << hbShift)), + (int32_t)(-0.1024912545928038654086122960507054813206 * (1 << hbShift)), + (int32_t)( 0.3173768238826674692454332671331940218806 * (1 << hbShift)), + +// (qint32)(-0.004102576237611492253332112767338912818 * (1 << hbShift)), +// (qint32)( 0.003950551047979387886410762575906119309 * (1 << hbShift)), +// (qint32)(-0.005807875789391703583164350277456833282 * (1 << hbShift)), +// (qint32)( 0.00823497890520805998770814682075069868 * (1 << hbShift)), +// (qint32)(-0.011372226513199541059195851744334504474 * (1 << hbShift)), +// (qint32)( 0.015471557140973646315984524335362948477 * (1 << hbShift)), +// (qint32)(-0.020944996398689276484450516591095947661 * (1 << hbShift)), +// (qint32)( 0.028568078132034283034279553703527199104 * (1 << hbShift)), +// (qint32)(-0.040015143905614086738964374490024056286 * (1 << hbShift)), +// (qint32)( 0.059669519431831075095828964549582451582 * (1 << hbShift)), +// (qint32)(-0.103669138691865420076609893840213771909 * (1 << hbShift)), +// (qint32)( 0.317491986549921390015072120149852707982 * (1 << hbShift)) +}; + +const double HBFIRFilterTraits<48>::hbCoeffsF[12] = { + -0.0011627994808655962074434020436797254661, + 0.0017451165792459334517860991553561689216, + -0.0029357205890606303047563052643909031758, + 0.0048726090910227891003780875678330630763, + -0.0077313759655872928144848543752232217230, + 0.0117637971494846688830238079503942572046, + -0.0173810771817523163074170611253066454083, + 0.0253500636065296450216699497559602605179, + -0.0373266939135983855102551842719549313188, + 0.0576685041500848358242414803953579394147, + -0.1024912545928038654086122960507054813206, + 0.3173768238826674692454332671331940218806, +}; + +const int16_t HBFIRFilterTraits<64>::hbMod[64+6] = { + 63,64,0,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,0,1,2 +}; + + +const int32_t HBFIRFilterTraits<64>::hbCoeffs[16] = { + //* Remez as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0004653050334792540416659067936677729449 * (1 << hbShift)), // ~1/2149 => hbShift = 12 + (int32_t)( 0.0007120490624526883919470643391491648799 * (1 << hbShift)), + (int32_t)(-0.0012303473710125558716887983479182366864 * (1 << hbShift)), + (int32_t)( 0.0019716520179919017584369012041634050547 * (1 << hbShift)), + (int32_t)(-0.0029947484165425580261710170049127555103 * (1 << hbShift)), + (int32_t)( 0.0043703902150498061263128590780979720876 * (1 << hbShift)), + (int32_t)(-0.0061858352927315653213558022116558277048 * (1 << hbShift)), + (int32_t)( 0.0085554408639278121950777489246320328675 * (1 << hbShift)), + (int32_t)(-0.0116397924445187355563247066925214312505 * (1 << hbShift)), + (int32_t)( 0.0156852221106748394852115069397768820636 * (1 << hbShift)), + (int32_t)(-0.0211070832238078286147153761476147337817 * (1 << hbShift)), + (int32_t)( 0.0286850846890029896607554604770484729670 * (1 << hbShift)), + (int32_t)(-0.0400956173930921908055147184768429724500 * (1 << hbShift)), + (int32_t)( 0.0597215923200692666572564348825835622847 * (1 << hbShift)), + (int32_t)(-0.1036982054813635201195864965484361164272 * (1 << hbShift)), + (int32_t)( 0.3175014394028848885298543791577685624361 * (1 << hbShift)), + +// (qint32)(-0.001114417441601693505720538368564120901 * (1 << hbShift)), +// (qint32)( 0.001268007827185253051302527005361753254 * (1 << hbShift)), +// (qint32)(-0.001959831378850490895410230152151598304 * (1 << hbShift)), +// (qint32)( 0.002878308307661380308073439948657323839 * (1 << hbShift)), +// (qint32)(-0.004071361818258721100571850826099762344 * (1 << hbShift)), +// (qint32)( 0.005597288494657440618973431867289036745 * (1 << hbShift)), +// (qint32)(-0.007532345003308904551886371336877346039 * (1 << hbShift)), +// (qint32)( 0.009980346844667375288961963519795972388 * (1 << hbShift)), +// (qint32)(-0.013092614174300500062830820979797863401 * (1 << hbShift)), +// (qint32)( 0.01710934914871829748417297878404497169 * (1 << hbShift)), +// (qint32)(-0.022443558692997273018576720460259821266 * (1 << hbShift)), +// (qint32)( 0.029875811511593811098386197500076377764 * (1 << hbShift)), +// (qint32)(-0.041086352085710403647667021687084343284 * (1 << hbShift)), +// (qint32)( 0.060465467462665789533104998554335907102 * (1 << hbShift)), +// (qint32)(-0.104159517495977321788203084906854201108 * (1 << hbShift)), +// (qint32)( 0.317657589850154464805598308885237202048 * (1 << hbShift)), +}; + +const float HBFIRFilterTraits<64>::hbCoeffsF[16] = { + -0.0004653050334792540416659067936677729449, + 0.0007120490624526883919470643391491648799, + -0.0012303473710125558716887983479182366864, + 0.0019716520179919017584369012041634050547, + -0.0029947484165425580261710170049127555103, + 0.0043703902150498061263128590780979720876, + -0.0061858352927315653213558022116558277048, + 0.0085554408639278121950777489246320328675, + -0.0116397924445187355563247066925214312505, + 0.0156852221106748394852115069397768820636, + -0.0211070832238078286147153761476147337817, + 0.0286850846890029896607554604770484729670, + -0.0400956173930921908055147184768429724500, + 0.0597215923200692666572564348825835622847, + -0.1036982054813635201195864965484361164272, + 0.3175014394028848885298543791577685624361, +}; + +const int16_t HBFIRFilterTraits<80>::hbMod[80+6] = { + 79,80,0,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,0,1,2 +}; + + +const int32_t HBFIRFilterTraits<80>::hbCoeffs[20] = { + //* Remez as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0001054430663706784843331246137587697831 * (1 << hbShift)), // ~1/9483 => hbShift = 14 + (int32_t)( 0.0001895717826405601933066613629108587702 * (1 << hbShift)), + (int32_t)(-0.0003519516996893227891822497621632237497 * (1 << hbShift)), + (int32_t)( 0.0005975111594421821190753485453228677216 * (1 << hbShift)), + (int32_t)(-0.0009524124279789792160699768430731637636 * (1 << hbShift)), + (int32_t)( 0.0014474605824692796454677967687985074008 * (1 << hbShift)), + (int32_t)(-0.0021186428821101787461911314380813564640 * (1 << hbShift)), + (int32_t)( 0.0030082068742630901220236339099756150972 * (1 << hbShift)), + (int32_t)(-0.0041664004891296358909502650647027621744 * (1 << hbShift)), + (int32_t)( 0.0056547140936428538088298623165428580251 * (1 << hbShift)), + (int32_t)(-0.0075518323360079901707120342280177283101 * (1 << hbShift)), + (int32_t)( 0.0099644038858163180155669280679830990266 * (1 << hbShift)), + (int32_t)(-0.0130470841719700410971105597468522319105 * (1 << hbShift)), + (int32_t)( 0.0170422818715445859028001507340377429500 * (1 << hbShift)), + (int32_t)(-0.0223637819225956900603957677731159492396 * (1 << hbShift)), + (int32_t)( 0.0297925991327811050257690084208661573939 * (1 << hbShift)), + (int32_t)(-0.0410092859102263174175817539435229264200 * (1 << hbShift)), + (int32_t)( 0.0604034694948822267757115866970707429573 * (1 << hbShift)), + (int32_t)(-0.1041194584045879306666293473426776472479 * (1 << hbShift)), + (int32_t)( 0.3176437752925042046214798574510496109724 * (1 << hbShift)), +}; + +const double HBFIRFilterTraits<80>::hbCoeffsF[20] = { + -0.0001054430663706784843331246137587697831, // ~1/9483 => hbShift = 14 + 0.0001895717826405601933066613629108587702, + -0.0003519516996893227891822497621632237497, + 0.0005975111594421821190753485453228677216, + -0.0009524124279789792160699768430731637636, + 0.0014474605824692796454677967687985074008, + -0.0021186428821101787461911314380813564640, + 0.0030082068742630901220236339099756150972, + -0.0041664004891296358909502650647027621744, + 0.0056547140936428538088298623165428580251, + -0.0075518323360079901707120342280177283101, + 0.0099644038858163180155669280679830990266, + -0.0130470841719700410971105597468522319105, + 0.0170422818715445859028001507340377429500, + -0.0223637819225956900603957677731159492396, + 0.0297925991327811050257690084208661573939, + -0.0410092859102263174175817539435229264200, + 0.0604034694948822267757115866970707429573, + -0.1041194584045879306666293473426776472479, + 0.3176437752925042046214798574510496109724, +}; + +const int16_t HBFIRFilterTraits<96>::hbMod[96+6] = { + 95,96,0,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,0,1,2 +}; + + +const int32_t HBFIRFilterTraits<96>::hbCoeffs[24] = { + //* Remez as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0000243052463317893968695708462046667364 * (1 << hbShift)), // 1/41143 => hbShift = 16 + (int32_t)( 0.0000503567741519847557611806732058568059 * (1 << hbShift)), + (int32_t)(-0.0001002354600628052128195172310043403741 * (1 << hbShift)), + (int32_t)( 0.0001801275832684542921834081052878673290 * (1 << hbShift)), + (int32_t)(-0.0003014864432246496970743687704441526876 * (1 << hbShift)), + (int32_t)( 0.0004783148860127731604417744559754055445 * (1 << hbShift)), + (int32_t)(-0.0007274200147704492930983422027679807798 * (1 << hbShift)), + (int32_t)( 0.0010686503612886001472748187524075547117 * (1 << hbShift)), + (int32_t)(-0.0015251456116906108098629779590282851132 * (1 << hbShift)), + (int32_t)( 0.0021238131085570461677181075543785482296 * (1 << hbShift)), + (int32_t)(-0.0028960654265650425873146467381502588978 * (1 << hbShift)), + (int32_t)( 0.0038789688077727475616629515542399531114 * (1 << hbShift)), + (int32_t)(-0.0051173875903961539915454359572777320864 * (1 << hbShift)), + (int32_t)( 0.0066675444490017317031305132957186287967 * (1 << hbShift)), + (int32_t)(-0.0086031967328669932404405784609480178915 * (1 << hbShift)), + (int32_t)( 0.0110268456349653827530676863943881471641 * (1 << hbShift)), + (int32_t)(-0.0140900919878225727721599014330422505736 * (1 << hbShift)), + (int32_t)( 0.0180336055419063577553995258995200856589 * (1 << hbShift)), + (int32_t)(-0.0232708957455770061584221508610426099040 * (1 << hbShift)), + (int32_t)( 0.0305843805330435619671547442521841730922 * (1 << hbShift)), + (int32_t)(-0.0416576245224431485070226699463091790676 * (1 << hbShift)), + (int32_t)( 0.0608846679850302968661779345893592108041 * (1 << hbShift)), + (int32_t)(-0.1044156487571061137087369274922821205109 * (1 << hbShift)), + (int32_t)( 0.3177437550265513332981015537370694801211 * (1 << hbShift)), +}; + +const double HBFIRFilterTraits<96>::hbCoeffsF[24] = { + -0.0000243052463317893968695708462046667364, + 0.0000503567741519847557611806732058568059, + -0.0001002354600628052128195172310043403741, + 0.0001801275832684542921834081052878673290, + -0.0003014864432246496970743687704441526876, + 0.0004783148860127731604417744559754055445, + -0.0007274200147704492930983422027679807798, + 0.0010686503612886001472748187524075547117, + -0.0015251456116906108098629779590282851132, + 0.0021238131085570461677181075543785482296, + -0.0028960654265650425873146467381502588978, + 0.0038789688077727475616629515542399531114, + -0.0051173875903961539915454359572777320864, + 0.0066675444490017317031305132957186287967, + -0.0086031967328669932404405784609480178915, + 0.0110268456349653827530676863943881471641, + -0.0140900919878225727721599014330422505736, + 0.0180336055419063577553995258995200856589, + -0.0232708957455770061584221508610426099040, + 0.0305843805330435619671547442521841730922, + -0.0416576245224431485070226699463091790676, + 0.0608846679850302968661779345893592108041, + -0.1044156487571061137087369274922821205109, + 0.3177437550265513332981015537370694801211, +}; + +const int16_t HBFIRFilterTraits<112>::hbMod[112+6] = { + 111,112,0,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,0,1,2 +}; + + +const int32_t HBFIRFilterTraits<112>::hbCoeffs[28] = { + //* Remez as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0000057182612173497993884982611156875265 * (1 << hbShift)), // ~1/174878 => hbShift = 18 + (int32_t)( 0.0000134089330475142792093150995169636985 * (1 << hbShift)), + (int32_t)(-0.0000285019056923813186876261183133607346 * (1 << hbShift)), + (int32_t)( 0.0000540489980941085909355720007241075109 * (1 << hbShift)), + (int32_t)(-0.0000947833751586818473982426480617391462 * (1 << hbShift)), + (int32_t)( 0.0001567867952902222809536542103003853299 * (1 << hbShift)), + (int32_t)(-0.0002476681450895876299839182266282477940 * (1 << hbShift)), + (int32_t)( 0.0003767405865262860096935593201550318554 * (1 << hbShift)), + (int32_t)(-0.0005551920408570068304551781146471967077 * (1 << hbShift)), + (int32_t)( 0.0007962529081739059839267769191906154447 * (1 << hbShift)), + (int32_t)(-0.0011153731476064448775026294313761354715 * (1 << hbShift)), + (int32_t)( 0.0015304714714705458088878087252737714152 * (1 << hbShift)), + (int32_t)(-0.0020622467141092528514212389723070373293 * (1 << hbShift)), + (int32_t)( 0.0027347048425079849461039049884902851772 * (1 << hbShift)), + (int32_t)(-0.0035759786171320282616159502708796935622 * (1 << hbShift)), + (int32_t)( 0.0046196952795832949348331375460929848487 * (1 << hbShift)), + (int32_t)(-0.0059072308383088713984454543037827534135 * (1 << hbShift)), + (int32_t)( 0.0074914857942512255370437479484735376900 * (1 << hbShift)), + (int32_t)(-0.0094433233524672671732602680094714742154 * (1 << hbShift)), + (int32_t)( 0.0118628557280401076418074879370578855742 * (1 << hbShift)), + (int32_t)(-0.0149000865720731560937206694461565348320 * (1 << hbShift)), + (int32_t)( 0.0187949228094094408780811988890491193160 * (1 << hbShift)), + (int32_t)(-0.0239611153097040799342387629167205886915 * (1 << hbShift)), + (int32_t)( 0.0311823240686547251132587632582726655528 * (1 << hbShift)), + (int32_t)(-0.0421443341867254184229807378869736567140 * (1 << hbShift)), + (int32_t)( 0.0612443342926724393349147135268140118569 * (1 << hbShift)), + (int32_t)(-0.1046363792986205604185201423206308390945 * (1 << hbShift)), + (int32_t)( 0.3178181645034457436516106554336147382855 * (1 << hbShift)), +}; + +const double HBFIRFilterTraits<112>::hbCoeffsF[28] = { + -0.0000057182612173497993884982611156875265, + 0.0000134089330475142792093150995169636985, + -0.0000285019056923813186876261183133607346, + 0.0000540489980941085909355720007241075109, + -0.0000947833751586818473982426480617391462, + 0.0001567867952902222809536542103003853299, + -0.0002476681450895876299839182266282477940, + 0.0003767405865262860096935593201550318554, + -0.0005551920408570068304551781146471967077, + 0.0007962529081739059839267769191906154447, + -0.0011153731476064448775026294313761354715, + 0.0015304714714705458088878087252737714152, + -0.0020622467141092528514212389723070373293, + 0.0027347048425079849461039049884902851772, + -0.0035759786171320282616159502708796935622, + 0.0046196952795832949348331375460929848487, + -0.0059072308383088713984454543037827534135, + 0.0074914857942512255370437479484735376900, + -0.0094433233524672671732602680094714742154, + 0.0118628557280401076418074879370578855742, + -0.0149000865720731560937206694461565348320, + 0.0187949228094094408780811988890491193160, + -0.0239611153097040799342387629167205886915, + 0.0311823240686547251132587632582726655528, + -0.0421443341867254184229807378869736567140, + 0.0612443342926724393349147135268140118569, + -0.1046363792986205604185201423206308390945, + 0.3178181645034457436516106554336147382855, +}; + +const int16_t HBFIRFilterTraits<128>::hbMod[128+6] = { + 127,128,0,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,0,1,2 +}; + + +const int32_t HBFIRFilterTraits<128>::hbCoeffs[32] = { + //* Remez as in https://www.dsprelated.com/showcode/270.php */ + (int32_t)(-0.0000013530084481063586964138655693856705 * (1 << hbShift)), // ~1/739098 => hbShift = 20 + (int32_t)( 0.0000035468835939308468221931557040615957 * (1 << hbShift)), + (int32_t)(-0.0000080263259193852748679242486984364291 * (1 << hbShift)), + (int32_t)( 0.0000160249362715262112246382419922241525 * (1 << hbShift)), + (int32_t)(-0.0000293930772937943995460653712203935584 * (1 << hbShift)), + (int32_t)( 0.0000506245785103059592661099708177374623 * (1 << hbShift)), + (int32_t)(-0.0000829848508757281692112955928664064231 * (1 << hbShift)), + (int32_t)( 0.0001306421953216197048494584542766006052 * (1 << hbShift)), + (int32_t)(-0.0001988007175677746298826653603697423023 * (1 << hbShift)), + (int32_t)( 0.0002938302746977689504891129157471141298 * (1 << hbShift)), + (int32_t)(-0.0004233934809845499216279796073081342911 * (1 << hbShift)), + (int32_t)( 0.0005965709177428588560684885599982862914 * (1 << hbShift)), + (int32_t)(-0.0008239883872841064762701512869114139903 * (1 << hbShift)), + (int32_t)( 0.0011179618447797630680778935285957231827 * (1 << hbShift)), + (int32_t)(-0.0014926744261021872121897313689942166093 * (1 << hbShift)), + (int32_t)( 0.0019644262679035678388628216595179765136 * (1 << hbShift)), + (int32_t)(-0.0025520032927806384458191413244776413194 * (1 << hbShift)), + (int32_t)( 0.0032772538071471992750238744207536001340 * (1 << hbShift)), + (int32_t)(-0.0041660018952565701713663948169141804101 * (1 << hbShift)), + (int32_t)( 0.0052495120038521464814196271220225753495 * (1 << hbShift)), + (int32_t)(-0.0065668614662222155736737150277804175857 * (1 << hbShift)), + (int32_t)( 0.0081688408784787811672822854802689107601 * (1 << hbShift)), + (int32_t)(-0.0101245136956070701239607245724982931279 * (1 << hbShift)), + (int32_t)( 0.0125326183062810583845880785247572930530 * (1 << hbShift)), + (int32_t)(-0.0155423066893098579810406079104723175988 * (1 << hbShift)), + (int32_t)( 0.0193932322698959942741669948418348212726 * (1 << hbShift)), + (int32_t)(-0.0244995384963461564076236953724219347350 * (1 << hbShift)), + (int32_t)( 0.0316459368298866380864176051090908003971 * (1 << hbShift)), + (int32_t)(-0.0425198999626832385700936356442980468273 * (1 << hbShift)), + (int32_t)( 0.0615208850123762293615747864805598510429 * (1 << hbShift)), + (int32_t)(-0.1048057030317238041972061068918264936656 * (1 << hbShift)), + (int32_t)( 0.3178751792920155860855402352171950042248 * (1 << hbShift)), +}; + +const double HBFIRFilterTraits<128>::hbCoeffsF[32] = { + //* Remez as in https://www.dsprelated.com/showcode/270.php */ + -0.0000013530084481063586964138655693856705, // ~1/739098 => hbShift = 20 + 0.0000035468835939308468221931557040615957, + -0.0000080263259193852748679242486984364291, + 0.0000160249362715262112246382419922241525, + -0.0000293930772937943995460653712203935584, + 0.0000506245785103059592661099708177374623, + -0.0000829848508757281692112955928664064231, + 0.0001306421953216197048494584542766006052, + -0.0001988007175677746298826653603697423023, + 0.0002938302746977689504891129157471141298, + -0.0004233934809845499216279796073081342911, + 0.0005965709177428588560684885599982862914, + -0.0008239883872841064762701512869114139903, + 0.0011179618447797630680778935285957231827, + -0.0014926744261021872121897313689942166093, + 0.0019644262679035678388628216595179765136, + -0.0025520032927806384458191413244776413194, + 0.0032772538071471992750238744207536001340, + -0.0041660018952565701713663948169141804101, + 0.0052495120038521464814196271220225753495, + -0.0065668614662222155736737150277804175857, + 0.0081688408784787811672822854802689107601, + -0.0101245136956070701239607245724982931279, + 0.0125326183062810583845880785247572930530, + -0.0155423066893098579810406079104723175988, + 0.0193932322698959942741669948418348212726, + -0.0244995384963461564076236953724219347350, + 0.0316459368298866380864176051090908003971, + -0.0425198999626832385700936356442980468273, + 0.0615208850123762293615747864805598510429, + -0.1048057030317238041972061068918264936656, + 0.3178751792920155860855402352171950042248, +}; + + diff --git a/android/app/src/main/cpp/dsp/hbfiltertraits.h b/android/app/src/main/cpp/dsp/hbfiltertraits.h new file mode 100644 index 0000000..77819a9 --- /dev/null +++ b/android/app/src/main/cpp/dsp/hbfiltertraits.h @@ -0,0 +1,147 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_HBFILTERTRAITS_H_ +#define SDRBASE_DSP_HBFILTERTRAITS_H_ + +#include +#include "export.h" + +// uses Q1.14 format internally, input and output are S16 + +/* + * supported filter orders: 96, 80, 64, 48, 32 + * any usage of another value will be prevented by compilation errors + */ +template +struct HBFIRFilterTraits +{ +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<16> +{ + static const int32_t hbOrder = 16; + static const int32_t hbShift = 12; + static const int16_t hbMod[16+6]; +#ifdef _MSC_VER + __declspec(align(16)) static const int32_t hbCoeffs[4]; +#else + static const int32_t hbCoeffs[4] __attribute__ ((aligned (16))); +#endif + static const double hbCoeffsF[4]; +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<32> +{ + static const int32_t hbOrder = 32; + static const int32_t hbShift = 12; + static const int16_t hbMod[32+6]; +#ifdef _MSC_VER + __declspec(align(32)) static const int32_t hbCoeffs[8]; +#else + static const int32_t hbCoeffs[8] __attribute__ ((aligned (32))); +#endif + static const double hbCoeffsF[8]; +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<48> +{ + static const int32_t hbOrder = 48; + static const int32_t hbShift = 12; + static const int16_t hbMod[48+6]; +#ifdef _MSC_VER + __declspec(align(16)) static const int32_t hbCoeffs[12]; +#else + static const int32_t hbCoeffs[12] __attribute__ ((aligned (16))); +#endif + static const double hbCoeffsF[12]; +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<64> +{ + static const int32_t hbOrder = 64; + static const int32_t hbShift = 12; + static const int16_t hbMod[64+6]; +#ifdef _MSC_VER + __declspec(align(32)) static const int32_t hbCoeffs[16]; +#else + static const int32_t hbCoeffs[16] __attribute__ ((aligned (32))); +#endif + static const float hbCoeffsF[16]; +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<80> +{ + static const int32_t hbOrder = 80; + static const int32_t hbShift = 14; + static const int16_t hbMod[80+6]; +#ifdef _MSC_VER + __declspec(align(16)) static const int32_t hbCoeffs[20]; +#else + static const int32_t hbCoeffs[20] __attribute__ ((aligned (16))); +#endif + static const double hbCoeffsF[20]; +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<96> +{ + static const int32_t hbOrder = 96; + static const int32_t hbShift = 16; + static const int16_t hbMod[96+6]; +#ifdef _MSC_VER + __declspec(align(32)) static const int32_t hbCoeffs[24]; +#else + static const int32_t hbCoeffs[24] __attribute__ ((aligned (32))); +#endif + static const double hbCoeffsF[24]; +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<112> +{ + static const int32_t hbOrder = 112; + static const int32_t hbShift = 18; + static const int16_t hbMod[112+6]; +#ifdef _MSC_VER + __declspec(align(16)) static const int32_t hbCoeffs[28]; +#else + static const int32_t hbCoeffs[28] __attribute__ ((aligned (16))); +#endif + static const double hbCoeffsF[28]; +}; + +template<> +struct SDRBASE_API HBFIRFilterTraits<128> +{ + static const int32_t hbOrder = 128; + static const int32_t hbShift = 20; + static const int16_t hbMod[128+6]; +#ifdef _MSC_VER + __declspec(align(16)) static const int32_t hbCoeffs[32]; +#else + static const int32_t hbCoeffs[32] __attribute__ ((aligned (16))); +#endif + static const double hbCoeffsF[32]; +}; + +#endif /* SDRBASE_DSP_HBFILTERTRAITS_H_ */ diff --git a/android/app/src/main/cpp/dsp/iirfilter.h b/android/app/src/main/cpp/dsp/iirfilter.h new file mode 100644 index 0000000..8734a65 --- /dev/null +++ b/android/app/src/main/cpp/dsp/iirfilter.h @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017, 2019-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +/** + * IIR filter + * See: https://cdn.mikroe.com/ebooks/img/8/2016/02/digital-filter-design-chapter-03-image-2-9.gif + */ + +#ifndef SDRBASE_DSP_IIRFILTER_H_ +#define SDRBASE_DSP_IIRFILTER_H_ + +#include +#include + +template class IIRFilter +{ +public: + IIRFilter(const Type *a, const Type *b); + ~IIRFilter(); + void setCoeffs(const Type *a, const Type *b); + Type run(const Type& sample); + +private: + Type *m_a; + Type *m_b; + Type *m_x; + Type *m_y; +}; + +template class IIRFilter +{ +public: + IIRFilter(const Type *a, const Type *b); + ~IIRFilter(); + void setCoeffs(const Type *a, const Type *b); + Type run(const Type& sample); + +private: + Type m_a[3]; + Type m_b[3]; + Type m_x[2]; + Type m_y[2]; +}; + +template +IIRFilter::IIRFilter(const Type *a, const Type *b) +{ + m_a = new Type[Order+1]; + m_b = new Type[Order+1]; + m_x = new Type[Order]; + m_y = new Type[Order]; + + setCoeffs(a, b); +} + +template +IIRFilter::~IIRFilter() +{ + delete[] m_y; + delete[] m_x; + delete[] m_b; + delete[] m_a; +} + +template +void IIRFilter::setCoeffs(const Type *a, const Type *b) +{ + memcpy(m_a, b, (Order+1)*sizeof(Type)); + memcpy(m_b, a, (Order+1)*sizeof(Type)); + + for (uint32_t i = 0; i < Order; i++) + { + m_x[i] = 0; + m_y[i] = 0; + } +} + + +template +Type IIRFilter::run(const Type& sample) +{ + Type y = m_b[0] * sample; + + for (uint32_t i = Order; i > 0; i--) + { + y += m_b[i] * m_x[i-1] + m_a[i] * m_y[i-1]; + + if (i > 1) // shift + { + m_x[i-1] = m_x[i-2]; + m_y[i-1] = m_y[i-2]; + } + } + + // last shift + m_x[0] = sample; + m_y[0] = y; + + return y; +} + + +template +IIRFilter::IIRFilter(const Type *a, const Type *b) +{ + setCoeffs(a, b); +} + +template +IIRFilter::~IIRFilter() +{ +} + +template +void IIRFilter::setCoeffs(const Type *a, const Type *b) +{ + m_a[0] = a[0]; + m_a[1] = a[1]; + m_a[2] = a[2]; + m_b[0] = b[0]; + m_b[1] = b[1]; + m_b[2] = b[2]; + m_x[0] = 0; + m_x[1] = 0; + m_y[0] = 0; + m_y[1] = 0; +} + +template +Type IIRFilter::run(const Type& sample) +{ + Type y = m_b[0]*sample + m_b[1]*m_x[0] + m_b[2]*m_x[1] + m_a[1]*m_y[0] + m_a[2]*m_y[1]; // this is y[n] + + m_x[1] = m_x[0]; + m_x[0] = sample; + + m_y[1] = m_y[0]; + m_y[0] = y; + + return y; +} + +#endif /* SDRBASE_DSP_IIRFILTER_H_ */ diff --git a/android/app/src/main/cpp/dsp/interpolator.cpp b/android/app/src/main/cpp/dsp/interpolator.cpp new file mode 100644 index 0000000..4650713 --- /dev/null +++ b/android/app/src/main/cpp/dsp/interpolator.cpp @@ -0,0 +1,194 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2016-2018 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#include +#include +#include "dsp/interpolator.h" + + +void Interpolator::createPolyphaseLowPass( + std::vector& taps, + int phaseSteps, + double gain, + double sampleRateHz, + double cutoffFreqHz, + double transitionWidthHz, + double oobAttenuationdB) +{ + double nbTapsPerPhase = (oobAttenuationdB * sampleRateHz) / (22.0 * transitionWidthHz * phaseSteps); + return createPolyphaseLowPass(taps, phaseSteps, gain, sampleRateHz, cutoffFreqHz, nbTapsPerPhase); +} + + +void Interpolator::createPolyphaseLowPass( + std::vector& taps, + int phaseSteps, + double gain, + double sampleRateHz, + double cutoffFreqHz, + double nbTapsPerPhase) +{ + int ntaps = (int)(nbTapsPerPhase * phaseSteps); + qDebug("Interpolator::createPolyphaseLowPass: ntaps: %d", ntaps); + + if ((ntaps % 2) != 0) { + ntaps++; + } + + ntaps *= phaseSteps; + + taps.resize(ntaps); + std::vector window(ntaps); + + for (int n = 0; n < ntaps; n++) { + window[n] = 0.54 - 0.46 * cos ((2 * M_PI * n) / (ntaps - 1)); + } + + int M = (ntaps - 1) / 2; + double fwT0 = 2 * M_PI * cutoffFreqHz / sampleRateHz; + + for (int n = -M; n <= M; n++) + { + if (n == 0) { + taps[n + M] = fwT0 / M_PI * window[n + M]; + } else { + taps[n + M] = sin (n * fwT0) / (n * M_PI) * window[n + M]; + } + } + + double max = taps[0 + M]; + + for (int n = 1; n <= M; n++) { + max += 2.0 * taps[n + M]; + } + + gain /= max; + + for (int i = 0; i < ntaps; i++) { + taps[i] *= gain; + } +} + +Interpolator::Interpolator() : + m_taps(0), + m_alignedTaps(0), + m_taps2(0), + m_alignedTaps2(0), + m_ptr(0), + m_phaseSteps(1), + m_nTaps(1) +{ +} + +Interpolator::~Interpolator() +{ + free(); +} + +void Interpolator::create(int phaseSteps, double sampleRate, double cutoff, double nbTapsPerPhase) +{ + free(); + + std::vector taps; + + createPolyphaseLowPass( + taps, + phaseSteps, // number of polyphases + 1.0, // gain + phaseSteps * sampleRate, // sampling frequency + cutoff, // hz beginning of transition band + nbTapsPerPhase); + + // init state + m_ptr = 0; + m_nTaps = taps.size() / phaseSteps; + m_phaseSteps = phaseSteps; + m_samples.resize(m_nTaps + 2); + + for (int i = 0; i < m_nTaps + 2; i++) { + m_samples[i] = 0; + } + + // reorder into polyphase + std::vector polyphase(taps.size()); + + for (int phase = 0; phase < phaseSteps; phase++) + { + for (int i = 0; i < m_nTaps; i++) { + polyphase[phase * m_nTaps + i] = taps[i * phaseSteps + phase]; + } + } + + // normalize phase filters + for (int phase = 0; phase < phaseSteps; phase++) + { + Real sum = 0; + + for (int i = phase * m_nTaps; i < phase * m_nTaps + m_nTaps; i++) { + sum += polyphase[i]; + } + + for (int i = phase * m_nTaps; i < phase * m_nTaps + m_nTaps; i++) { + polyphase[i] /= sum; + } + } + + // move taps around to match sse storage requirements + m_taps = new float[2 * taps.size() + 8]; + + for (uint i = 0; i < 2 * taps.size() + 8; ++i) { + m_taps[i] = 0; + } + + m_alignedTaps = (float*)((((quint64)m_taps) + 15) & ~15); + + for (uint i = 0; i < taps.size(); ++i) + { + m_alignedTaps[2 * i + 0] = polyphase[i]; + m_alignedTaps[2 * i + 1] = polyphase[i]; + } + + m_taps2 = new float[2 * taps.size() + 8]; + + for (uint i = 0; i < 2 * taps.size() + 8; ++i) { + m_taps2[i] = 0; + } + + m_alignedTaps2 = (float*)((((quint64)m_taps2) + 15) & ~15); + + for (uint i = 1; i < taps.size(); ++i) + { + m_alignedTaps2[2 * (i - 1) + 0] = polyphase[i]; + m_alignedTaps2[2 * (i - 1) + 1] = polyphase[i]; + } +} + +void Interpolator::free() +{ + if (m_taps != NULL) + { + delete[] m_taps; + m_taps = NULL; + m_alignedTaps = NULL; + delete[] m_taps2; + m_taps2 = NULL; + m_alignedTaps2 = NULL; + } +} diff --git a/android/app/src/main/cpp/dsp/interpolator.h b/android/app/src/main/cpp/dsp/interpolator.h new file mode 100644 index 0000000..cdad384 --- /dev/null +++ b/android/app/src/main/cpp/dsp/interpolator.h @@ -0,0 +1,226 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 Hoernchen // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTERPOLATOR_H +#define INCLUDE_INTERPOLATOR_H + +#ifdef USE_SSE2 +#include +#endif +#include "dsp/dsptypes.h" +#include "export.h" +#include + +class SDRBASE_API Interpolator { +public: + Interpolator(); + ~Interpolator(); + + void create(int phaseSteps, double sampleRate, double cutoff, double nbTapsPerPhase = 4.5); + void free(); + + // Original code allowed for upsampling, but was never used that way + // The decimation factor should always be lower than 2 for proper work + bool decimate(Real *distance, const Complex& next, Complex* result) + { + advanceFilter(next); + *distance -= 1.0; + + if (*distance >= 1.0) { + return false; + } + + doInterpolate((int) floor(*distance * (Real)m_phaseSteps), result); + + return true; + } + + // interpolation simplified from the generalized resampler + bool interpolate(Real *distance, const Complex& next, Complex* result) + { + bool consumed = false; + + if (*distance >= 1.0) + { + advanceFilter(next); + *distance -= 1.0; + consumed = true; + } + + doInterpolate((int)floor(*distance * (Real)m_phaseSteps), result); + + return consumed; + } + + // original interpolator which is actually an arbitrary rational resampler P/Q for any positive P, Q + // sampling frequency must be the highest of the two + bool resample(Real* distance, const Complex& next, bool* consumed, Complex* result) + { + while (*distance >= 1.0) + { + if (!(*consumed)) + { + advanceFilter(next); + *distance -= 1.0; + *consumed = true; + } + else + { + return false; + } + } + + doInterpolate((int)floor(*distance * (Real)m_phaseSteps), result); + + return true; + } + +private: + float* m_taps; + float* m_alignedTaps; + float* m_taps2; + float* m_alignedTaps2; + std::vector m_samples; + int m_ptr; + int m_phaseSteps; + int m_nTaps; + + static void createPolyphaseLowPass( + std::vector& taps, + int phaseSteps, + double gain, + double sampleRateHz, + double cutoffFreqHz, + double transitionWidthHz, + double oobAttenuationdB); + + static void createPolyphaseLowPass( + std::vector& taps, + int phaseSteps, + double gain, + double sampleRateHz, + double cutoffFreqHz, + double nbTapsPerPhase); + + void createTaps(int nTaps, double sampleRate, double cutoff, std::vector* taps); + + void advanceFilter(const Complex& next) + { + m_ptr--; + + if (m_ptr < 0) { + m_ptr = m_nTaps - 1; + } + + m_samples[m_ptr] = next; + } + + void advanceFilter() + { + m_ptr--; + + if (m_ptr < 0) { + m_ptr = m_nTaps - 1; + } + + m_samples[m_ptr].real(0.0); + m_samples[m_ptr].imag(0.0); + } + + void doInterpolate(int phase, Complex* result) + { + if (phase < 0) { + phase = 0; + } +#if USE_SSE2 + // beware of the ringbuffer + if(m_ptr == 0) { + // only one straight block + const float* src = (const float*)&m_samples[0]; + const __m128* filter = (const __m128*)&m_alignedTaps[phase * m_nTaps * 2]; + __m128 sum = _mm_setzero_ps(); + int todo = m_nTaps / 2; + + for(int i = 0; i < todo; i++) { + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(src), *filter)); + src += 4; + filter += 1; + } + + // add upper half to lower half and store + _mm_storel_pi((__m64*)result, _mm_add_ps(sum, _mm_shuffle_ps(sum, _mm_setzero_ps(), _MM_SHUFFLE(1, 0, 3, 2)))); + } else { + // two blocks + const float* src = (const float*)&m_samples[m_ptr]; + const __m128* filter = (const __m128*)&m_alignedTaps[phase * m_nTaps * 2]; + __m128 sum = _mm_setzero_ps(); + + // first block + int block = m_nTaps - m_ptr; + int todo = block / 2; + if(block & 1) + todo++; + for(int i = 0; i < todo; i++) { + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(src), *filter)); + src += 4; + filter += 1; + } + if(block & 1) { + // one sample beyond the end -> switch coefficient table + filter = (const __m128*)&m_alignedTaps2[phase * m_nTaps * 2 + todo * 4 - 4]; + } + // second block + src = (const float*)&m_samples[0]; + block = m_ptr; + todo = block / 2; + for(int i = 0; i < todo; i++) { + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(src), *filter)); + src += 4; + filter += 1; + } + if(block & 1) { + // one sample remaining + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadl_pi(_mm_setzero_ps(), (const __m64*)src), filter[0])); + } + + // add upper half to lower half and store + _mm_storel_pi((__m64*)result, _mm_add_ps(sum, _mm_shuffle_ps(sum, _mm_setzero_ps(), _MM_SHUFFLE(1, 0, 3, 2)))); + } +#else + int sample = m_ptr; + const Real* coeff = &m_alignedTaps[phase * m_nTaps * 2]; + Real rAcc = 0; + Real iAcc = 0; + + for (int i = 0; i < m_nTaps; i++) { + rAcc += *coeff * m_samples[sample].real(); + iAcc += *coeff * m_samples[sample].imag(); + sample = (sample + 1) % m_nTaps; + coeff += 2; + } + + *result = Complex(rAcc, iAcc); +#endif + + } +}; + +#endif // INCLUDE_INTERPOLATOR_H diff --git a/android/app/src/main/cpp/dsp/interpolators.h b/android/app/src/main/cpp/dsp/interpolators.h new file mode 100644 index 0000000..230a3f5 --- /dev/null +++ b/android/app/src/main/cpp/dsp/interpolators.h @@ -0,0 +1,1423 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GPL_DSP_INTERPOLATORS_H_ +#define INCLUDE_GPL_DSP_INTERPOLATORS_H_ + +#include "dsp/dsptypes.h" +#ifdef USE_SSE4_1 +#include "dsp/inthalfbandfiltereo1.h" +#else +#include "dsp/inthalfbandfilterdb.h" +#endif + +#define INTERPOLATORS_HB_FILTER_ORDER_FIRST 64 +#define INTERPOLATORS_HB_FILTER_ORDER_SECOND 32 +#define INTERPOLATORS_HB_FILTER_ORDER_NEXT 16 + +template +struct interpolation_shifts +{ + static const uint post1 = 0; + static const uint pre2 = 0; + static const uint post2 = 0; + static const uint pre4 = 0; + static const uint post4 = 0; + static const uint pre8 = 0; + static const uint post8 = 0; + static const uint pre16 = 0; + static const uint post16 = 0; + static const uint pre32 = 0; + static const uint post32 = 0; + static const uint pre64 = 0; + static const uint post64 = 0; +}; + +template<> +struct interpolation_shifts<16, 16> +{ + static const uint post1 = 0; + static const uint pre2 = 1; + static const uint post2 = 1; + static const uint pre4 = 2; + static const uint post4 = 2; + static const uint pre8 = 3; + static const uint post8 = 3; + static const uint pre16 = 3; + static const uint post16 = 3; + static const uint pre32 = 3; + static const uint post32 = 3; + static const uint pre64 = 3; + static const uint post64 = 3; +}; + +template<> +struct interpolation_shifts<16, 12> +{ + static const uint post1 = 4; + static const uint pre2 = 1; + static const uint post2 = 5; + static const uint pre4 = 2; + static const uint post4 = 6; + static const uint pre8 = 3; + static const uint post8 = 7; + static const uint pre16 = 3; + static const uint post16 = 7; + static const uint pre32 = 3; + static const uint post32 = 7; + static const uint pre64 = 3; + static const uint post64 = 7; +}; + +template<> +struct interpolation_shifts<16, 8> +{ + static const uint post1 = 8; + static const uint pre2 = 1; + static const uint post2 = 9; + static const uint pre4 = 2; + static const uint post4 = 10; + static const uint pre8 = 3; + static const uint post8 = 11; + static const uint pre16 = 3; + static const uint post16 = 11; + static const uint pre32 = 3; + static const uint post32 = 11; + static const uint pre64 = 3; + static const uint post64 = 11; +}; + +template +class Interpolators +{ +public: + // interleaved I/Q input buffer + void interpolate1(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + + void interpolate2_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate2_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate2_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + + void interpolate4_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate4_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate4_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + + void interpolate8_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate8_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate8_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + + void interpolate16_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate16_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate16_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + + void interpolate32_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate32_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate32_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + + void interpolate64_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate64_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + void interpolate64_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ = false); + +private: +#ifdef USE_SSE4_1 + IntHalfbandFilterEO1 m_interpolator2; // 1st stages + IntHalfbandFilterEO1 m_interpolator4; // 2nd stages + IntHalfbandFilterEO1 m_interpolator8; // 3rd stages + IntHalfbandFilterEO1 m_interpolator16; // 4th stages + IntHalfbandFilterEO1 m_interpolator32; // 5th stages + IntHalfbandFilterEO1 m_interpolator64; // 6th stages +#else + IntHalfbandFilterDB m_interpolator2; // 1st stages + IntHalfbandFilterDB m_interpolator4; // 2nd stages + IntHalfbandFilterDB m_interpolator8; // 3rd stages + IntHalfbandFilterDB m_interpolator16; // 4th stages + IntHalfbandFilterDB m_interpolator32; // 5th stages + IntHalfbandFilterDB m_interpolator64; // 6th stages +#endif +}; + +template +void Interpolators::interpolate1(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + if (invertIQ) + { + for (int pos = 0; pos < len - 1; pos += 2) + { + buf[pos+1] = (**it).m_real >> interpolation_shifts::post1; + buf[pos+0] = (**it).m_imag >> interpolation_shifts::post1; + ++(*it); + } + } + else + { + for (int pos = 0; pos < len - 1; pos += 2) + { + buf[pos+0] = (**it).m_real >> interpolation_shifts::post1; + buf[pos+1] = (**it).m_imag >> interpolation_shifts::post1; + ++(*it); + } + } +} + +template +void Interpolators::interpolate2_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[4]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 3; pos += 4) + { + *bufI = (**it).m_real << interpolation_shifts::pre2; + *bufQ = (**it).m_imag << interpolation_shifts::pre2; +// intbuf[2] = 0; +// intbuf[3] = 0; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post2; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post2; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post2; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post2; + + ++(*it); + } +} + +template +void Interpolators::interpolate2_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[8]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[5]; + bufQ1 = &intbuf[4]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[4]; + bufQ1 = &intbuf[5]; + } + + for (int pos = 0; pos < len - 7; pos += 8) + { + memset(intbuf, 0, 8*sizeof(qint32)); + + *bufI0 = (**it).m_real << interpolation_shifts::pre2; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre2; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post2; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post2; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post2; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post2; + buf[pos+4] = intbuf[4] >> interpolation_shifts::post2; + buf[pos+5] = intbuf[5] >> interpolation_shifts::post2; + buf[pos+6] = intbuf[6] >> interpolation_shifts::post2; + buf[pos+7] = intbuf[7] >> interpolation_shifts::post2; + } +} + +template +void Interpolators::interpolate2_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[8]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[5]; + bufQ1 = &intbuf[4]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[4]; + bufQ1 = &intbuf[5]; + } + + for (int pos = 0; pos < len - 7; pos += 8) + { + memset(intbuf, 0, 8*sizeof(qint32)); + + *bufI0 = (**it).m_real << interpolation_shifts::pre2; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre2; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post2; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post2; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post2; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post2; + buf[pos+4] = intbuf[4] >> interpolation_shifts::post2; + buf[pos+5] = intbuf[5] >> interpolation_shifts::post2; + buf[pos+6] = intbuf[6] >> interpolation_shifts::post2; + buf[pos+7] = intbuf[7] >> interpolation_shifts::post2; + } +} + +template +void Interpolators::interpolate4_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[8]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 7; pos += 8) + { + memset(intbuf, 0, 8*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre4; + *bufQ = (**it).m_imag << interpolation_shifts::pre4; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator4.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post4; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post4; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post4; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post4; + buf[pos+4] = intbuf[4] >> interpolation_shifts::post4; + buf[pos+5] = intbuf[5] >> interpolation_shifts::post4; + buf[pos+6] = intbuf[6] >> interpolation_shifts::post4; + buf[pos+7] = intbuf[7] >> interpolation_shifts::post4; + + ++(*it); + } +} + +template +void Interpolators::interpolate4_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[16]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[9]; + bufQ1 = &intbuf[8]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[8]; + bufQ1 = &intbuf[9]; + } + + for (int pos = 0; pos < len - 15; pos += 16) + { + memset(intbuf, 0, 16*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre4; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre4; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator4.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + + for (int i = 0; i < 16; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post4; + } + } +} + +template +void Interpolators::interpolate4_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[16]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[9]; + bufQ1 = &intbuf[8]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[8]; + bufQ1 = &intbuf[9]; + } + + for (int pos = 0; pos < len - 15; pos += 16) + { + memset(intbuf, 0, 16*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre4; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre4; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator4.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + + for (int i = 0; i < 16; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post4; + } + } +} + +template +void Interpolators::interpolate8_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[16]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 15; pos += 16) + { + memset(intbuf, 0, 16*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre8; + *bufQ = (**it).m_imag << interpolation_shifts::pre8; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator4.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator8.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator8.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator8.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post8; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post8; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post8; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post8; + buf[pos+4] = intbuf[4] >> interpolation_shifts::post8; + buf[pos+5] = intbuf[5] >> interpolation_shifts::post8; + buf[pos+6] = intbuf[6] >> interpolation_shifts::post8; + buf[pos+7] = intbuf[7] >> interpolation_shifts::post8; + buf[pos+8] = intbuf[8] >> interpolation_shifts::post8; + buf[pos+9] = intbuf[9] >> interpolation_shifts::post8; + buf[pos+10] = intbuf[10] >> interpolation_shifts::post8; + buf[pos+11] = intbuf[11] >> interpolation_shifts::post8; + buf[pos+12] = intbuf[12] >> interpolation_shifts::post8; + buf[pos+13] = intbuf[13] >> interpolation_shifts::post8; + buf[pos+14] = intbuf[14] >> interpolation_shifts::post8; + buf[pos+15] = intbuf[15] >> interpolation_shifts::post8; + + ++(*it); + } +} + +template +void Interpolators::interpolate8_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[32]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[17]; + bufQ1 = &intbuf[16]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[16]; + bufQ1 = &intbuf[17]; + } + + for (int pos = 0; pos < len - 31; pos += 32) + { + memset(intbuf, 0, 32*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre8; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre8; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator4.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator8.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator8.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator8.myInterpolateInf(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + + for (int i = 0; i < 32; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post8; + } + } +} + +template +void Interpolators::interpolate8_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[32]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[17]; + bufQ1 = &intbuf[16]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[16]; + bufQ1 = &intbuf[17]; + } + + for (int pos = 0; pos < len - 31; pos += 32) + { + memset(intbuf, 0, 32*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre8; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre8; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator4.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator8.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator8.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator8.myInterpolateSup(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + + for (int i = 0; i < 32; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post8; + } + } +} + +template +void Interpolators::interpolate16_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[32]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 31; pos += 32) + { + memset(intbuf, 0, 32*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre16; + *bufQ = (**it).m_imag << interpolation_shifts::pre16; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + m_interpolator4.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator8.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator8.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21]); + m_interpolator8.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + + m_interpolator16.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator16.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator16.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator16.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator16.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19]); + m_interpolator16.myInterpolate(&intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator16.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27]); + m_interpolator16.myInterpolate(&intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post16; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post16; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post16; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post16; + buf[pos+4] = intbuf[4] >> interpolation_shifts::post16; + buf[pos+5] = intbuf[5] >> interpolation_shifts::post16; + buf[pos+6] = intbuf[6] >> interpolation_shifts::post16; + buf[pos+7] = intbuf[7] >> interpolation_shifts::post16; + buf[pos+8] = intbuf[8] >> interpolation_shifts::post16; + buf[pos+9] = intbuf[9] >> interpolation_shifts::post16; + buf[pos+10] = intbuf[10] >> interpolation_shifts::post16; + buf[pos+11] = intbuf[11] >> interpolation_shifts::post16; + buf[pos+12] = intbuf[12] >> interpolation_shifts::post16; + buf[pos+13] = intbuf[13] >> interpolation_shifts::post16; + buf[pos+14] = intbuf[14] >> interpolation_shifts::post16; + buf[pos+15] = intbuf[15] >> interpolation_shifts::post16; + buf[pos+16] = intbuf[16] >> interpolation_shifts::post16; + buf[pos+17] = intbuf[17] >> interpolation_shifts::post16; + buf[pos+18] = intbuf[18] >> interpolation_shifts::post16; + buf[pos+19] = intbuf[19] >> interpolation_shifts::post16; + buf[pos+20] = intbuf[20] >> interpolation_shifts::post16; + buf[pos+21] = intbuf[21] >> interpolation_shifts::post16; + buf[pos+22] = intbuf[22] >> interpolation_shifts::post16; + buf[pos+23] = intbuf[23] >> interpolation_shifts::post16; + buf[pos+24] = intbuf[24] >> interpolation_shifts::post16; + buf[pos+25] = intbuf[25] >> interpolation_shifts::post16; + buf[pos+26] = intbuf[26] >> interpolation_shifts::post16; + buf[pos+27] = intbuf[27] >> interpolation_shifts::post16; + buf[pos+28] = intbuf[28] >> interpolation_shifts::post16; + buf[pos+29] = intbuf[29] >> interpolation_shifts::post16; + buf[pos+30] = intbuf[30] >> interpolation_shifts::post16; + buf[pos+31] = intbuf[31] >> interpolation_shifts::post16; + + ++(*it); + } +} + +template +void Interpolators::interpolate16_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[64]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[33]; + bufQ1 = &intbuf[32]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[32]; + bufQ1 = &intbuf[33]; + } + + for (int pos = 0; pos < len - 63; pos += 64) + { + memset(intbuf, 0, 64*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre16; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre16; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator4.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator8.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator8.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator8.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + + m_interpolator16.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator16.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator16.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator16.myInterpolateInf(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator16.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator16.myInterpolateInf(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator16.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator16.myInterpolateInf(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + + for (int i = 0; i < 64; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post16; + } + } +} + +template +void Interpolators::interpolate16_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[64]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[33]; + bufQ1 = &intbuf[32]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[32]; + bufQ1 = &intbuf[33]; + } + + for (int pos = 0; pos < len - 63; pos += 64) + { + memset(intbuf, 0, 64*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre16; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre16; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator4.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator8.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator8.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator8.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + + m_interpolator16.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator16.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator16.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator16.myInterpolateSup(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator16.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator16.myInterpolateSup(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator16.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator16.myInterpolateSup(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + + for (int i = 0; i < 64; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post16; + } + } +} + +template +void Interpolators::interpolate32_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[64]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 63; pos += 64) + { + memset(intbuf, 0, 64*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre32; + *bufQ = (**it).m_imag << interpolation_shifts::pre32; + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17]); + m_interpolator4.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + m_interpolator8.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator8.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41]); + m_interpolator8.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + + m_interpolator16.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator16.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator16.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21]); + m_interpolator16.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator16.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37]); + m_interpolator16.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator16.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53]); + m_interpolator16.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + + m_interpolator32.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator32.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator32.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator32.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator32.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19]); + m_interpolator32.myInterpolate(&intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator32.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27]); + m_interpolator32.myInterpolate(&intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator32.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35]); + m_interpolator32.myInterpolate(&intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator32.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43]); + m_interpolator32.myInterpolate(&intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator32.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51]); + m_interpolator32.myInterpolate(&intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator32.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59]); + m_interpolator32.myInterpolate(&intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post32; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post32; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post32; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post32; + buf[pos+4] = intbuf[4] >> interpolation_shifts::post32; + buf[pos+5] = intbuf[5] >> interpolation_shifts::post32; + buf[pos+6] = intbuf[6] >> interpolation_shifts::post32; + buf[pos+7] = intbuf[7] >> interpolation_shifts::post32; + buf[pos+8] = intbuf[8] >> interpolation_shifts::post32; + buf[pos+9] = intbuf[9] >> interpolation_shifts::post32; + buf[pos+10] = intbuf[10] >> interpolation_shifts::post32; + buf[pos+11] = intbuf[11] >> interpolation_shifts::post32; + buf[pos+12] = intbuf[12] >> interpolation_shifts::post32; + buf[pos+13] = intbuf[13] >> interpolation_shifts::post32; + buf[pos+14] = intbuf[14] >> interpolation_shifts::post32; + buf[pos+15] = intbuf[15] >> interpolation_shifts::post32; + buf[pos+16] = intbuf[16] >> interpolation_shifts::post32; + buf[pos+17] = intbuf[17] >> interpolation_shifts::post32; + buf[pos+18] = intbuf[18] >> interpolation_shifts::post32; + buf[pos+19] = intbuf[19] >> interpolation_shifts::post32; + buf[pos+20] = intbuf[20] >> interpolation_shifts::post32; + buf[pos+21] = intbuf[21] >> interpolation_shifts::post32; + buf[pos+22] = intbuf[22] >> interpolation_shifts::post32; + buf[pos+23] = intbuf[23] >> interpolation_shifts::post32; + buf[pos+24] = intbuf[24] >> interpolation_shifts::post32; + buf[pos+25] = intbuf[25] >> interpolation_shifts::post32; + buf[pos+26] = intbuf[26] >> interpolation_shifts::post32; + buf[pos+27] = intbuf[27] >> interpolation_shifts::post32; + buf[pos+28] = intbuf[28] >> interpolation_shifts::post32; + buf[pos+29] = intbuf[29] >> interpolation_shifts::post32; + buf[pos+30] = intbuf[30] >> interpolation_shifts::post32; + buf[pos+31] = intbuf[31] >> interpolation_shifts::post32; + buf[pos+32] = intbuf[32] >> interpolation_shifts::post32; + buf[pos+33] = intbuf[33] >> interpolation_shifts::post32; + buf[pos+34] = intbuf[34] >> interpolation_shifts::post32; + buf[pos+35] = intbuf[35] >> interpolation_shifts::post32; + buf[pos+36] = intbuf[36] >> interpolation_shifts::post32; + buf[pos+37] = intbuf[37] >> interpolation_shifts::post32; + buf[pos+38] = intbuf[38] >> interpolation_shifts::post32; + buf[pos+39] = intbuf[39] >> interpolation_shifts::post32; + buf[pos+40] = intbuf[40] >> interpolation_shifts::post32; + buf[pos+41] = intbuf[41] >> interpolation_shifts::post32; + buf[pos+42] = intbuf[42] >> interpolation_shifts::post32; + buf[pos+43] = intbuf[43] >> interpolation_shifts::post32; + buf[pos+44] = intbuf[44] >> interpolation_shifts::post32; + buf[pos+45] = intbuf[45] >> interpolation_shifts::post32; + buf[pos+46] = intbuf[46] >> interpolation_shifts::post32; + buf[pos+47] = intbuf[47] >> interpolation_shifts::post32; + buf[pos+48] = intbuf[48] >> interpolation_shifts::post32; + buf[pos+49] = intbuf[49] >> interpolation_shifts::post32; + buf[pos+50] = intbuf[50] >> interpolation_shifts::post32; + buf[pos+51] = intbuf[51] >> interpolation_shifts::post32; + buf[pos+52] = intbuf[52] >> interpolation_shifts::post32; + buf[pos+53] = intbuf[53] >> interpolation_shifts::post32; + buf[pos+54] = intbuf[54] >> interpolation_shifts::post32; + buf[pos+55] = intbuf[55] >> interpolation_shifts::post32; + buf[pos+56] = intbuf[56] >> interpolation_shifts::post32; + buf[pos+57] = intbuf[57] >> interpolation_shifts::post32; + buf[pos+58] = intbuf[58] >> interpolation_shifts::post32; + buf[pos+59] = intbuf[59] >> interpolation_shifts::post32; + buf[pos+60] = intbuf[60] >> interpolation_shifts::post32; + buf[pos+61] = intbuf[61] >> interpolation_shifts::post32; + buf[pos+62] = intbuf[62] >> interpolation_shifts::post32; + buf[pos+63] = intbuf[63] >> interpolation_shifts::post32; + + ++(*it); + } +} + +template +void Interpolators::interpolate32_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[128]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[65]; + bufQ1 = &intbuf[64]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[64]; + bufQ1 = &intbuf[65]; + } + + for (int pos = 0; pos < len - 127; pos += 128) + { + memset(intbuf, 0, 128*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre32; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre32; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator4.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator8.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator8.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator8.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + + m_interpolator16.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator16.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator16.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator16.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + m_interpolator16.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[68], &intbuf[69], &intbuf[72], &intbuf[73], &intbuf[76], &intbuf[77]); + m_interpolator16.myInterpolateInf(&intbuf[80], &intbuf[81], &intbuf[84], &intbuf[85], &intbuf[88], &intbuf[89], &intbuf[92], &intbuf[93]); + m_interpolator16.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[100], &intbuf[101], &intbuf[104], &intbuf[105], &intbuf[108], &intbuf[109]); + m_interpolator16.myInterpolateInf(&intbuf[112], &intbuf[113], &intbuf[116], &intbuf[117], &intbuf[120], &intbuf[121], &intbuf[124], &intbuf[125]); + + m_interpolator32.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator32.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator32.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator32.myInterpolateInf(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator32.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator32.myInterpolateInf(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator32.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator32.myInterpolateInf(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + m_interpolator32.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[66], &intbuf[67], &intbuf[68], &intbuf[69], &intbuf[70], &intbuf[71]); + m_interpolator32.myInterpolateInf(&intbuf[72], &intbuf[73], &intbuf[74], &intbuf[75], &intbuf[76], &intbuf[77], &intbuf[78], &intbuf[79]); + m_interpolator32.myInterpolateInf(&intbuf[80], &intbuf[81], &intbuf[82], &intbuf[83], &intbuf[84], &intbuf[85], &intbuf[86], &intbuf[87]); + m_interpolator32.myInterpolateInf(&intbuf[88], &intbuf[89], &intbuf[90], &intbuf[91], &intbuf[92], &intbuf[93], &intbuf[94], &intbuf[95]); + m_interpolator32.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[98], &intbuf[99], &intbuf[100], &intbuf[101], &intbuf[102], &intbuf[103]); + m_interpolator32.myInterpolateInf(&intbuf[104], &intbuf[105], &intbuf[106], &intbuf[107], &intbuf[108], &intbuf[109], &intbuf[110], &intbuf[111]); + m_interpolator32.myInterpolateInf(&intbuf[112], &intbuf[113], &intbuf[114], &intbuf[115], &intbuf[116], &intbuf[117], &intbuf[118], &intbuf[119]); + m_interpolator32.myInterpolateInf(&intbuf[120], &intbuf[121], &intbuf[122], &intbuf[123], &intbuf[124], &intbuf[125], &intbuf[126], &intbuf[127]); + + for (int i = 0; i < 128; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post32; + } + } +} + +template +void Interpolators::interpolate32_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[128]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[65]; + bufQ1 = &intbuf[64]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[64]; + bufQ1 = &intbuf[65]; + } + + for (int pos = 0; pos < len - 127; pos += 128) + { + memset(intbuf, 0, 128*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre32; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre32; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator4.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator8.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator8.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator8.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + + m_interpolator16.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator16.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator16.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator16.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + m_interpolator16.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[68], &intbuf[69], &intbuf[72], &intbuf[73], &intbuf[76], &intbuf[77]); + m_interpolator16.myInterpolateSup(&intbuf[80], &intbuf[81], &intbuf[84], &intbuf[85], &intbuf[88], &intbuf[89], &intbuf[92], &intbuf[93]); + m_interpolator16.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[100], &intbuf[101], &intbuf[104], &intbuf[105], &intbuf[108], &intbuf[109]); + m_interpolator16.myInterpolateSup(&intbuf[112], &intbuf[113], &intbuf[116], &intbuf[117], &intbuf[120], &intbuf[121], &intbuf[124], &intbuf[125]); + + m_interpolator32.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator32.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator32.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator32.myInterpolateSup(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator32.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator32.myInterpolateSup(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator32.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator32.myInterpolateSup(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + m_interpolator32.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[66], &intbuf[67], &intbuf[68], &intbuf[69], &intbuf[70], &intbuf[71]); + m_interpolator32.myInterpolateSup(&intbuf[72], &intbuf[73], &intbuf[74], &intbuf[75], &intbuf[76], &intbuf[77], &intbuf[78], &intbuf[79]); + m_interpolator32.myInterpolateSup(&intbuf[80], &intbuf[81], &intbuf[82], &intbuf[83], &intbuf[84], &intbuf[85], &intbuf[86], &intbuf[87]); + m_interpolator32.myInterpolateSup(&intbuf[88], &intbuf[89], &intbuf[90], &intbuf[91], &intbuf[92], &intbuf[93], &intbuf[94], &intbuf[95]); + m_interpolator32.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[98], &intbuf[99], &intbuf[100], &intbuf[101], &intbuf[102], &intbuf[103]); + m_interpolator32.myInterpolateSup(&intbuf[104], &intbuf[105], &intbuf[106], &intbuf[107], &intbuf[108], &intbuf[109], &intbuf[110], &intbuf[111]); + m_interpolator32.myInterpolateSup(&intbuf[112], &intbuf[113], &intbuf[114], &intbuf[115], &intbuf[116], &intbuf[117], &intbuf[118], &intbuf[119]); + m_interpolator32.myInterpolateSup(&intbuf[120], &intbuf[121], &intbuf[122], &intbuf[123], &intbuf[124], &intbuf[125], &intbuf[126], &intbuf[127]); + + for (int i = 0; i < 128; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post32; + } + } +} + +template +void Interpolators::interpolate64_cen(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[128]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 127; pos += 128) + { + memset(intbuf, 0, 128*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre64; + *bufQ = (**it).m_imag << interpolation_shifts::pre64; + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[64], &intbuf[65]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33]); + m_interpolator4.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17]); + m_interpolator8.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator8.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81]); + m_interpolator8.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + + m_interpolator16.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + m_interpolator16.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator16.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41]); + m_interpolator16.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator16.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73]); + m_interpolator16.myInterpolate(&intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator16.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105]); + m_interpolator16.myInterpolate(&intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + + m_interpolator32.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator32.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator32.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21]); + m_interpolator32.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator32.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37]); + m_interpolator32.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator32.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53]); + m_interpolator32.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + m_interpolator32.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[68], &intbuf[69]); + m_interpolator32.myInterpolate(&intbuf[72], &intbuf[73], &intbuf[76], &intbuf[77]); + m_interpolator32.myInterpolate(&intbuf[80], &intbuf[81], &intbuf[84], &intbuf[85]); + m_interpolator32.myInterpolate(&intbuf[88], &intbuf[89], &intbuf[92], &intbuf[93]); + m_interpolator32.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[100], &intbuf[101]); + m_interpolator32.myInterpolate(&intbuf[104], &intbuf[105], &intbuf[108], &intbuf[109]); + m_interpolator32.myInterpolate(&intbuf[112], &intbuf[113], &intbuf[116], &intbuf[117]); + m_interpolator32.myInterpolate(&intbuf[120], &intbuf[121], &intbuf[124], &intbuf[125]); + + m_interpolator64.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator64.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator64.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator64.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator64.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19]); + m_interpolator64.myInterpolate(&intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator64.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27]); + m_interpolator64.myInterpolate(&intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator64.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35]); + m_interpolator64.myInterpolate(&intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator64.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43]); + m_interpolator64.myInterpolate(&intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator64.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51]); + m_interpolator64.myInterpolate(&intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator64.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59]); + m_interpolator64.myInterpolate(&intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + m_interpolator64.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[66], &intbuf[67]); + m_interpolator64.myInterpolate(&intbuf[68], &intbuf[69], &intbuf[70], &intbuf[71]); + m_interpolator64.myInterpolate(&intbuf[72], &intbuf[73], &intbuf[74], &intbuf[75]); + m_interpolator64.myInterpolate(&intbuf[76], &intbuf[77], &intbuf[78], &intbuf[79]); + m_interpolator64.myInterpolate(&intbuf[80], &intbuf[81], &intbuf[82], &intbuf[83]); + m_interpolator64.myInterpolate(&intbuf[84], &intbuf[85], &intbuf[86], &intbuf[87]); + m_interpolator64.myInterpolate(&intbuf[88], &intbuf[89], &intbuf[90], &intbuf[91]); + m_interpolator64.myInterpolate(&intbuf[92], &intbuf[93], &intbuf[94], &intbuf[95]); + m_interpolator64.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[98], &intbuf[99]); + m_interpolator64.myInterpolate(&intbuf[100], &intbuf[101], &intbuf[102], &intbuf[103]); + m_interpolator64.myInterpolate(&intbuf[104], &intbuf[105], &intbuf[106], &intbuf[107]); + m_interpolator64.myInterpolate(&intbuf[108], &intbuf[109], &intbuf[110], &intbuf[111]); + m_interpolator64.myInterpolate(&intbuf[112], &intbuf[113], &intbuf[114], &intbuf[115]); + m_interpolator64.myInterpolate(&intbuf[116], &intbuf[117], &intbuf[118], &intbuf[119]); + m_interpolator64.myInterpolate(&intbuf[120], &intbuf[121], &intbuf[122], &intbuf[123]); + m_interpolator64.myInterpolate(&intbuf[124], &intbuf[125], &intbuf[126], &intbuf[127]); + + buf[pos+0] = intbuf[0] >> interpolation_shifts::post64; + buf[pos+1] = intbuf[1] >> interpolation_shifts::post64; + buf[pos+2] = intbuf[2] >> interpolation_shifts::post64; + buf[pos+3] = intbuf[3] >> interpolation_shifts::post64; + buf[pos+4] = intbuf[4] >> interpolation_shifts::post64; + buf[pos+5] = intbuf[5] >> interpolation_shifts::post64; + buf[pos+6] = intbuf[6] >> interpolation_shifts::post64; + buf[pos+7] = intbuf[7] >> interpolation_shifts::post64; + buf[pos+8] = intbuf[8] >> interpolation_shifts::post64; + buf[pos+9] = intbuf[9] >> interpolation_shifts::post64; + buf[pos+10] = intbuf[10] >> interpolation_shifts::post64; + buf[pos+11] = intbuf[11] >> interpolation_shifts::post64; + buf[pos+12] = intbuf[12] >> interpolation_shifts::post64; + buf[pos+13] = intbuf[13] >> interpolation_shifts::post64; + buf[pos+14] = intbuf[14] >> interpolation_shifts::post64; + buf[pos+15] = intbuf[15] >> interpolation_shifts::post64; + buf[pos+16] = intbuf[16] >> interpolation_shifts::post64; + buf[pos+17] = intbuf[17] >> interpolation_shifts::post64; + buf[pos+18] = intbuf[18] >> interpolation_shifts::post64; + buf[pos+19] = intbuf[19] >> interpolation_shifts::post64; + buf[pos+20] = intbuf[20] >> interpolation_shifts::post64; + buf[pos+21] = intbuf[21] >> interpolation_shifts::post64; + buf[pos+22] = intbuf[22] >> interpolation_shifts::post64; + buf[pos+23] = intbuf[23] >> interpolation_shifts::post64; + buf[pos+24] = intbuf[24] >> interpolation_shifts::post64; + buf[pos+25] = intbuf[25] >> interpolation_shifts::post64; + buf[pos+26] = intbuf[26] >> interpolation_shifts::post64; + buf[pos+27] = intbuf[27] >> interpolation_shifts::post64; + buf[pos+28] = intbuf[28] >> interpolation_shifts::post64; + buf[pos+29] = intbuf[29] >> interpolation_shifts::post64; + buf[pos+30] = intbuf[30] >> interpolation_shifts::post64; + buf[pos+31] = intbuf[31] >> interpolation_shifts::post64; + buf[pos+32] = intbuf[32] >> interpolation_shifts::post64; + buf[pos+33] = intbuf[33] >> interpolation_shifts::post64; + buf[pos+34] = intbuf[34] >> interpolation_shifts::post64; + buf[pos+35] = intbuf[35] >> interpolation_shifts::post64; + buf[pos+36] = intbuf[36] >> interpolation_shifts::post64; + buf[pos+37] = intbuf[37] >> interpolation_shifts::post64; + buf[pos+38] = intbuf[38] >> interpolation_shifts::post64; + buf[pos+39] = intbuf[39] >> interpolation_shifts::post64; + buf[pos+40] = intbuf[40] >> interpolation_shifts::post64; + buf[pos+41] = intbuf[41] >> interpolation_shifts::post64; + buf[pos+42] = intbuf[42] >> interpolation_shifts::post64; + buf[pos+43] = intbuf[43] >> interpolation_shifts::post64; + buf[pos+44] = intbuf[44] >> interpolation_shifts::post64; + buf[pos+45] = intbuf[45] >> interpolation_shifts::post64; + buf[pos+46] = intbuf[46] >> interpolation_shifts::post64; + buf[pos+47] = intbuf[47] >> interpolation_shifts::post64; + buf[pos+48] = intbuf[48] >> interpolation_shifts::post64; + buf[pos+49] = intbuf[49] >> interpolation_shifts::post64; + buf[pos+50] = intbuf[50] >> interpolation_shifts::post64; + buf[pos+51] = intbuf[51] >> interpolation_shifts::post64; + buf[pos+52] = intbuf[52] >> interpolation_shifts::post64; + buf[pos+53] = intbuf[53] >> interpolation_shifts::post64; + buf[pos+54] = intbuf[54] >> interpolation_shifts::post64; + buf[pos+55] = intbuf[55] >> interpolation_shifts::post64; + buf[pos+56] = intbuf[56] >> interpolation_shifts::post64; + buf[pos+57] = intbuf[57] >> interpolation_shifts::post64; + buf[pos+58] = intbuf[58] >> interpolation_shifts::post64; + buf[pos+59] = intbuf[59] >> interpolation_shifts::post64; + buf[pos+60] = intbuf[60] >> interpolation_shifts::post64; + buf[pos+61] = intbuf[61] >> interpolation_shifts::post64; + buf[pos+62] = intbuf[62] >> interpolation_shifts::post64; + buf[pos+63] = intbuf[63] >> interpolation_shifts::post64; + buf[pos+64] = intbuf[64] >> interpolation_shifts::post64; + buf[pos+65] = intbuf[65] >> interpolation_shifts::post64; + buf[pos+66] = intbuf[66] >> interpolation_shifts::post64; + buf[pos+67] = intbuf[67] >> interpolation_shifts::post64; + buf[pos+68] = intbuf[68] >> interpolation_shifts::post64; + buf[pos+69] = intbuf[69] >> interpolation_shifts::post64; + buf[pos+70] = intbuf[70] >> interpolation_shifts::post64; + buf[pos+71] = intbuf[71] >> interpolation_shifts::post64; + buf[pos+72] = intbuf[72] >> interpolation_shifts::post64; + buf[pos+73] = intbuf[73] >> interpolation_shifts::post64; + buf[pos+74] = intbuf[74] >> interpolation_shifts::post64; + buf[pos+75] = intbuf[75] >> interpolation_shifts::post64; + buf[pos+76] = intbuf[76] >> interpolation_shifts::post64; + buf[pos+77] = intbuf[77] >> interpolation_shifts::post64; + buf[pos+78] = intbuf[78] >> interpolation_shifts::post64; + buf[pos+79] = intbuf[79] >> interpolation_shifts::post64; + buf[pos+80] = intbuf[80] >> interpolation_shifts::post64; + buf[pos+81] = intbuf[81] >> interpolation_shifts::post64; + buf[pos+82] = intbuf[82] >> interpolation_shifts::post64; + buf[pos+83] = intbuf[83] >> interpolation_shifts::post64; + buf[pos+84] = intbuf[84] >> interpolation_shifts::post64; + buf[pos+85] = intbuf[85] >> interpolation_shifts::post64; + buf[pos+86] = intbuf[86] >> interpolation_shifts::post64; + buf[pos+87] = intbuf[87] >> interpolation_shifts::post64; + buf[pos+88] = intbuf[88] >> interpolation_shifts::post64; + buf[pos+89] = intbuf[89] >> interpolation_shifts::post64; + buf[pos+90] = intbuf[90] >> interpolation_shifts::post64; + buf[pos+91] = intbuf[91] >> interpolation_shifts::post64; + buf[pos+92] = intbuf[92] >> interpolation_shifts::post64; + buf[pos+93] = intbuf[93] >> interpolation_shifts::post64; + buf[pos+94] = intbuf[94] >> interpolation_shifts::post64; + buf[pos+95] = intbuf[95] >> interpolation_shifts::post64; + buf[pos+96] = intbuf[96] >> interpolation_shifts::post64; + buf[pos+97] = intbuf[97] >> interpolation_shifts::post64; + buf[pos+98] = intbuf[98] >> interpolation_shifts::post64; + buf[pos+99] = intbuf[99] >> interpolation_shifts::post64; + buf[pos+100] = intbuf[100] >> interpolation_shifts::post64; + buf[pos+101] = intbuf[101] >> interpolation_shifts::post64; + buf[pos+102] = intbuf[102] >> interpolation_shifts::post64; + buf[pos+103] = intbuf[103] >> interpolation_shifts::post64; + buf[pos+104] = intbuf[104] >> interpolation_shifts::post64; + buf[pos+105] = intbuf[105] >> interpolation_shifts::post64; + buf[pos+106] = intbuf[106] >> interpolation_shifts::post64; + buf[pos+107] = intbuf[107] >> interpolation_shifts::post64; + buf[pos+108] = intbuf[108] >> interpolation_shifts::post64; + buf[pos+109] = intbuf[109] >> interpolation_shifts::post64; + buf[pos+109] = intbuf[109] >> interpolation_shifts::post64; + buf[pos+110] = intbuf[110] >> interpolation_shifts::post64; + buf[pos+111] = intbuf[111] >> interpolation_shifts::post64; + buf[pos+112] = intbuf[112] >> interpolation_shifts::post64; + buf[pos+113] = intbuf[113] >> interpolation_shifts::post64; + buf[pos+114] = intbuf[114] >> interpolation_shifts::post64; + buf[pos+115] = intbuf[115] >> interpolation_shifts::post64; + buf[pos+116] = intbuf[116] >> interpolation_shifts::post64; + buf[pos+117] = intbuf[117] >> interpolation_shifts::post64; + buf[pos+118] = intbuf[118] >> interpolation_shifts::post64; + buf[pos+119] = intbuf[119] >> interpolation_shifts::post64; + buf[pos+120] = intbuf[120] >> interpolation_shifts::post64; + buf[pos+121] = intbuf[121] >> interpolation_shifts::post64; + buf[pos+122] = intbuf[122] >> interpolation_shifts::post64; + buf[pos+123] = intbuf[123] >> interpolation_shifts::post64; + buf[pos+124] = intbuf[124] >> interpolation_shifts::post64; + buf[pos+125] = intbuf[125] >> interpolation_shifts::post64; + buf[pos+126] = intbuf[126] >> interpolation_shifts::post64; + buf[pos+127] = intbuf[127] >> interpolation_shifts::post64; + + ++(*it); + } +} + +template +void Interpolators::interpolate64_inf(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[256]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[129]; + bufQ1 = &intbuf[128]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[128]; + bufQ1 = &intbuf[129]; + } + + for (int pos = 0; pos < len - 255; pos += 256) + { + memset(intbuf, 0, 256*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre64; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre64; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[64], &intbuf[65], &intbuf[128], &intbuf[129], &intbuf[192], &intbuf[193]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + m_interpolator4.myInterpolateInf(&intbuf[128], &intbuf[129], &intbuf[160], &intbuf[161], &intbuf[192], &intbuf[193], &intbuf[224], &intbuf[225]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator8.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + m_interpolator8.myInterpolateSup(&intbuf[128], &intbuf[129], &intbuf[144], &intbuf[145], &intbuf[160], &intbuf[161], &intbuf[176], &intbuf[177]); + m_interpolator8.myInterpolateSup(&intbuf[192], &intbuf[193], &intbuf[208], &intbuf[209], &intbuf[224], &intbuf[225], &intbuf[240], &intbuf[241]); + + m_interpolator16.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator16.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator16.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator16.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + m_interpolator16.myInterpolateInf(&intbuf[128], &intbuf[129], &intbuf[136], &intbuf[137], &intbuf[144], &intbuf[145], &intbuf[152], &intbuf[153]); + m_interpolator16.myInterpolateInf(&intbuf[160], &intbuf[161], &intbuf[168], &intbuf[169], &intbuf[176], &intbuf[177], &intbuf[184], &intbuf[185]); + m_interpolator16.myInterpolateInf(&intbuf[192], &intbuf[193], &intbuf[200], &intbuf[201], &intbuf[208], &intbuf[209], &intbuf[216], &intbuf[217]); + m_interpolator16.myInterpolateInf(&intbuf[224], &intbuf[225], &intbuf[232], &intbuf[233], &intbuf[240], &intbuf[241], &intbuf[248], &intbuf[249]); + + for (int i = 0; i < 16; i++) { + m_interpolator32.myInterpolateSup( + &intbuf[16*i+0], + &intbuf[16*i+1], + &intbuf[16*i+4], + &intbuf[16*i+5], + &intbuf[16*i+8], + &intbuf[16*i+9], + &intbuf[16*i+12], + &intbuf[16*i+13]); + } + + for (int i = 0; i < 32; i++) { + m_interpolator64.myInterpolateInf( + &intbuf[8*i+0], + &intbuf[8*i+1], + &intbuf[8*i+2], + &intbuf[8*i+3], + &intbuf[8*i+4], + &intbuf[8*i+5], + &intbuf[8*i+6], + &intbuf[8*i+7]); + } + + for (int i = 0; i < 256; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post64; + } + } +} + +template +void Interpolators::interpolate64_sup(SampleVector::iterator* it, T* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[256]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[129]; + bufQ1 = &intbuf[128]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[128]; + bufQ1 = &intbuf[129]; + } + + for (int pos = 0; pos < len - 255; pos += 256) + { + memset(intbuf, 0, 256*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre64; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre64; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[64], &intbuf[65], &intbuf[128], &intbuf[129], &intbuf[192], &intbuf[193]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + m_interpolator4.myInterpolateSup(&intbuf[128], &intbuf[129], &intbuf[160], &intbuf[161], &intbuf[192], &intbuf[193], &intbuf[224], &intbuf[225]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator8.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + m_interpolator8.myInterpolateInf(&intbuf[128], &intbuf[129], &intbuf[144], &intbuf[145], &intbuf[160], &intbuf[161], &intbuf[176], &intbuf[177]); + m_interpolator8.myInterpolateInf(&intbuf[192], &intbuf[193], &intbuf[208], &intbuf[209], &intbuf[224], &intbuf[225], &intbuf[240], &intbuf[241]); + + m_interpolator16.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator16.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator16.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator16.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + m_interpolator16.myInterpolateSup(&intbuf[128], &intbuf[129], &intbuf[136], &intbuf[137], &intbuf[144], &intbuf[145], &intbuf[152], &intbuf[153]); + m_interpolator16.myInterpolateSup(&intbuf[160], &intbuf[161], &intbuf[168], &intbuf[169], &intbuf[176], &intbuf[177], &intbuf[184], &intbuf[185]); + m_interpolator16.myInterpolateSup(&intbuf[192], &intbuf[193], &intbuf[200], &intbuf[201], &intbuf[208], &intbuf[209], &intbuf[216], &intbuf[217]); + m_interpolator16.myInterpolateSup(&intbuf[224], &intbuf[225], &intbuf[232], &intbuf[233], &intbuf[240], &intbuf[241], &intbuf[248], &intbuf[249]); + + for (int i = 0; i < 16; i++) { + m_interpolator32.myInterpolateInf( + &intbuf[16*i+0], + &intbuf[16*i+1], + &intbuf[16*i+4], + &intbuf[16*i+5], + &intbuf[16*i+8], + &intbuf[16*i+9], + &intbuf[16*i+12], + &intbuf[16*i+13]); + } + + for (int i = 0; i < 32; i++) { + m_interpolator64.myInterpolateSup( + &intbuf[8*i+0], + &intbuf[8*i+1], + &intbuf[8*i+2], + &intbuf[8*i+3], + &intbuf[8*i+4], + &intbuf[8*i+5], + &intbuf[8*i+6], + &intbuf[8*i+7]); + } + + for (int i = 0; i < 256; i++) { + buf[pos+i] = intbuf[i] >> interpolation_shifts::post64; + } + } +} + +#endif /* INCLUDE_GPL_DSP_INTERPOLATORS_H_ */ diff --git a/android/app/src/main/cpp/dsp/interpolatorsif.h b/android/app/src/main/cpp/dsp/interpolatorsif.h new file mode 100644 index 0000000..5f496c1 --- /dev/null +++ b/android/app/src/main/cpp/dsp/interpolatorsif.h @@ -0,0 +1,1404 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SDRBASE_DSP_INTERPOLATORSIF_H_ +#define INCLUDE_SDRBASE_DSP_INTERPOLATORSIF_H_ + +#include "dsp/dsptypes.h" +#ifdef USE_SSE4_1 +#include "dsp/inthalfbandfiltereo1.h" +#else +#include "dsp/inthalfbandfilterdb.h" +#endif + +#define INTERPOLATORS_HB_FILTER_ORDER_FIRST 64 +#define INTERPOLATORS_HB_FILTER_ORDER_SECOND 32 +#define INTERPOLATORS_HB_FILTER_ORDER_NEXT 16 + +template +struct interpolation_shifts_float +{ + static constexpr float post1 = 1.0f; + static const uint pre2 = 0; + static constexpr float post2 = 1.0f; + static const uint pre4 = 0; + static constexpr float post4 = 1.0f; + static const uint pre8 = 0; + static constexpr float post8 = 1.0f; + static const uint pre16 = 0; + static constexpr float post16 = 1.0f; + static const uint pre32 = 0; + static constexpr float post32 = 1.0f; + static const uint pre64 = 0; + static constexpr float post64 = 1.0f; +}; + +template<> +struct interpolation_shifts_float<16, 16> +{ + static constexpr float post1 = (1<<0)*32768.0f; + static const uint pre2 = 1; + static constexpr float post2 = (1<<1)*32768.0f; + static const uint pre4 = 2; + static constexpr float post4 = (1<<2)*32768.0f; + static const uint pre8 = 3; + static constexpr float post8 = (1<<3)*32768.0f; + static const uint pre16 = 3; + static constexpr float post16 = (1<<3)*32768.0f; + static const uint pre32 = 3; + static constexpr float post32 = (1<<3)*32768.0f; + static const uint pre64 = 3; + static constexpr float post64 = (1<<3)*32768.0f; +}; + +template<> +struct interpolation_shifts_float<16, 12> // never used, just an example +{ + static constexpr float post1 = (1<<4)*2048.0f; + static const uint pre2 = 1; + static constexpr float post2 = (1<<5)*2048.0f; + static const uint pre4 = 2; + static constexpr float post4 = (1<<6)*2048.0f; + static const uint pre8 = 3; + static constexpr float post8 = (1<<7)*2048.0f; + static const uint pre16 = 3; + static constexpr float post16 = (1<<7)*2048.0f; + static const uint pre32 = 3; + static constexpr float post32 = (1<<7)*2048.0f; + static const uint pre64 = 3; + static constexpr float post64 = (1<<7)*2048.0f; +}; + +template +class InterpolatorsIF +{ +public: + // interleaved I/Q input buffer + void interpolate1(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + + void interpolate2_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate2_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate2_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + + void interpolate4_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate4_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate4_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + + void interpolate8_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate8_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate8_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + + void interpolate16_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate16_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate16_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + + void interpolate32_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate32_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate32_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + + void interpolate64_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate64_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + void interpolate64_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ = false); + +private: +#ifdef USE_SSE4_1 + IntHalfbandFilterEO1 m_interpolator2; // 1st stages + IntHalfbandFilterEO1 m_interpolator4; // 2nd stages + IntHalfbandFilterEO1 m_interpolator8; // 3rd stages + IntHalfbandFilterEO1 m_interpolator16; // 4th stages + IntHalfbandFilterEO1 m_interpolator32; // 5th stages + IntHalfbandFilterEO1 m_interpolator64; // 6th stages +#else + IntHalfbandFilterDB m_interpolator2; // 1st stages + IntHalfbandFilterDB m_interpolator4; // 2nd stages + IntHalfbandFilterDB m_interpolator8; // 3rd stages + IntHalfbandFilterDB m_interpolator16; // 4th stages + IntHalfbandFilterDB m_interpolator32; // 5th stages + IntHalfbandFilterDB m_interpolator64; // 6th stages +#endif +}; + +template +void InterpolatorsIF::interpolate1(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + if (invertIQ) + { + for (int pos = 0; pos < len - 1; pos += 2) + { + buf[pos+1] = (float) ((**it).m_real >> interpolation_shifts::post1); + buf[pos+0] = (float) ((**it).m_imag >> interpolation_shifts::post1); + ++(*it); + } + } + else + { + for (int pos = 0; pos < len - 1; pos += 2) + { + buf[pos+0] = (float) ((**it).m_real >> interpolation_shifts::post1); + buf[pos+1] = (float) ((**it).m_imag >> interpolation_shifts::post1); + ++(*it); + } + } +} + +template +void InterpolatorsIF::interpolate2_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[4]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 3; pos += 4) + { + *bufI = (**it).m_real << interpolation_shifts::pre2; + *bufQ = (**it).m_imag << interpolation_shifts::pre2; +// intbuf[2] = 0; +// intbuf[3] = 0; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post2); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post2); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post2); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post2); + + ++(*it); + } +} + +template +void InterpolatorsIF::interpolate2_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[8]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[5]; + bufQ1 = &intbuf[4]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[4]; + bufQ1 = &intbuf[5]; + } + + for (int pos = 0; pos < len - 7; pos += 8) + { + memset(intbuf, 0, 8*sizeof(qint32)); + + *bufI0 = (**it).m_real << interpolation_shifts::pre2; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre2; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post2); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post2); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post2); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post2); + buf[pos+4] = (float) (intbuf[4] >> interpolation_shifts::post2); + buf[pos+5] = (float) (intbuf[5] >> interpolation_shifts::post2); + buf[pos+6] = (float) (intbuf[6] >> interpolation_shifts::post2); + buf[pos+7] = (float) (intbuf[7] >> interpolation_shifts::post2); + } +} + +template +void InterpolatorsIF::interpolate2_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[8]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[5]; + bufQ1 = &intbuf[4]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[4]; + bufQ1 = &intbuf[5]; + } + + for (int pos = 0; pos < len - 7; pos += 8) + { + memset(intbuf, 0, 8*sizeof(qint32)); + + *bufI0 = (**it).m_real << interpolation_shifts::pre2; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre2; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre2; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post2); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post2); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post2); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post2); + buf[pos+4] = (float) (intbuf[4] >> interpolation_shifts::post2); + buf[pos+5] = (float) (intbuf[5] >> interpolation_shifts::post2); + buf[pos+6] = (float) (intbuf[6] >> interpolation_shifts::post2); + buf[pos+7] = (float) (intbuf[7] >> interpolation_shifts::post2); + } +} + +template +void InterpolatorsIF::interpolate4_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[8]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 7; pos += 8) + { + memset(intbuf, 0, 8*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre4; + *bufQ = (**it).m_imag << interpolation_shifts::pre4; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator4.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post4); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post4); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post4); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post4); + buf[pos+4] = (float) (intbuf[4] >> interpolation_shifts::post4); + buf[pos+5] = (float) (intbuf[5] >> interpolation_shifts::post4); + buf[pos+6] = (float) (intbuf[6] >> interpolation_shifts::post4); + buf[pos+7] = (float) (intbuf[7] >> interpolation_shifts::post4); + + ++(*it); + } +} + +template +void InterpolatorsIF::interpolate4_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[16]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[9]; + bufQ1 = &intbuf[8]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[8]; + bufQ1 = &intbuf[9]; + } + + for (int pos = 0; pos < len - 15; pos += 16) + { + memset(intbuf, 0, 16*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre4; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre4; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator4.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + + for (int i = 0; i < 16; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post4); + } + } +} + +template +void InterpolatorsIF::interpolate4_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[16]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[9]; + bufQ1 = &intbuf[8]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[8]; + bufQ1 = &intbuf[9]; + } + + for (int pos = 0; pos < len - 15; pos += 16) + { + memset(intbuf, 0, 16*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre4; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre4; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre4; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator4.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + + for (int i = 0; i < 16; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post4); + } + } +} + +template +void InterpolatorsIF::interpolate8_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[16]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 15; pos += 16) + { + memset(intbuf, 0, 16*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre8; + *bufQ = (**it).m_imag << interpolation_shifts::pre8; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator4.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator8.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator8.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator8.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post8); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post8); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post8); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post8); + buf[pos+4] = (float) (intbuf[4] >> interpolation_shifts::post8); + buf[pos+5] = (float) (intbuf[5] >> interpolation_shifts::post8); + buf[pos+6] = (float) (intbuf[6] >> interpolation_shifts::post8); + buf[pos+7] = (float) (intbuf[7] >> interpolation_shifts::post8); + buf[pos+8] = (float) (intbuf[8] >> interpolation_shifts::post8); + buf[pos+9] = (float) (intbuf[9] >> interpolation_shifts::post8); + buf[pos+10] = (float) (intbuf[10] >> interpolation_shifts::post8); + buf[pos+11] = (float) (intbuf[11] >> interpolation_shifts::post8); + buf[pos+12] = (float) (intbuf[12] >> interpolation_shifts::post8); + buf[pos+13] = (float) (intbuf[13] >> interpolation_shifts::post8); + buf[pos+14] = (float) (intbuf[14] >> interpolation_shifts::post8); + buf[pos+15] = (float) (intbuf[15] >> interpolation_shifts::post8); + + ++(*it); + } +} + +template +void InterpolatorsIF::interpolate8_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[32]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[17]; + bufQ1 = &intbuf[16]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[16]; + bufQ1 = &intbuf[17]; + } + + for (int pos = 0; pos < len - 31; pos += 32) + { + memset(intbuf, 0, 32*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre8; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre8; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator4.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator8.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator8.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator8.myInterpolateInf(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + + for (int i = 0; i < 32; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post8); + } + } +} + +template +void InterpolatorsIF::interpolate8_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[32]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[17]; + bufQ1 = &intbuf[16]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[16]; + bufQ1 = &intbuf[17]; + } + + for (int pos = 0; pos < len - 31; pos += 32) + { + memset(intbuf, 0, 32*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre8; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre8; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre8; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator4.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator8.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator8.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator8.myInterpolateSup(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + + for (int i = 0; i < 32; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post8); + } + } +} + +template +void InterpolatorsIF::interpolate16_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[32]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 31; pos += 32) + { + memset(intbuf, 0, 32*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre16; + *bufQ = (**it).m_imag << interpolation_shifts::pre16; + + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + m_interpolator4.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator8.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator8.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21]); + m_interpolator8.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + + m_interpolator16.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator16.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator16.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator16.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator16.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19]); + m_interpolator16.myInterpolate(&intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator16.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27]); + m_interpolator16.myInterpolate(&intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post16); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post16); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post16); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post16); + buf[pos+4] = (float) (intbuf[4] >> interpolation_shifts::post16); + buf[pos+5] = (float) (intbuf[5] >> interpolation_shifts::post16); + buf[pos+6] = (float) (intbuf[6] >> interpolation_shifts::post16); + buf[pos+7] = (float) (intbuf[7] >> interpolation_shifts::post16); + buf[pos+8] = (float) (intbuf[8] >> interpolation_shifts::post16); + buf[pos+9] = (float) (intbuf[9] >> interpolation_shifts::post16); + buf[pos+10] = (float) (intbuf[10] >> interpolation_shifts::post16); + buf[pos+11] = (float) (intbuf[11] >> interpolation_shifts::post16); + buf[pos+12] = (float) (intbuf[12] >> interpolation_shifts::post16); + buf[pos+13] = (float) (intbuf[13] >> interpolation_shifts::post16); + buf[pos+14] = (float) (intbuf[14] >> interpolation_shifts::post16); + buf[pos+15] = (float) (intbuf[15] >> interpolation_shifts::post16); + buf[pos+16] = (float) (intbuf[16] >> interpolation_shifts::post16); + buf[pos+17] = (float) (intbuf[17] >> interpolation_shifts::post16); + buf[pos+18] = (float) (intbuf[18] >> interpolation_shifts::post16); + buf[pos+19] = (float) (intbuf[19] >> interpolation_shifts::post16); + buf[pos+20] = (float) (intbuf[20] >> interpolation_shifts::post16); + buf[pos+21] = (float) (intbuf[21] >> interpolation_shifts::post16); + buf[pos+22] = (float) (intbuf[22] >> interpolation_shifts::post16); + buf[pos+23] = (float) (intbuf[23] >> interpolation_shifts::post16); + buf[pos+24] = (float) (intbuf[24] >> interpolation_shifts::post16); + buf[pos+25] = (float) (intbuf[25] >> interpolation_shifts::post16); + buf[pos+26] = (float) (intbuf[26] >> interpolation_shifts::post16); + buf[pos+27] = (float) (intbuf[27] >> interpolation_shifts::post16); + buf[pos+28] = (float) (intbuf[28] >> interpolation_shifts::post16); + buf[pos+29] = (float) (intbuf[29] >> interpolation_shifts::post16); + buf[pos+30] = (float) (intbuf[30] >> interpolation_shifts::post16); + buf[pos+31] = (float) (intbuf[31] >> interpolation_shifts::post16); + + ++(*it); + } +} + +template +void InterpolatorsIF::interpolate16_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[64]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[33]; + bufQ1 = &intbuf[32]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[32]; + bufQ1 = &intbuf[33]; + } + + for (int pos = 0; pos < len - 63; pos += 64) + { + memset(intbuf, 0, 64*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre16; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre16; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator4.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator8.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator8.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator8.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + + m_interpolator16.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator16.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator16.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator16.myInterpolateInf(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator16.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator16.myInterpolateInf(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator16.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator16.myInterpolateInf(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + + for (int i = 0; i < 64; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post16); + } + } +} + +template +void InterpolatorsIF::interpolate16_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[64]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[33]; + bufQ1 = &intbuf[32]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[32]; + bufQ1 = &intbuf[33]; + } + + for (int pos = 0; pos < len - 63; pos += 64) + { + memset(intbuf, 0, 64*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre16; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre16; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre16; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator4.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator8.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator8.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator8.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + + m_interpolator16.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator16.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator16.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator16.myInterpolateSup(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator16.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator16.myInterpolateSup(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator16.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator16.myInterpolateSup(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + + for (int i = 0; i < 64; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post16); + } + } +} + +template +void InterpolatorsIF::interpolate32_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[64]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 63; pos += 64) + { + memset(intbuf, 0, 64*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre32; + *bufQ = (**it).m_imag << interpolation_shifts::pre32; + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17]); + m_interpolator4.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + m_interpolator8.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator8.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41]); + m_interpolator8.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + + m_interpolator16.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator16.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator16.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21]); + m_interpolator16.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator16.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37]); + m_interpolator16.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator16.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53]); + m_interpolator16.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + + m_interpolator32.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator32.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator32.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator32.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator32.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19]); + m_interpolator32.myInterpolate(&intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator32.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27]); + m_interpolator32.myInterpolate(&intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator32.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35]); + m_interpolator32.myInterpolate(&intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator32.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43]); + m_interpolator32.myInterpolate(&intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator32.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51]); + m_interpolator32.myInterpolate(&intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator32.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59]); + m_interpolator32.myInterpolate(&intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post32); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post32); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post32); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post32); + buf[pos+4] = (float) (intbuf[4] >> interpolation_shifts::post32); + buf[pos+5] = (float) (intbuf[5] >> interpolation_shifts::post32); + buf[pos+6] = (float) (intbuf[6] >> interpolation_shifts::post32); + buf[pos+7] = (float) (intbuf[7] >> interpolation_shifts::post32); + buf[pos+8] = (float) (intbuf[8] >> interpolation_shifts::post32); + buf[pos+9] = (float) (intbuf[9] >> interpolation_shifts::post32); + buf[pos+10] = (float) (intbuf[10] >> interpolation_shifts::post32); + buf[pos+11] = (float) (intbuf[11] >> interpolation_shifts::post32); + buf[pos+12] = (float) (intbuf[12] >> interpolation_shifts::post32); + buf[pos+13] = (float) (intbuf[13] >> interpolation_shifts::post32); + buf[pos+14] = (float) (intbuf[14] >> interpolation_shifts::post32); + buf[pos+15] = (float) (intbuf[15] >> interpolation_shifts::post32); + buf[pos+16] = (float) (intbuf[16] >> interpolation_shifts::post32); + buf[pos+17] = (float) (intbuf[17] >> interpolation_shifts::post32); + buf[pos+18] = (float) (intbuf[18] >> interpolation_shifts::post32); + buf[pos+19] = (float) (intbuf[19] >> interpolation_shifts::post32); + buf[pos+20] = (float) (intbuf[20] >> interpolation_shifts::post32); + buf[pos+21] = (float) (intbuf[21] >> interpolation_shifts::post32); + buf[pos+22] = (float) (intbuf[22] >> interpolation_shifts::post32); + buf[pos+23] = (float) (intbuf[23] >> interpolation_shifts::post32); + buf[pos+24] = (float) (intbuf[24] >> interpolation_shifts::post32); + buf[pos+25] = (float) (intbuf[25] >> interpolation_shifts::post32); + buf[pos+26] = (float) (intbuf[26] >> interpolation_shifts::post32); + buf[pos+27] = (float) (intbuf[27] >> interpolation_shifts::post32); + buf[pos+28] = (float) (intbuf[28] >> interpolation_shifts::post32); + buf[pos+29] = (float) (intbuf[29] >> interpolation_shifts::post32); + buf[pos+30] = (float) (intbuf[30] >> interpolation_shifts::post32); + buf[pos+31] = (float) (intbuf[31] >> interpolation_shifts::post32); + buf[pos+32] = (float) (intbuf[32] >> interpolation_shifts::post32); + buf[pos+33] = (float) (intbuf[33] >> interpolation_shifts::post32); + buf[pos+34] = (float) (intbuf[34] >> interpolation_shifts::post32); + buf[pos+35] = (float) (intbuf[35] >> interpolation_shifts::post32); + buf[pos+36] = (float) (intbuf[36] >> interpolation_shifts::post32); + buf[pos+37] = (float) (intbuf[37] >> interpolation_shifts::post32); + buf[pos+38] = (float) (intbuf[38] >> interpolation_shifts::post32); + buf[pos+39] = (float) (intbuf[39] >> interpolation_shifts::post32); + buf[pos+40] = (float) (intbuf[40] >> interpolation_shifts::post32); + buf[pos+41] = (float) (intbuf[41] >> interpolation_shifts::post32); + buf[pos+42] = (float) (intbuf[42] >> interpolation_shifts::post32); + buf[pos+43] = (float) (intbuf[43] >> interpolation_shifts::post32); + buf[pos+44] = (float) (intbuf[44] >> interpolation_shifts::post32); + buf[pos+45] = (float) (intbuf[45] >> interpolation_shifts::post32); + buf[pos+46] = (float) (intbuf[46] >> interpolation_shifts::post32); + buf[pos+47] = (float) (intbuf[47] >> interpolation_shifts::post32); + buf[pos+48] = (float) (intbuf[48] >> interpolation_shifts::post32); + buf[pos+49] = (float) (intbuf[49] >> interpolation_shifts::post32); + buf[pos+50] = (float) (intbuf[50] >> interpolation_shifts::post32); + buf[pos+51] = (float) (intbuf[51] >> interpolation_shifts::post32); + buf[pos+52] = (float) (intbuf[52] >> interpolation_shifts::post32); + buf[pos+53] = (float) (intbuf[53] >> interpolation_shifts::post32); + buf[pos+54] = (float) (intbuf[54] >> interpolation_shifts::post32); + buf[pos+55] = (float) (intbuf[55] >> interpolation_shifts::post32); + buf[pos+56] = (float) (intbuf[56] >> interpolation_shifts::post32); + buf[pos+57] = (float) (intbuf[57] >> interpolation_shifts::post32); + buf[pos+58] = (float) (intbuf[58] >> interpolation_shifts::post32); + buf[pos+59] = (float) (intbuf[59] >> interpolation_shifts::post32); + buf[pos+60] = (float) (intbuf[60] >> interpolation_shifts::post32); + buf[pos+61] = (float) (intbuf[61] >> interpolation_shifts::post32); + buf[pos+62] = (float) (intbuf[62] >> interpolation_shifts::post32); + buf[pos+63] = (float) (intbuf[63] >> interpolation_shifts::post32); + + ++(*it); + } +} + +template +void InterpolatorsIF::interpolate32_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[128]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[65]; + bufQ1 = &intbuf[64]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[64]; + bufQ1 = &intbuf[65]; + } + + for (int pos = 0; pos < len - 127; pos += 128) + { + memset(intbuf, 0, 128*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre32; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre32; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator4.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator8.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator8.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator8.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + + m_interpolator16.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator16.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator16.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator16.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + m_interpolator16.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[68], &intbuf[69], &intbuf[72], &intbuf[73], &intbuf[76], &intbuf[77]); + m_interpolator16.myInterpolateInf(&intbuf[80], &intbuf[81], &intbuf[84], &intbuf[85], &intbuf[88], &intbuf[89], &intbuf[92], &intbuf[93]); + m_interpolator16.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[100], &intbuf[101], &intbuf[104], &intbuf[105], &intbuf[108], &intbuf[109]); + m_interpolator16.myInterpolateInf(&intbuf[112], &intbuf[113], &intbuf[116], &intbuf[117], &intbuf[120], &intbuf[121], &intbuf[124], &intbuf[125]); + + m_interpolator32.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator32.myInterpolateInf(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator32.myInterpolateInf(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator32.myInterpolateInf(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator32.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator32.myInterpolateInf(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator32.myInterpolateInf(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator32.myInterpolateInf(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + m_interpolator32.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[66], &intbuf[67], &intbuf[68], &intbuf[69], &intbuf[70], &intbuf[71]); + m_interpolator32.myInterpolateInf(&intbuf[72], &intbuf[73], &intbuf[74], &intbuf[75], &intbuf[76], &intbuf[77], &intbuf[78], &intbuf[79]); + m_interpolator32.myInterpolateInf(&intbuf[80], &intbuf[81], &intbuf[82], &intbuf[83], &intbuf[84], &intbuf[85], &intbuf[86], &intbuf[87]); + m_interpolator32.myInterpolateInf(&intbuf[88], &intbuf[89], &intbuf[90], &intbuf[91], &intbuf[92], &intbuf[93], &intbuf[94], &intbuf[95]); + m_interpolator32.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[98], &intbuf[99], &intbuf[100], &intbuf[101], &intbuf[102], &intbuf[103]); + m_interpolator32.myInterpolateInf(&intbuf[104], &intbuf[105], &intbuf[106], &intbuf[107], &intbuf[108], &intbuf[109], &intbuf[110], &intbuf[111]); + m_interpolator32.myInterpolateInf(&intbuf[112], &intbuf[113], &intbuf[114], &intbuf[115], &intbuf[116], &intbuf[117], &intbuf[118], &intbuf[119]); + m_interpolator32.myInterpolateInf(&intbuf[120], &intbuf[121], &intbuf[122], &intbuf[123], &intbuf[124], &intbuf[125], &intbuf[126], &intbuf[127]); + + for (int i = 0; i < 128; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post32); + } + } +} + +template +void InterpolatorsIF::interpolate32_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[128]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[65]; + bufQ1 = &intbuf[64]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[64]; + bufQ1 = &intbuf[65]; + } + + for (int pos = 0; pos < len - 127; pos += 128) + { + memset(intbuf, 0, 128*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre32; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre32; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre32; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator4.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator8.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator8.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator8.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + + m_interpolator16.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5], &intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator16.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21], &intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator16.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37], &intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator16.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53], &intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + m_interpolator16.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[68], &intbuf[69], &intbuf[72], &intbuf[73], &intbuf[76], &intbuf[77]); + m_interpolator16.myInterpolateSup(&intbuf[80], &intbuf[81], &intbuf[84], &intbuf[85], &intbuf[88], &intbuf[89], &intbuf[92], &intbuf[93]); + m_interpolator16.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[100], &intbuf[101], &intbuf[104], &intbuf[105], &intbuf[108], &intbuf[109]); + m_interpolator16.myInterpolateSup(&intbuf[112], &intbuf[113], &intbuf[116], &intbuf[117], &intbuf[120], &intbuf[121], &intbuf[124], &intbuf[125]); + + m_interpolator32.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3], &intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator32.myInterpolateSup(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11], &intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator32.myInterpolateSup(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19], &intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator32.myInterpolateSup(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27], &intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator32.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35], &intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator32.myInterpolateSup(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43], &intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator32.myInterpolateSup(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51], &intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator32.myInterpolateSup(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59], &intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + m_interpolator32.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[66], &intbuf[67], &intbuf[68], &intbuf[69], &intbuf[70], &intbuf[71]); + m_interpolator32.myInterpolateSup(&intbuf[72], &intbuf[73], &intbuf[74], &intbuf[75], &intbuf[76], &intbuf[77], &intbuf[78], &intbuf[79]); + m_interpolator32.myInterpolateSup(&intbuf[80], &intbuf[81], &intbuf[82], &intbuf[83], &intbuf[84], &intbuf[85], &intbuf[86], &intbuf[87]); + m_interpolator32.myInterpolateSup(&intbuf[88], &intbuf[89], &intbuf[90], &intbuf[91], &intbuf[92], &intbuf[93], &intbuf[94], &intbuf[95]); + m_interpolator32.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[98], &intbuf[99], &intbuf[100], &intbuf[101], &intbuf[102], &intbuf[103]); + m_interpolator32.myInterpolateSup(&intbuf[104], &intbuf[105], &intbuf[106], &intbuf[107], &intbuf[108], &intbuf[109], &intbuf[110], &intbuf[111]); + m_interpolator32.myInterpolateSup(&intbuf[112], &intbuf[113], &intbuf[114], &intbuf[115], &intbuf[116], &intbuf[117], &intbuf[118], &intbuf[119]); + m_interpolator32.myInterpolateSup(&intbuf[120], &intbuf[121], &intbuf[122], &intbuf[123], &intbuf[124], &intbuf[125], &intbuf[126], &intbuf[127]); + + for (int i = 0; i < 128; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post32); + } + } +} + +template +void InterpolatorsIF::interpolate64_cen(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[128]; + qint32 *bufI, *bufQ; + + if (invertIQ) + { + bufI = &intbuf[1]; + bufQ = &intbuf[0]; + } + else + { + bufI = &intbuf[0]; + bufQ = &intbuf[1]; + } + + for (int pos = 0; pos < len - 127; pos += 128) + { + memset(intbuf, 0, 128*sizeof(qint32)); + *bufI = (**it).m_real << interpolation_shifts::pre64; + *bufQ = (**it).m_imag << interpolation_shifts::pre64; + m_interpolator2.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[64], &intbuf[65]); + + m_interpolator4.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33]); + m_interpolator4.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + + m_interpolator8.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17]); + m_interpolator8.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator8.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81]); + m_interpolator8.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + + m_interpolator16.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9]); + m_interpolator16.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator16.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41]); + m_interpolator16.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator16.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73]); + m_interpolator16.myInterpolate(&intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator16.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105]); + m_interpolator16.myInterpolate(&intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + + m_interpolator32.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[4], &intbuf[5]); + m_interpolator32.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[12], &intbuf[13]); + m_interpolator32.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[20], &intbuf[21]); + m_interpolator32.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[28], &intbuf[29]); + m_interpolator32.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[36], &intbuf[37]); + m_interpolator32.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[44], &intbuf[45]); + m_interpolator32.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[52], &intbuf[53]); + m_interpolator32.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[60], &intbuf[61]); + m_interpolator32.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[68], &intbuf[69]); + m_interpolator32.myInterpolate(&intbuf[72], &intbuf[73], &intbuf[76], &intbuf[77]); + m_interpolator32.myInterpolate(&intbuf[80], &intbuf[81], &intbuf[84], &intbuf[85]); + m_interpolator32.myInterpolate(&intbuf[88], &intbuf[89], &intbuf[92], &intbuf[93]); + m_interpolator32.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[100], &intbuf[101]); + m_interpolator32.myInterpolate(&intbuf[104], &intbuf[105], &intbuf[108], &intbuf[109]); + m_interpolator32.myInterpolate(&intbuf[112], &intbuf[113], &intbuf[116], &intbuf[117]); + m_interpolator32.myInterpolate(&intbuf[120], &intbuf[121], &intbuf[124], &intbuf[125]); + + m_interpolator64.myInterpolate(&intbuf[0], &intbuf[1], &intbuf[2], &intbuf[3]); + m_interpolator64.myInterpolate(&intbuf[4], &intbuf[5], &intbuf[6], &intbuf[7]); + m_interpolator64.myInterpolate(&intbuf[8], &intbuf[9], &intbuf[10], &intbuf[11]); + m_interpolator64.myInterpolate(&intbuf[12], &intbuf[13], &intbuf[14], &intbuf[15]); + m_interpolator64.myInterpolate(&intbuf[16], &intbuf[17], &intbuf[18], &intbuf[19]); + m_interpolator64.myInterpolate(&intbuf[20], &intbuf[21], &intbuf[22], &intbuf[23]); + m_interpolator64.myInterpolate(&intbuf[24], &intbuf[25], &intbuf[26], &intbuf[27]); + m_interpolator64.myInterpolate(&intbuf[28], &intbuf[29], &intbuf[30], &intbuf[31]); + m_interpolator64.myInterpolate(&intbuf[32], &intbuf[33], &intbuf[34], &intbuf[35]); + m_interpolator64.myInterpolate(&intbuf[36], &intbuf[37], &intbuf[38], &intbuf[39]); + m_interpolator64.myInterpolate(&intbuf[40], &intbuf[41], &intbuf[42], &intbuf[43]); + m_interpolator64.myInterpolate(&intbuf[44], &intbuf[45], &intbuf[46], &intbuf[47]); + m_interpolator64.myInterpolate(&intbuf[48], &intbuf[49], &intbuf[50], &intbuf[51]); + m_interpolator64.myInterpolate(&intbuf[52], &intbuf[53], &intbuf[54], &intbuf[55]); + m_interpolator64.myInterpolate(&intbuf[56], &intbuf[57], &intbuf[58], &intbuf[59]); + m_interpolator64.myInterpolate(&intbuf[60], &intbuf[61], &intbuf[62], &intbuf[63]); + m_interpolator64.myInterpolate(&intbuf[64], &intbuf[65], &intbuf[66], &intbuf[67]); + m_interpolator64.myInterpolate(&intbuf[68], &intbuf[69], &intbuf[70], &intbuf[71]); + m_interpolator64.myInterpolate(&intbuf[72], &intbuf[73], &intbuf[74], &intbuf[75]); + m_interpolator64.myInterpolate(&intbuf[76], &intbuf[77], &intbuf[78], &intbuf[79]); + m_interpolator64.myInterpolate(&intbuf[80], &intbuf[81], &intbuf[82], &intbuf[83]); + m_interpolator64.myInterpolate(&intbuf[84], &intbuf[85], &intbuf[86], &intbuf[87]); + m_interpolator64.myInterpolate(&intbuf[88], &intbuf[89], &intbuf[90], &intbuf[91]); + m_interpolator64.myInterpolate(&intbuf[92], &intbuf[93], &intbuf[94], &intbuf[95]); + m_interpolator64.myInterpolate(&intbuf[96], &intbuf[97], &intbuf[98], &intbuf[99]); + m_interpolator64.myInterpolate(&intbuf[100], &intbuf[101], &intbuf[102], &intbuf[103]); + m_interpolator64.myInterpolate(&intbuf[104], &intbuf[105], &intbuf[106], &intbuf[107]); + m_interpolator64.myInterpolate(&intbuf[108], &intbuf[109], &intbuf[110], &intbuf[111]); + m_interpolator64.myInterpolate(&intbuf[112], &intbuf[113], &intbuf[114], &intbuf[115]); + m_interpolator64.myInterpolate(&intbuf[116], &intbuf[117], &intbuf[118], &intbuf[119]); + m_interpolator64.myInterpolate(&intbuf[120], &intbuf[121], &intbuf[122], &intbuf[123]); + m_interpolator64.myInterpolate(&intbuf[124], &intbuf[125], &intbuf[126], &intbuf[127]); + + buf[pos+0] = (float) (intbuf[0] >> interpolation_shifts::post64); + buf[pos+1] = (float) (intbuf[1] >> interpolation_shifts::post64); + buf[pos+2] = (float) (intbuf[2] >> interpolation_shifts::post64); + buf[pos+3] = (float) (intbuf[3] >> interpolation_shifts::post64); + buf[pos+4] = (float) (intbuf[4] >> interpolation_shifts::post64); + buf[pos+5] = (float) (intbuf[5] >> interpolation_shifts::post64); + buf[pos+6] = (float) (intbuf[6] >> interpolation_shifts::post64); + buf[pos+7] = (float) (intbuf[7] >> interpolation_shifts::post64); + buf[pos+8] = (float) (intbuf[8] >> interpolation_shifts::post64); + buf[pos+9] = (float) (intbuf[9] >> interpolation_shifts::post64); + buf[pos+10] = (float) (intbuf[10] >> interpolation_shifts::post64); + buf[pos+11] = (float) (intbuf[11] >> interpolation_shifts::post64); + buf[pos+12] = (float) (intbuf[12] >> interpolation_shifts::post64); + buf[pos+13] = (float) (intbuf[13] >> interpolation_shifts::post64); + buf[pos+14] = (float) (intbuf[14] >> interpolation_shifts::post64); + buf[pos+15] = (float) (intbuf[15] >> interpolation_shifts::post64); + buf[pos+16] = (float) (intbuf[16] >> interpolation_shifts::post64); + buf[pos+17] = (float) (intbuf[17] >> interpolation_shifts::post64); + buf[pos+18] = (float) (intbuf[18] >> interpolation_shifts::post64); + buf[pos+19] = (float) (intbuf[19] >> interpolation_shifts::post64); + buf[pos+20] = (float) (intbuf[20] >> interpolation_shifts::post64); + buf[pos+21] = (float) (intbuf[21] >> interpolation_shifts::post64); + buf[pos+22] = (float) (intbuf[22] >> interpolation_shifts::post64); + buf[pos+23] = (float) (intbuf[23] >> interpolation_shifts::post64); + buf[pos+24] = (float) (intbuf[24] >> interpolation_shifts::post64); + buf[pos+25] = (float) (intbuf[25] >> interpolation_shifts::post64); + buf[pos+26] = (float) (intbuf[26] >> interpolation_shifts::post64); + buf[pos+27] = (float) (intbuf[27] >> interpolation_shifts::post64); + buf[pos+28] = (float) (intbuf[28] >> interpolation_shifts::post64); + buf[pos+29] = (float) (intbuf[29] >> interpolation_shifts::post64); + buf[pos+30] = (float) (intbuf[30] >> interpolation_shifts::post64); + buf[pos+31] = (float) (intbuf[31] >> interpolation_shifts::post64); + buf[pos+32] = (float) (intbuf[32] >> interpolation_shifts::post64); + buf[pos+33] = (float) (intbuf[33] >> interpolation_shifts::post64); + buf[pos+34] = (float) (intbuf[34] >> interpolation_shifts::post64); + buf[pos+35] = (float) (intbuf[35] >> interpolation_shifts::post64); + buf[pos+36] = (float) (intbuf[36] >> interpolation_shifts::post64); + buf[pos+37] = (float) (intbuf[37] >> interpolation_shifts::post64); + buf[pos+38] = (float) (intbuf[38] >> interpolation_shifts::post64); + buf[pos+39] = (float) (intbuf[39] >> interpolation_shifts::post64); + buf[pos+40] = (float) (intbuf[40] >> interpolation_shifts::post64); + buf[pos+41] = (float) (intbuf[41] >> interpolation_shifts::post64); + buf[pos+42] = (float) (intbuf[42] >> interpolation_shifts::post64); + buf[pos+43] = (float) (intbuf[43] >> interpolation_shifts::post64); + buf[pos+44] = (float) (intbuf[44] >> interpolation_shifts::post64); + buf[pos+45] = (float) (intbuf[45] >> interpolation_shifts::post64); + buf[pos+46] = (float) (intbuf[46] >> interpolation_shifts::post64); + buf[pos+47] = (float) (intbuf[47] >> interpolation_shifts::post64); + buf[pos+48] = (float) (intbuf[48] >> interpolation_shifts::post64); + buf[pos+49] = (float) (intbuf[49] >> interpolation_shifts::post64); + buf[pos+50] = (float) (intbuf[50] >> interpolation_shifts::post64); + buf[pos+51] = (float) (intbuf[51] >> interpolation_shifts::post64); + buf[pos+52] = (float) (intbuf[52] >> interpolation_shifts::post64); + buf[pos+53] = (float) (intbuf[53] >> interpolation_shifts::post64); + buf[pos+54] = (float) (intbuf[54] >> interpolation_shifts::post64); + buf[pos+55] = (float) (intbuf[55] >> interpolation_shifts::post64); + buf[pos+56] = (float) (intbuf[56] >> interpolation_shifts::post64); + buf[pos+57] = (float) (intbuf[57] >> interpolation_shifts::post64); + buf[pos+58] = (float) (intbuf[58] >> interpolation_shifts::post64); + buf[pos+59] = (float) (intbuf[59] >> interpolation_shifts::post64); + buf[pos+60] = (float) (intbuf[60] >> interpolation_shifts::post64); + buf[pos+61] = (float) (intbuf[61] >> interpolation_shifts::post64); + buf[pos+62] = (float) (intbuf[62] >> interpolation_shifts::post64); + buf[pos+63] = (float) (intbuf[63] >> interpolation_shifts::post64); + buf[pos+64] = (float) (intbuf[64] >> interpolation_shifts::post64); + buf[pos+65] = (float) (intbuf[65] >> interpolation_shifts::post64); + buf[pos+66] = (float) (intbuf[66] >> interpolation_shifts::post64); + buf[pos+67] = (float) (intbuf[67] >> interpolation_shifts::post64); + buf[pos+68] = (float) (intbuf[68] >> interpolation_shifts::post64); + buf[pos+69] = (float) (intbuf[69] >> interpolation_shifts::post64); + buf[pos+70] = (float) (intbuf[70] >> interpolation_shifts::post64); + buf[pos+71] = (float) (intbuf[71] >> interpolation_shifts::post64); + buf[pos+72] = (float) (intbuf[72] >> interpolation_shifts::post64); + buf[pos+73] = (float) (intbuf[73] >> interpolation_shifts::post64); + buf[pos+74] = (float) (intbuf[74] >> interpolation_shifts::post64); + buf[pos+75] = (float) (intbuf[75] >> interpolation_shifts::post64); + buf[pos+76] = (float) (intbuf[76] >> interpolation_shifts::post64); + buf[pos+77] = (float) (intbuf[77] >> interpolation_shifts::post64); + buf[pos+78] = (float) (intbuf[78] >> interpolation_shifts::post64); + buf[pos+79] = (float) (intbuf[79] >> interpolation_shifts::post64); + buf[pos+80] = (float) (intbuf[80] >> interpolation_shifts::post64); + buf[pos+81] = (float) (intbuf[81] >> interpolation_shifts::post64); + buf[pos+82] = (float) (intbuf[82] >> interpolation_shifts::post64); + buf[pos+83] = (float) (intbuf[83] >> interpolation_shifts::post64); + buf[pos+84] = (float) (intbuf[84] >> interpolation_shifts::post64); + buf[pos+85] = (float) (intbuf[85] >> interpolation_shifts::post64); + buf[pos+86] = (float) (intbuf[86] >> interpolation_shifts::post64); + buf[pos+87] = (float) (intbuf[87] >> interpolation_shifts::post64); + buf[pos+88] = (float) (intbuf[88] >> interpolation_shifts::post64); + buf[pos+89] = (float) (intbuf[89] >> interpolation_shifts::post64); + buf[pos+90] = (float) (intbuf[90] >> interpolation_shifts::post64); + buf[pos+91] = (float) (intbuf[91] >> interpolation_shifts::post64); + buf[pos+92] = (float) (intbuf[92] >> interpolation_shifts::post64); + buf[pos+93] = (float) (intbuf[93] >> interpolation_shifts::post64); + buf[pos+94] = (float) (intbuf[94] >> interpolation_shifts::post64); + buf[pos+95] = (float) (intbuf[95] >> interpolation_shifts::post64); + buf[pos+96] = (float) (intbuf[96] >> interpolation_shifts::post64); + buf[pos+97] = (float) (intbuf[97] >> interpolation_shifts::post64); + buf[pos+98] = (float) (intbuf[98] >> interpolation_shifts::post64); + buf[pos+99] = (float) (intbuf[99] >> interpolation_shifts::post64); + buf[pos+100] = (float) (intbuf[100] >> interpolation_shifts::post64); + buf[pos+101] = (float) (intbuf[101] >> interpolation_shifts::post64); + buf[pos+102] = (float) (intbuf[102] >> interpolation_shifts::post64); + buf[pos+103] = (float) (intbuf[103] >> interpolation_shifts::post64); + buf[pos+104] = (float) (intbuf[104] >> interpolation_shifts::post64); + buf[pos+105] = (float) (intbuf[105] >> interpolation_shifts::post64); + buf[pos+106] = (float) (intbuf[106] >> interpolation_shifts::post64); + buf[pos+107] = (float) (intbuf[107] >> interpolation_shifts::post64); + buf[pos+108] = (float) (intbuf[108] >> interpolation_shifts::post64); + buf[pos+109] = (float) (intbuf[109] >> interpolation_shifts::post64); + buf[pos+110] = (float) (intbuf[110] >> interpolation_shifts::post64); + buf[pos+111] = (float) (intbuf[111] >> interpolation_shifts::post64); + buf[pos+112] = (float) (intbuf[112] >> interpolation_shifts::post64); + buf[pos+113] = (float) (intbuf[113] >> interpolation_shifts::post64); + buf[pos+114] = (float) (intbuf[114] >> interpolation_shifts::post64); + buf[pos+115] = (float) (intbuf[115] >> interpolation_shifts::post64); + buf[pos+116] = (float) (intbuf[116] >> interpolation_shifts::post64); + buf[pos+117] = (float) (intbuf[117] >> interpolation_shifts::post64); + buf[pos+118] = (float) (intbuf[118] >> interpolation_shifts::post64); + buf[pos+119] = (float) (intbuf[119] >> interpolation_shifts::post64); + buf[pos+120] = (float) (intbuf[120] >> interpolation_shifts::post64); + buf[pos+121] = (float) (intbuf[121] >> interpolation_shifts::post64); + buf[pos+122] = (float) (intbuf[122] >> interpolation_shifts::post64); + buf[pos+123] = (float) (intbuf[123] >> interpolation_shifts::post64); + buf[pos+124] = (float) (intbuf[124] >> interpolation_shifts::post64); + buf[pos+125] = (float) (intbuf[125] >> interpolation_shifts::post64); + buf[pos+126] = (float) (intbuf[126] >> interpolation_shifts::post64); + buf[pos+127] = (float) (intbuf[127] >> interpolation_shifts::post64); + + ++(*it); + } +} + +template +void InterpolatorsIF::interpolate64_inf(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[256]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[129]; + bufQ1 = &intbuf[128]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[128]; + bufQ1 = &intbuf[129]; + } + + for (int pos = 0; pos < len - 255; pos += 256) + { + memset(intbuf, 0, 256*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre64; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre64; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + + m_interpolator2.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[64], &intbuf[65], &intbuf[128], &intbuf[129], &intbuf[192], &intbuf[193]); + + m_interpolator4.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + m_interpolator4.myInterpolateInf(&intbuf[128], &intbuf[129], &intbuf[160], &intbuf[161], &intbuf[192], &intbuf[193], &intbuf[224], &intbuf[225]); + + m_interpolator8.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator8.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + m_interpolator8.myInterpolateSup(&intbuf[128], &intbuf[129], &intbuf[144], &intbuf[145], &intbuf[160], &intbuf[161], &intbuf[176], &intbuf[177]); + m_interpolator8.myInterpolateSup(&intbuf[192], &intbuf[193], &intbuf[208], &intbuf[209], &intbuf[224], &intbuf[225], &intbuf[240], &intbuf[241]); + + m_interpolator16.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator16.myInterpolateInf(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator16.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator16.myInterpolateInf(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + m_interpolator16.myInterpolateInf(&intbuf[128], &intbuf[129], &intbuf[136], &intbuf[137], &intbuf[144], &intbuf[145], &intbuf[152], &intbuf[153]); + m_interpolator16.myInterpolateInf(&intbuf[160], &intbuf[161], &intbuf[168], &intbuf[169], &intbuf[176], &intbuf[177], &intbuf[184], &intbuf[185]); + m_interpolator16.myInterpolateInf(&intbuf[192], &intbuf[193], &intbuf[200], &intbuf[201], &intbuf[208], &intbuf[209], &intbuf[216], &intbuf[217]); + m_interpolator16.myInterpolateInf(&intbuf[224], &intbuf[225], &intbuf[232], &intbuf[233], &intbuf[240], &intbuf[241], &intbuf[248], &intbuf[249]); + + for (int i = 0; i < 16; i++) { + m_interpolator32.myInterpolateSup( + &intbuf[16*i+0], + &intbuf[16*i+1], + &intbuf[16*i+4], + &intbuf[16*i+5], + &intbuf[16*i+8], + &intbuf[16*i+9], + &intbuf[16*i+12], + &intbuf[16*i+13]); + } + + for (int i = 0; i < 32; i++) { + m_interpolator64.myInterpolateInf( + &intbuf[8*i+0], + &intbuf[8*i+1], + &intbuf[8*i+2], + &intbuf[8*i+3], + &intbuf[8*i+4], + &intbuf[8*i+5], + &intbuf[8*i+6], + &intbuf[8*i+7]); + } + + for (int i = 0; i < 256; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post64); + } + } +} + +template +void InterpolatorsIF::interpolate64_sup(SampleVector::iterator* it, float* buf, qint32 len, bool invertIQ) +{ + qint32 intbuf[256]; + qint32 *bufI0, *bufQ0, *bufI1, *bufQ1; + + if (invertIQ) + { + bufI0 = &intbuf[1]; + bufQ0 = &intbuf[0]; + bufI1 = &intbuf[129]; + bufQ1 = &intbuf[128]; + } + else + { + bufI0 = &intbuf[0]; + bufQ0 = &intbuf[1]; + bufI1 = &intbuf[128]; + bufQ1 = &intbuf[129]; + } + + for (int pos = 0; pos < len - 255; pos += 256) + { + memset(intbuf, 0, 256*sizeof(qint32)); + *bufI0 = (**it).m_real << interpolation_shifts::pre64; + *bufQ0 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + *bufI1 = (**it).m_real << interpolation_shifts::pre64; + *bufQ1 = (**it).m_imag << interpolation_shifts::pre64; + ++(*it); + + m_interpolator2.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[64], &intbuf[65], &intbuf[128], &intbuf[129], &intbuf[192], &intbuf[193]); + + m_interpolator4.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[32], &intbuf[33], &intbuf[64], &intbuf[65], &intbuf[96], &intbuf[97]); + m_interpolator4.myInterpolateSup(&intbuf[128], &intbuf[129], &intbuf[160], &intbuf[161], &intbuf[192], &intbuf[193], &intbuf[224], &intbuf[225]); + + m_interpolator8.myInterpolateInf(&intbuf[0], &intbuf[1], &intbuf[16], &intbuf[17], &intbuf[32], &intbuf[33], &intbuf[48], &intbuf[49]); + m_interpolator8.myInterpolateInf(&intbuf[64], &intbuf[65], &intbuf[80], &intbuf[81], &intbuf[96], &intbuf[97], &intbuf[112], &intbuf[113]); + m_interpolator8.myInterpolateInf(&intbuf[128], &intbuf[129], &intbuf[144], &intbuf[145], &intbuf[160], &intbuf[161], &intbuf[176], &intbuf[177]); + m_interpolator8.myInterpolateInf(&intbuf[192], &intbuf[193], &intbuf[208], &intbuf[209], &intbuf[224], &intbuf[225], &intbuf[240], &intbuf[241]); + + m_interpolator16.myInterpolateSup(&intbuf[0], &intbuf[1], &intbuf[8], &intbuf[9], &intbuf[16], &intbuf[17], &intbuf[24], &intbuf[25]); + m_interpolator16.myInterpolateSup(&intbuf[32], &intbuf[33], &intbuf[40], &intbuf[41], &intbuf[48], &intbuf[49], &intbuf[56], &intbuf[57]); + m_interpolator16.myInterpolateSup(&intbuf[64], &intbuf[65], &intbuf[72], &intbuf[73], &intbuf[80], &intbuf[81], &intbuf[88], &intbuf[89]); + m_interpolator16.myInterpolateSup(&intbuf[96], &intbuf[97], &intbuf[104], &intbuf[105], &intbuf[112], &intbuf[113], &intbuf[120], &intbuf[121]); + m_interpolator16.myInterpolateSup(&intbuf[128], &intbuf[129], &intbuf[136], &intbuf[137], &intbuf[144], &intbuf[145], &intbuf[152], &intbuf[153]); + m_interpolator16.myInterpolateSup(&intbuf[160], &intbuf[161], &intbuf[168], &intbuf[169], &intbuf[176], &intbuf[177], &intbuf[184], &intbuf[185]); + m_interpolator16.myInterpolateSup(&intbuf[192], &intbuf[193], &intbuf[200], &intbuf[201], &intbuf[208], &intbuf[209], &intbuf[216], &intbuf[217]); + m_interpolator16.myInterpolateSup(&intbuf[224], &intbuf[225], &intbuf[232], &intbuf[233], &intbuf[240], &intbuf[241], &intbuf[248], &intbuf[249]); + + for (int i = 0; i < 16; i++) { + m_interpolator32.myInterpolateInf( + &intbuf[16*i+0], + &intbuf[16*i+1], + &intbuf[16*i+4], + &intbuf[16*i+5], + &intbuf[16*i+8], + &intbuf[16*i+9], + &intbuf[16*i+12], + &intbuf[16*i+13]); + } + + for (int i = 0; i < 32; i++) { + m_interpolator64.myInterpolateSup( + &intbuf[8*i+0], + &intbuf[8*i+1], + &intbuf[8*i+2], + &intbuf[8*i+3], + &intbuf[8*i+4], + &intbuf[8*i+5], + &intbuf[8*i+6], + &intbuf[8*i+7]); + } + + for (int i = 0; i < 256; i++) { + buf[pos+i] = (float) (intbuf[i] >> interpolation_shifts::post64); + } + } +} + +#endif // INCLUDE_SDRBASE_DSP_INTERPOLATORSIF_H_ diff --git a/android/app/src/main/cpp/dsp/inthalfbandfilter.h b/android/app/src/main/cpp/dsp/inthalfbandfilter.h new file mode 100644 index 0000000..1f698fe --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfilter.h @@ -0,0 +1,917 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_H +#define INCLUDE_INTHALFBANDFILTER_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilter { +public: + IntHalfbandFilter() : + m_ptr(0), + m_state(0) + { + for (int i = 0; i < HBFIRFilterTraits::hbOrder + 1; i++) + { + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + } + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + m_samples[m_ptr][0] = sample->real(); + m_samples[m_ptr][1] = sample->imag(); + + switch(m_state) + { + case 0: + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 1; + + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + m_samples[m_ptr][0] = 0; + m_samples[m_ptr][1] = 0; + + // save result + doFIR(SampleOut); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + + // save result + doFIR(SampleOut); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + +// bool workDecimateCenter(qint32 *x, qint32 *y) +// { +// // insert sample into ring-buffer +// m_samples[m_ptr][0] = *x; +// m_samples[m_ptr][1] = *y; +// +// switch(m_state) +// { +// case 0: +// // advance write-pointer +// m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); +// +// // next state +// m_state = 1; +// +// // tell caller we don't have a new sample +// return false; +// +// default: +// // save result +// doFIR(x, y); +// +// // advance write-pointer +// m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); +// +// // next state +// m_state = 0; +// +// // tell caller we have a new sample +// return true; +// } +// } + + // downsample by 2, return edges of spectrum rotated into center - unused +// bool workDecimateFullRotate(Sample* sample) +// { +// switch(m_state) +// { +// case 0: +// // insert sample into ring-buffer +// m_samples[m_ptr][0] = sample->real(); +// m_samples[m_ptr][1] = sample->imag(); +// +// // advance write-pointer +// m_ptr = (m_ptr + HB_FILTERORDER) % (HB_FILTERORDER + 1); +// +// // next state +// m_state = 1; +// +// // tell caller we don't have a new sample +// return false; +// +// default: +// // insert sample into ring-buffer +// m_samples[m_ptr][0] = -sample->real(); +// m_samples[m_ptr][1] = sample->imag(); +// +// // save result +// doFIR(sample); +// +// // advance write-pointer +// m_ptr = (m_ptr + HB_FILTERORDER) % (HB_FILTERORDER + 1); +// +// // next state +// m_state = 0; +// +// // tell caller we have a new sample +// return true; +// } +// } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + m_samples[m_ptr][0] = -sample->imag(); + m_samples[m_ptr][1] = sample->real(); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 1; + + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + m_samples[m_ptr][0] = -sample->real(); + m_samples[m_ptr][1] = -sample->imag(); + + // save result + doFIR(sample); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 2; + + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sample->imag(); + m_samples[m_ptr][1] = -sample->real(); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 3; + + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sample->real(); + m_samples[m_ptr][1] = sample->imag(); + + // save result + doFIR(sample); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + m_samples[m_ptr][0] = 0; + m_samples[m_ptr][1] = 0; + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + m_samples[m_ptr][0] = 0; + m_samples[m_ptr][1] = 0; + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sample->imag(); + m_samples[m_ptr][1] = -sample->real(); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 1; + + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + m_samples[m_ptr][0] = -sample->real(); + m_samples[m_ptr][1] = -sample->imag(); + + // save result + doFIR(sample); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 2; + + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + m_samples[m_ptr][0] = -sample->imag(); + m_samples[m_ptr][1] = sample->real(); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 3; + + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sample->real(); + m_samples[m_ptr][1] = sample->imag(); + + // save result + doFIR(sample); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + m_samples[m_ptr][0] = 0; + m_samples[m_ptr][1] = 0; + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + m_samples[m_ptr][0] = 0; + m_samples[m_ptr][1] = 0; + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + m_ptr = (m_ptr + HBFIRFilterTraits::hbOrder) % (HBFIRFilterTraits::hbOrder + 1); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + m_samples[m_ptr][0] = sample1->real(); + m_samples[m_ptr][1] = sample1->imag(); + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + + m_samples[m_ptr][0] = sample2->real(); + m_samples[m_ptr][1] = sample2->imag(); + + doFIR(sample2); + + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + } + + void myDecimate(qint32 x1, qint32 y1, qint32 *x2, qint32 *y2) + { + m_samples[m_ptr][0] = x1; + m_samples[m_ptr][1] = y1; + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + + m_samples[m_ptr][0] = *x2; + m_samples[m_ptr][1] = *y2; + + doFIR(x2, y2); + + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + m_samples[m_ptr][0] = sample1->real(); + m_samples[m_ptr][1] = sample1->imag(); + + doFIR(sample1); + + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + + m_samples[m_ptr][0] = 0; + m_samples[m_ptr][1] = 0; + + doFIR(sample2); + + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + + doFIR(x1, y1); + + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + + m_samples[m_ptr][0] = 0; + m_samples[m_ptr][1] = 0; + + doFIR(x2, y2); + + m_ptr = HBFIRFilterTraits::hbMod[m_ptr + 2 - 1]; + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + AccuType m_samples[HBFIRFilterTraits::hbOrder + 1][2]; // Valgrind optim (from qint16) + qint16 m_ptr; + int m_state; + + void doFIR(Sample* sample) + { + // init read-pointer + int a = HBFIRFilterTraits::hbMod[m_ptr + 2 + 1]; // 0 + 1 + int b = HBFIRFilterTraits::hbMod[m_ptr + 2 - 2]; //-1 - 1 + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + // do multiply-accumulate + //qint32 iTmp = m_samples[a][0] + m_samples[b][0]; // Valgrind optim + //qint32 qTmp = m_samples[a][1] + m_samples[b][1]; // Valgrind optim + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + + // update read-pointer + a = HBFIRFilterTraits::hbMod[a + 2 + 2]; + b = HBFIRFilterTraits::hbMod[b + 2 - 2]; + } + + a = HBFIRFilterTraits::hbMod[a + 2 - 1]; + + iAcc += ((qint32)m_samples[a][0] + 1) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((qint32)m_samples[a][1] + 1) << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(Sample* sample) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doFIR(qint32 *x, qint32 *y) + { + // Coefficients. This is a sinc function: + // Half of the half of coefficients are stored because: + // - half of the coefficients are 0 + // - there is a symmertry around the central 0.5 coefficient (not stored either) + // There are actually order+1 coefficients + + // init read-pointer + int a = HBFIRFilterTraits::hbMod[m_ptr + 2 + 1]; // 0 + 1 + int b = HBFIRFilterTraits::hbMod[m_ptr + 2 - 2]; //-1 - 1 + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + // do multiply-accumulate + //qint32 iTmp = m_samples[a][0] + m_samples[b][0]; // Valgrind optim + //qint32 qTmp = m_samples[a][1] + m_samples[b][1]; // Valgrind optim + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + + // update read-pointer + a = HBFIRFilterTraits::hbMod[a + 2 + 2]; + b = HBFIRFilterTraits::hbMod[b + 2 - 2]; + } + + a = HBFIRFilterTraits::hbMod[a + 2 - 1]; + + iAcc += ((qint32)m_samples[a][0] + 1) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((qint32)m_samples[a][1] + 1) << (HBFIRFilterTraits::hbShift - 1); + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + +}; + +//template +//IntHalfbandFilter::IntHalfbandFilter() +//{ +// for (int i = 0; i < HBFIRFilterTraits::hbOrder + 1; i++) +// { +// m_samples[i][0] = 0; +// m_samples[i][1] = 0; +// } +// +// m_ptr = 0; +// m_state = 0; +//} + +#endif // INCLUDE_INTHALFBANDFILTER_H diff --git a/android/app/src/main/cpp/dsp/inthalfbandfilterdb.h b/android/app/src/main/cpp/dsp/inthalfbandfilterdb.h new file mode 100644 index 0000000..80ae052 --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfilterdb.h @@ -0,0 +1,800 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_DB_H +#define INCLUDE_INTHALFBANDFILTER_DB_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" + +template +class IntHalfbandFilterDB { +public: + IntHalfbandFilterDB() + { + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; + } + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + +// bool workDecimateCenter(qint32 *x, qint32 *y) +// { +// // insert sample into ring-buffer +// storeSample32(*x, *y); +// +// switch (m_state) +// { +// case 0: +// // advance write-pointer +// advancePointer(); +// // next state +// m_state = 1; +// // tell caller we don't have a new sample +// return false; +// +// default: +// // save result +// doFIR(x, y); +// // advance write-pointer +// advancePointer(); +// // next state +// m_state = 0; +// // tell caller we have a new sample +// return true; +// } +// } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSampleFixReal((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSampleAccu(x1, y1); + advancePointer(); + + storeSampleAccu(*x2, *y2); + doFIRAccu(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSampleFixReal((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ +// void myInterpolateZeroStuffing(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) +// { +// storeSampleAccu(*x1, *y1); +// doFIR(x1, y1); +// advancePointer(); +// +// storeSampleAccu(0, 0); +// doFIR(x2, y2); +// advancePointer(); +// } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + AccuType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSampleFixReal(const FixReal& sampleI, const FixReal& sampleQ) + { + m_samplesDB[m_ptr][0] = sampleI; + m_samplesDB[m_ptr][1] = sampleQ; + m_samplesDB[m_ptr + m_size][0] = sampleI; + m_samplesDB[m_ptr + m_size][1] = sampleQ; + } + + void storeSampleAccu(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(qint32 *x, qint32 *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doFIRAccu(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +#endif // INCLUDE_INTHALFBANDFILTER_DB_H diff --git a/android/app/src/main/cpp/dsp/inthalfbandfilterdbf.h b/android/app/src/main/cpp/dsp/inthalfbandfilterdbf.h new file mode 100644 index 0000000..3749707 --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfilterdbf.h @@ -0,0 +1,178 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// Float half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_DBF_H +#define INCLUDE_INTHALFBANDFILTER_DBF_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterDBF { +public: + IntHalfbandFilterDBF(); + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSample(x1, y1); + advancePointer(); + + storeSample(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + SampleType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSample(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] / 2.0; + qAcc += m_samplesDB[b-1][1] / 2.0; + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc * SDR_RX_SCALED; + *y = qAcc * SDR_RX_SCALED; + } +}; + +template +IntHalfbandFilterDBF::IntHalfbandFilterDBF() +{ + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DBF_H diff --git a/android/app/src/main/cpp/dsp/inthalfbandfilterdbff.h b/android/app/src/main/cpp/dsp/inthalfbandfilterdbff.h new file mode 100644 index 0000000..d0adbe9 --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfilterdbff.h @@ -0,0 +1,743 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// Float half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_DBFF_H +#define INCLUDE_INTHALFBANDFILTER_DBFF_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterDBFF { +public: + IntHalfbandFilterDBFF(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(FSample* sample) + { + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(FSample* sampleIn, FSample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(FSample* sampleIn, FSample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(FSample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->imag(), (Real) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->real(), (Real) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) sample->imag(), (Real) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(FSample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) sample->imag(), (Real) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->real(), (Real) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->imag(), (Real) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + void myDecimate(const FSample* sample1, FSample* sample2) + { + storeSampleReal((Real) sample1->real(), (Real) sample1->imag()); + advancePointer(); + + storeSampleReal((Real) sample2->real(), (Real) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSampleAccu(x1, y1); + advancePointer(); + + storeSampleAccu(*x2, *y2); + doFIRAccu(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(FSample* sample1, FSample* sample2) + { + storeSampleReal((Real) sample1->real(), (Real) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSampleReal((Real) 0, (Real) 0); + doFIR(sample2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + SampleType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSampleReal(const Real& sampleI, const Real& sampleQ) + { + m_samplesDB[m_ptr][0] = sampleI; + m_samplesDB[m_ptr][1] = sampleQ; + m_samplesDB[m_ptr + m_size][0] = sampleI; + m_samplesDB[m_ptr + m_size][1] = sampleQ; + } + + void storeSampleAccu(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(FSample* sample) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(iAcc); + sample->setImag(qAcc); + } + + void doFIRAccu(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] / 2.0; + qAcc += m_samplesDB[b-1][1] / 2.0; + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(FSample* sample) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + sample->setReal(iAcc); + sample->setImag(qAcc); + } + + void doInterpolateFIR(Real *x, Real *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc; + *y = qAcc; + } +}; + +template +IntHalfbandFilterDBFF::IntHalfbandFilterDBFF() +{ + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DBFF_H diff --git a/android/app/src/main/cpp/dsp/inthalfbandfilterdbfi.h b/android/app/src/main/cpp/dsp/inthalfbandfilterdbfi.h new file mode 100644 index 0000000..853888e --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfilterdbfi.h @@ -0,0 +1,743 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// Float half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_DBFI_H +#define INCLUDE_INTHALFBANDFILTER_DBFI_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterDBFI { +public: + IntHalfbandFilterDBFI(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSampleFixReal((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSampleAccu(x1, y1); + advancePointer(); + + storeSampleAccu(*x2, *y2); + doFIRAccu(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSampleFixReal((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + SampleType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSampleFixReal(const FixReal& sampleI, const FixReal& sampleQ) + { + m_samplesDB[m_ptr][0] = sampleI / SDR_RX_SCALED; + m_samplesDB[m_ptr][1] = sampleQ / SDR_RX_SCALED; + m_samplesDB[m_ptr + m_size][0] = sampleI / SDR_RX_SCALED; + m_samplesDB[m_ptr + m_size][1] = sampleQ / SDR_RX_SCALED; + } + + void storeSampleAccu(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(iAcc*SDR_RX_SCALED); + sample->setImag(qAcc*SDR_RX_SCALED); + } + + void doFIRAccu(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] / 2.0; + qAcc += m_samplesDB[b-1][1] / 2.0; + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(Sample* sample) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + sample->setReal(iAcc * SDR_RX_SCALED); + sample->setImag(qAcc * SDR_RX_SCALED); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc * SDR_RX_SCALED; + *y = qAcc * SDR_RX_SCALED; + } +}; + +template +IntHalfbandFilterDBFI::IntHalfbandFilterDBFI() +{ + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DBF_H diff --git a/android/app/src/main/cpp/dsp/inthalfbandfiltereo.h b/android/app/src/main/cpp/dsp/inthalfbandfiltereo.h new file mode 100644 index 0000000..21bf94d --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfiltereo.h @@ -0,0 +1,981 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2020 Edouard Griffiths, F4EXB // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" + +template +class IntHalfbandFilterEO { +public: + IntHalfbandFilterEO() + { + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0; + m_even[1][i] = 0; + m_odd[0][i] = 0; + m_odd[1][i] = 0; + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; + } + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample32(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + void myDecimateCen(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + + storeSample32(x3, y3); + advancePointer(); + + storeSample32(*x4, *y4); + doFIR(x4, y4); + advancePointer(); + } + + void myDecimateCen(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(x2, y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(x3, y3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateCen(int32_t *in, int32_t *out) + { + storeSample32(in[0], in[1]); + advancePointer(); + + storeSample32(in[2], in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(in[4], in[5]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateInf(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(-y1, x1); + advancePointer(); + + storeSample32(-x2, -y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(y3, -x3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateInf(int32_t *in, int32_t *out) + { + storeSample32(-in[1], in[0]); + advancePointer(); + + storeSample32(-in[2], -in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(in[5], -in[4]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateSup(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(y1, -x1); + advancePointer(); + + storeSample32(-x2, -y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(-y3, x3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateSup(int32_t *in, int32_t *out) + { + storeSample32(in[1], -in[0]); + advancePointer(); + + storeSample32(-in[2], -in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(-in[5], in[4]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) + { + storeSample32(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample32(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + EOStorageType m_even[2][HBFIRFilterTraits::hbOrder]; + EOStorageType m_odd[2][HBFIRFilterTraits::hbOrder]; + EOStorageType m_samples[HBFIRFilterTraits::hbOrder][2]; + + int m_ptr; + int m_size; + int m_state; + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = IQorder ? sampleI : sampleQ; + m_even[1][m_ptr/2] = IQorder ? sampleQ : sampleI; + m_even[0][m_ptr/2 + m_size] = IQorder ? sampleI : sampleQ; + m_even[1][m_ptr/2 + m_size] = IQorder ? sampleQ : sampleI; + } + else + { + m_odd[0][m_ptr/2] = IQorder ? sampleI : sampleQ; + m_odd[1][m_ptr/2] = IQorder ? sampleQ : sampleI; + m_odd[0][m_ptr/2 + m_size] = IQorder ? sampleI : sampleQ; + m_odd[1][m_ptr/2 + m_size] = IQorder ? sampleQ : sampleI; + } + } + + void storeSample32(int32_t x, int32_t y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = IQorder ? x : y; + m_even[1][m_ptr/2] = IQorder ? y : x; + m_even[0][m_ptr/2 + m_size] = IQorder ? x : y; + m_even[1][m_ptr/2 + m_size] = IQorder ? y : x; + } + else + { + m_odd[0][m_ptr/2] = IQorder ? x : y; + m_odd[1][m_ptr/2] = IQorder ? y : x; + m_odd[0][m_ptr/2 + m_size] = IQorder ? x : y; + m_odd[1][m_ptr/2 + m_size] = IQorder ? y : x; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += ((EOStorageType)(m_even[0][a] + m_even[0][b])) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += ((EOStorageType)(m_even[1][a] + m_even[1][b])) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += ((EOStorageType)(m_odd[0][a] + m_odd[0][b])) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += ((EOStorageType)(m_odd[1][a] + m_odd[1][b])) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += m_odd[0][m_ptr/2 + m_size/2] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_odd[1][m_ptr/2 + m_size/2] << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += m_even[0][m_ptr/2 + m_size/2 + 1] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_even[1][m_ptr/2 + m_size/2 + 1] << (HBFIRFilterTraits::hbShift - 1); + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(int32_t *x, int32_t *y) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += ((EOStorageType)(m_even[0][a] + m_even[0][b])) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += ((EOStorageType)(m_even[1][a] + m_even[1][b])) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += ((EOStorageType)(m_odd[0][a] + m_odd[0][b])) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += ((EOStorageType)(m_odd[1][a] + m_odd[1][b])) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += m_odd[0][m_ptr/2 + m_size/2] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_odd[1][m_ptr/2 + m_size/2] << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += m_even[0][m_ptr/2 + m_size/2 + 1] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_even[1][m_ptr/2 + m_size/2 + 1] << (HBFIRFilterTraits::hbShift - 1); + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += ((EOStorageType)(m_samples[a][0] + m_samples[b][0])) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += ((EOStorageType)(m_samples[a][1] + m_samples[b][1])) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += ((EOStorageType)(m_samples[a][0] + m_samples[b][0])) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += ((EOStorageType)(m_samples[a][1] + m_samples[b][1])) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +//template +//IntHalfbandFilterEO::IntHalfbandFilterEO() +//{ +// m_size = HBFIRFilterTraits::hbOrder/2; + +// for (int i = 0; i < 2*m_size; i++) +// { +// m_even[0][i] = 0; +// m_even[1][i] = 0; +// m_odd[0][i] = 0; +// m_odd[1][i] = 0; +// m_samples[i][0] = 0; +// m_samples[i][1] = 0; +// } + +// m_ptr = 0; +// m_state = 0; +//} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO_H_ */ diff --git a/android/app/src/main/cpp/dsp/inthalfbandfiltereo1.h b/android/app/src/main/cpp/dsp/inthalfbandfiltereo1.h new file mode 100644 index 0000000..d672326 --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfiltereo1.h @@ -0,0 +1,853 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2019 Davide Gerhard // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +//#include "dsp/inthalfbandfiltereo1i.h" + +template +class IntHalfbandFilterEO1 { +public: + IntHalfbandFilterEO1(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample32(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) + { + storeSample32(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample32(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + int32_t m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + int32_t m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + + int m_ptr; + int m_size; + int m_state; + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = sampleI; + m_even[1][m_ptr/2] = sampleQ; + m_even[0][m_ptr/2 + m_size] = sampleI; + m_even[1][m_ptr/2 + m_size] = sampleQ; + } + else + { + m_odd[0][m_ptr/2] = sampleI; + m_odd[1][m_ptr/2] = sampleQ; + m_odd[0][m_ptr/2 + m_size] = sampleI; + m_odd[1][m_ptr/2 + m_size] = sampleQ; + } + } + + void storeSample32(int32_t x, int32_t y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + int32_t rand(int32_t mod) + { + return (RAND_MAX/2 - std::rand()) % mod; + } + + void doFIR(Sample* sample) + { + int32_t iAcc = 0; + int32_t qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(int32_t *x, int32_t *y) + { + int32_t iAcc = 0; + int32_t qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + qint32 iAcc = 0; + qint32 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint32 iAcc = 0; + qint32 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +template +IntHalfbandFilterEO1::IntHalfbandFilterEO1() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0; + m_even[1][i] = 0; + m_odd[0][i] = 0; + m_odd[1][i] = 0; + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ */ diff --git a/android/app/src/main/cpp/dsp/inthalfbandfiltereo1i.h b/android/app/src/main/cpp/dsp/inthalfbandfiltereo1i.h new file mode 100644 index 0000000..5508c8c --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfiltereo1i.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016, 2019 Edouard Griffiths, F4EXB // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO1I_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO1I_H_ + +#include + +#if defined(USE_SSE4_1) +#include +#endif + +#include "hbfiltertraits.h" + +template +class IntHalfbandFilterEO1Intrisics +{ +public: + static void work( + int ptr, + int32_t even[2][HBFilterOrder], + int32_t odd[2][HBFilterOrder], + int32_t& iAcc, int32_t& qAcc) + { +#if defined(USE_SSE4_1) + int a = ptr/2 + HBFIRFilterTraits::hbOrder/2; // tip pointer + int b = ptr/2 + 1; // tail pointer + const __m128i* h = (const __m128i*) HBFIRFilterTraits::hbCoeffs; + __m128i sumI = _mm_setzero_si128(); + __m128i sumQ = _mm_setzero_si128(); + __m128i sa, sb; + a -= 3; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 16; i++) + { + if ((ptr % 2) == 0) + { + sa = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*) &(even[0][a])), _MM_SHUFFLE(0,1,2,3)); + sb = _mm_loadu_si128((__m128i*) &(even[0][b])); + sumI = _mm_add_epi32(sumI, _mm_mullo_epi32(_mm_add_epi32(sa, sb), *h)); + + sa = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*) &(even[1][a])), _MM_SHUFFLE(0,1,2,3)); + sb = _mm_loadu_si128((__m128i*) &(even[1][b])); + sumQ = _mm_add_epi32(sumQ, _mm_mullo_epi32(_mm_add_epi32(sa, sb), *h)); + } + else + { + sa = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*) &(odd[0][a])), _MM_SHUFFLE(0,1,2,3)); + sb = _mm_loadu_si128((__m128i*) &(odd[0][b])); + sumI = _mm_add_epi32(sumI, _mm_mullo_epi32(_mm_add_epi32(sa, sb), *h)); + + sa = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*) &(odd[1][a])), _MM_SHUFFLE(0,1,2,3)); + sb = _mm_loadu_si128((__m128i*) &(odd[1][b])); + sumQ = _mm_add_epi32(sumQ, _mm_mullo_epi32(_mm_add_epi32(sa, sb), *h)); + } + + a -= 4; + b += 4; + ++h; + } + + // horizontal add of four 32 bit partial sums + + sumI = _mm_add_epi32(sumI, _mm_srli_si128(sumI, 8)); + sumI = _mm_add_epi32(sumI, _mm_srli_si128(sumI, 4)); + iAcc = _mm_cvtsi128_si32(sumI); + + sumQ = _mm_add_epi32(sumQ, _mm_srli_si128(sumQ, 8)); + sumQ = _mm_add_epi32(sumQ, _mm_srli_si128(sumQ, 4)); + qAcc = _mm_cvtsi128_si32(sumQ); +#endif + } +}; + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO1I_H_ */ diff --git a/android/app/src/main/cpp/dsp/inthalfbandfiltereo2.h b/android/app/src/main/cpp/dsp/inthalfbandfiltereo2.h new file mode 100644 index 0000000..2cc14cb --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfiltereo2.h @@ -0,0 +1,864 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterEO2 { +public: + IntHalfbandFilterEO2(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample32(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) + { + storeSample32(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample32(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2, qint32 *x3, qint32 *y3, qint32 *x4, qint32 *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + qint64 m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + qint64 m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + + int m_ptr; + int m_size; + int m_state; + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = sampleI; + m_even[1][m_ptr/2] = sampleQ; + m_even[0][m_ptr/2 + m_size] = sampleI; + m_even[1][m_ptr/2 + m_size] = sampleQ; + } + else + { + m_odd[0][m_ptr/2] = sampleI; + m_odd[1][m_ptr/2] = sampleQ; + m_odd[0][m_ptr/2 + m_size] = sampleI; + m_odd[1][m_ptr/2 + m_size] = sampleQ; + } + } + + void storeSample32(int32_t x, int32_t y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void storeSample64(qint64 x, qint64 y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(int32_t *x, int32_t *y) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +template +IntHalfbandFilterEO2::IntHalfbandFilterEO2() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0; + m_even[1][i] = 0; + m_odd[0][i] = 0; + m_odd[1][i] = 0; + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ */ diff --git a/android/app/src/main/cpp/dsp/inthalfbandfiltereof.h b/android/app/src/main/cpp/dsp/inthalfbandfiltereof.h new file mode 100644 index 0000000..f16ac0a --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfiltereof.h @@ -0,0 +1,254 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2019 Davide Gerhard // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" + +template +class IntHalfbandFilterEOF { +public: + IntHalfbandFilterEOF(); + + bool workDecimateCenter(float *x, float *y) + { + // insert sample into ring-buffer + storeSample(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + void myDecimate(float x1, float y1, float *x2, float *y2) + { + storeSample(x1, y1); + advancePointer(); + + storeSample(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(float *x1, float *y1, float *x2, float *y2) + { + storeSample(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(float *x1, float *y1, float *x2, float *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + + void myInterpolateInf(float *x1, float *y1, float *x2, float *y2, float *x3, float *y3, float *x4, float *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = *y1; + *y1 = -x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = -*y3; + *y3 = x; + } + + void myInterpolateSup(float *x1, float *y1, float *x2, float *y2, float *x3, float *y3, float *x4, float *y4) + { + myInterpolate(x1, y1, x2, y2); + myInterpolate(x3, y3, x4, y4); + // rotation + qint32 x; + x = *x1; + *x1 = -*y1; + *y1 = x; + *x2 = -*x2; + *y2 = -*y2; + x = *x3; + *x3 = *y3; + *y3 = -x; + } + +protected: + float m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + float m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + float m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + + int m_ptr; + int m_size; + int m_state; + + void storeSample(float x, float y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = IQOrder ? x : y; + m_even[1][m_ptr/2] = IQOrder ? y : x; + m_even[0][m_ptr/2 + m_size] = IQOrder ? x : y; + m_even[1][m_ptr/2 + m_size] = IQOrder ? y : x; + } + else + { + m_odd[0][m_ptr/2] = IQOrder ? x : y; + m_odd[1][m_ptr/2] = IQOrder ? y : x; + m_odd[0][m_ptr/2 + m_size] = IQOrder ? x : y; + m_odd[1][m_ptr/2 + m_size] = IQOrder ? y : x; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(float *x, float *y) + { + float iAcc = 0; + float qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += m_odd[0][m_ptr/2 + m_size/2] * 0.5f; + qAcc += m_odd[1][m_ptr/2 + m_size/2] * 0.5f; + } + else + { + iAcc += m_even[0][m_ptr/2 + m_size/2 + 1] * 0.5f; + qAcc += m_even[1][m_ptr/2 + m_size/2 + 1] * 0.5f; + } + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(float *x, float *y) + { + qint32 iAcc = 0; + qint32 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc * SDR_RX_SCALED; + *y = qAcc * SDR_RX_SCALED; + } +}; + +template +IntHalfbandFilterEOF::IntHalfbandFilterEOF() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0.0f; + m_even[1][i] = 0.0f; + m_odd[0][i] = 0.0f; + m_odd[1][i] = 0.0f; + m_samples[i][0] = 0.0f; + m_samples[i][1] = 0.0f; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ */ diff --git a/android/app/src/main/cpp/dsp/inthalfbandfilterst.h b/android/app/src/main/cpp/dsp/inthalfbandfilterst.h new file mode 100644 index 0000000..c04a15e --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfilterst.h @@ -0,0 +1,578 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd and I/Q stride with double buffering variant // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_ST_H +#define INCLUDE_INTHALFBANDFILTER_ST_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "dsp/inthalfbandfiltersti.h" +#include "export.h" + +template +class SDRANGEL_API IntHalfbandFilterST { +public: + IntHalfbandFilterST(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample(0, 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample(0, 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample(0, 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample(0, 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample(0, 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) + { + storeSample(x1, y1); + advancePointer(); + + storeSample(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + void myInterpolate(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample(0, 0); + doFIR(sample2); + advancePointer(); + } + + void myInterpolate(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) + { + storeSample(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample(0, 0); + doFIR(x2, y2); + advancePointer(); + } + +protected: + int32_t m_samplesDB[2*HBFilterOrder][2]; // double buffer technique with even/odd amnd I/Q stride + int32_t m_samplesAligned[HBFilterOrder][2] __attribute__ ((aligned (16))); + int m_ptr; + int m_size; + int m_state; + int32_t m_iEvenAcc; + int32_t m_qEvenAcc; + int32_t m_iOddAcc; + int32_t m_qOddAcc; + + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + m_samplesDB[m_ptr][0] = sampleI; + m_samplesDB[m_ptr][1] = sampleQ; + m_samplesDB[m_ptr + m_size][0] = sampleI; + m_samplesDB[m_ptr + m_size][1] = sampleQ; + } + + void storeSample(int32_t x, int32_t y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + // calculate on odd values + + if ((m_ptr % 2) == 1) + { + m_iEvenAcc = 0; + m_qEvenAcc = 0; + m_iOddAcc = 0; + m_qOddAcc = 0; +#ifdef USE_SSE4_1 +// memcpy((void *) m_samplesAligned, (const void *) &(m_samplesDB[ m_ptr + 1][0]), HBFilterOrder*2*sizeof(int32_t)); + IntHalfbandFilterSTIntrinsics::workNA( + m_ptr + 1, + m_samplesDB, + m_iEvenAcc, + m_qEvenAcc, + m_iOddAcc, + m_qOddAcc); +#else + int a = m_ptr + m_size; // tip pointer - odd + int b = m_ptr + 1; // tail pointer - aven + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + m_iEvenAcc += (m_samplesDB[a-1][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + m_iOddAcc += (m_samplesDB[a][0] + m_samplesDB[b+1][0]) * HBFIRFilterTraits::hbCoeffs[i]; + m_qEvenAcc += (m_samplesDB[a-1][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + m_qOddAcc += (m_samplesDB[a][1] + m_samplesDB[b+1][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a -= 2; + b += 2; + } +#endif + m_iEvenAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2][0]) << (HBFIRFilterTraits::hbShift - 1); + m_qEvenAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2][1]) << (HBFIRFilterTraits::hbShift - 1); + m_iOddAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2 + 1][0]) << (HBFIRFilterTraits::hbShift - 1); + m_qOddAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2 + 1][1]) << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(m_iEvenAcc >> HBFIRFilterTraits::hbShift -1); + sample->setImag(m_qEvenAcc >> HBFIRFilterTraits::hbShift -1); + } + else + { + sample->setReal(m_iOddAcc >> HBFIRFilterTraits::hbShift -1); + sample->setImag(m_qOddAcc >> HBFIRFilterTraits::hbShift -1); + } + } + + void doFIR(int32_t *x, int32_t *y) + { + // calculate on odd values + + if ((m_ptr % 2) == 1) + { + m_iEvenAcc = 0; + m_qEvenAcc = 0; + m_iOddAcc = 0; + m_qOddAcc = 0; + +#ifdef USE_SSE4_1 +// memcpy((void *) m_samplesAligned, (const void *) &(m_samplesDB[ m_ptr + 1][0]), HBFilterOrder*2*sizeof(int32_t)); + IntHalfbandFilterSTIntrinsics::workNA( + m_ptr + 1, + m_samplesDB, + m_iEvenAcc, + m_qEvenAcc, + m_iOddAcc, + m_qOddAcc); +#else + int a = m_ptr + m_size; // tip pointer - odd + int b = m_ptr + 1; // tail pointer - aven + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + m_iEvenAcc += (m_samplesDB[a-1][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + m_iOddAcc += (m_samplesDB[a][0] + m_samplesDB[b+1][0]) * HBFIRFilterTraits::hbCoeffs[i]; + m_qEvenAcc += (m_samplesDB[a-1][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + m_qOddAcc += (m_samplesDB[a][1] + m_samplesDB[b+1][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a -= 2; + b += 2; + } +#endif + m_iEvenAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2][0]) << (HBFIRFilterTraits::hbShift - 1); + m_qEvenAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2][1]) << (HBFIRFilterTraits::hbShift - 1); + m_iOddAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2 + 1][0]) << (HBFIRFilterTraits::hbShift - 1); + m_qOddAcc += ((int32_t)m_samplesDB[m_ptr + m_size/2 + 1][1]) << (HBFIRFilterTraits::hbShift - 1); + + *x = m_iEvenAcc >> HBFIRFilterTraits::hbShift -1; + *y = m_qEvenAcc >> HBFIRFilterTraits::hbShift -1; + } + else + { + *x = m_iOddAcc >> HBFIRFilterTraits::hbShift -1; + *y = m_qOddAcc >> HBFIRFilterTraits::hbShift -1; + } + } +}; + +template +IntHalfbandFilterST::IntHalfbandFilterST() +{ + m_size = HBFIRFilterTraits::hbOrder; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; + m_iEvenAcc = 0; + m_qEvenAcc = 0; + m_iOddAcc = 0; + m_qOddAcc = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DB_H diff --git a/android/app/src/main/cpp/dsp/inthalfbandfiltersti.h b/android/app/src/main/cpp/dsp/inthalfbandfiltersti.h new file mode 100644 index 0000000..4566b75 --- /dev/null +++ b/android/app/src/main/cpp/dsp/inthalfbandfiltersti.h @@ -0,0 +1,142 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016, 2019 Edouard Griffiths, F4EXB // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd and I/Q stride with double buffering variant // +// This is the SIMD intrinsics code // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTERSTI_H_ +#define SDRBASE_DSP_INTHALFBANDFILTERSTI_H_ + +#include + +#if defined(USE_SSE4_1) +#include +#endif + +#include "hbfiltertraits.h" + +template +class IntHalfbandFilterSTIntrinsics +{ +public: + static void work( + int32_t samples[HBFilterOrder][2], + int32_t& iEvenAcc, int32_t& qEvenAcc, + int32_t& iOddAcc, int32_t& qOddAcc) + { +#if defined(USE_SSE4_1) + int a = HBFIRFilterTraits::hbOrder - 2; // tip + int b = 0; // tail + const int *h = (const int*) HBFIRFilterTraits::hbCoeffs; + __m128i sum = _mm_setzero_si128(); + __m128i shh, sa, sb; + int32_t sums[4] __attribute__ ((aligned (16))); + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 16; i++) + { + shh = _mm_set_epi32(h[4*i], h[4*i], h[4*i], h[4*i]); + sa = _mm_load_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_load_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + shh = _mm_set_epi32(h[4*i+1], h[4*i+1], h[4*i+1], h[4*i+1]); + sa = _mm_load_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_load_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + shh = _mm_set_epi32(h[4*i+2], h[4*i+2], h[4*i+2], h[4*i+2]); + sa = _mm_load_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_load_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + shh = _mm_set_epi32(h[4*i+3], h[4*i+3], h[4*i+3], h[4*i+3]); + sa = _mm_load_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_load_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + } + + // Extract values from sum vector + _mm_store_si128((__m128i*) sums, sum); + iEvenAcc = sums[0]; + qEvenAcc = sums[1]; + iOddAcc = sums[2]; + qOddAcc = sums[3]; +#endif + } + + // not aligned version + static void workNA( + int ptr, + int32_t samples[HBFilterOrder*2][2], + int32_t& iEvenAcc, int32_t& qEvenAcc, + int32_t& iOddAcc, int32_t& qOddAcc) + { +#if defined(USE_SSE4_1) + int a = ptr + HBFIRFilterTraits::hbOrder - 2; // tip + int b = ptr + 0; // tail + const int *h = (const int*) HBFIRFilterTraits::hbCoeffs; + __m128i sum = _mm_setzero_si128(); + __m128i shh, sa, sb; + int32_t sums[4] __attribute__ ((aligned (16))); + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 16; i++) + { + shh = _mm_set_epi32(h[4*i], h[4*i], h[4*i], h[4*i]); + sa = _mm_loadu_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_loadu_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + shh = _mm_set_epi32(h[4*i+1], h[4*i+1], h[4*i+1], h[4*i+1]); + sa = _mm_loadu_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_loadu_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + shh = _mm_set_epi32(h[4*i+2], h[4*i+2], h[4*i+2], h[4*i+2]); + sa = _mm_loadu_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_loadu_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + shh = _mm_set_epi32(h[4*i+3], h[4*i+3], h[4*i+3], h[4*i+3]); + sa = _mm_loadu_si128((__m128i*) &(samples[a][0])); // Ei,Eq,Oi,Oq + sb = _mm_loadu_si128((__m128i*) &(samples[b][0])); + sum = _mm_add_epi32(sum, _mm_mullo_epi32(_mm_add_epi32(sa, sb), shh)); + a -= 2; + b += 2; + } + + // Extract values from sum vector + _mm_store_si128((__m128i*) sums, sum); + iEvenAcc = sums[0]; + qEvenAcc = sums[1]; + iOddAcc = sums[2]; + qOddAcc = sums[3]; +#endif + } +}; + + + +#endif /* SDRBASE_DSP_INTHALFBANDFILTERSTI_H_ */ diff --git a/android/app/src/main/cpp/dsp/kissengine.cpp b/android/app/src/main/cpp/dsp/kissengine.cpp new file mode 100644 index 0000000..5595769 --- /dev/null +++ b/android/app/src/main/cpp/dsp/kissengine.cpp @@ -0,0 +1,61 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#include "dsp/kissengine.h" +#include "util/profiler.h" + +const QString KissEngine::m_name = "Kiss"; + +QString KissEngine::getName() const +{ + return m_name; +} + +void KissEngine::configure(int n, bool inverse) +{ + m_fft.configure(n, inverse); + if(n > (int) m_in.size()) + m_in.resize(n); + if(n > (int) m_out.size()) + m_out.resize(n); +} + +void KissEngine::transform() +{ + PROFILER_START() + + m_fft.transform(&m_in[0], &m_out[0]); + + PROFILER_STOP(QString("%1 %2").arg(getName()).arg(m_out.size())) +} + +Complex* KissEngine::in() +{ + return &m_in[0]; +} + +Complex* KissEngine::out() +{ + return &m_out[0]; +} + +void KissEngine::setReuse(bool reuse) +{ + (void) reuse; +} diff --git a/android/app/src/main/cpp/dsp/kissengine.h b/android/app/src/main/cpp/dsp/kissengine.h new file mode 100644 index 0000000..691dfaf --- /dev/null +++ b/android/app/src/main/cpp/dsp/kissengine.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_KISSENGINE_H +#define INCLUDE_KISSENGINE_H + +#include "dsp/fftengine.h" +#include "dsp/kissfft.h" +#include "export.h" + +class SDRBASE_API KissEngine : public FFTEngine { +public: + virtual void configure(int n, bool inverse); + virtual void transform(); + + virtual Complex* in(); + virtual Complex* out(); + + virtual void setReuse(bool reuse); + QString getName() const override; + static const QString m_name; + +protected: + typedef kissfft KissFFT; + KissFFT m_fft; + + std::vector m_in; + std::vector m_out; +}; + +#endif // INCLUDE_KISSENGINE_H diff --git a/android/app/src/main/cpp/dsp/kissfft.h b/android/app/src/main/cpp/dsp/kissfft.h new file mode 100644 index 0000000..ff0975d --- /dev/null +++ b/android/app/src/main/cpp/dsp/kissfft.h @@ -0,0 +1,409 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_KISSFFT_H +#define INCLUDE_KISSFFT_H + +#include +#include + +/* +Copyright (c) 2003-2010 Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +namespace kissfft_utils { + + template + struct traits { + typedef T_scalar scalar_type; + typedef T_complex cpx_type; + void fill_twiddles(std::complex* dst, int nfft, bool inverse) + { + T_scalar phinc = (inverse ? 2 : -2) * acos((T_scalar)-1) / nfft; + for(int i = 0; i < nfft; ++i) + dst[i] = exp(std::complex(0, i * phinc)); + } + + void prepare(std::vector >& dst, int nfft, bool inverse, std::vector& stageRadix, std::vector& stageRemainder) + { + _twiddles.resize(nfft); + fill_twiddles(&_twiddles[0], nfft, inverse); + dst = _twiddles; + + //factorize + //start factoring out 4's, then 2's, then 3,5,7,9,... + int n = nfft; + int p = 4; + do { + while(n % p) { + switch(p) { + case 4: + p = 2; + break; + case 2: + p = 3; + break; + default: + p += 2; + break; + } + if(p * p > n) + p = n;// no more factors + } + n /= p; + stageRadix.push_back(p); + stageRemainder.push_back(n); + } while(n > 1); + } + std::vector _twiddles; + + const cpx_type twiddle(int i) + { + return _twiddles[i]; + } + }; + +} // namespace + +template > +class kissfft { +public: + typedef T_traits traits_type; + typedef typename traits_type::scalar_type scalar_type; + typedef typename traits_type::cpx_type cpx_type; + + kissfft() + { + } + + kissfft(int nfft, bool inverse, const traits_type & traits = traits_type()) : + _nfft(nfft), _inverse(inverse), _traits(traits) + { + _traits.prepare(_twiddles, _nfft, _inverse, _stageRadix, _stageRemainder); + } + + void configure(int nfft, bool inverse, const traits_type & traits = traits_type()) + { + _twiddles.clear(); + _stageRadix.clear(); + _stageRemainder.clear(); + + _nfft = nfft; + _inverse = inverse; + _traits = traits; + _traits.prepare(_twiddles, _nfft, _inverse, _stageRadix, _stageRemainder); + } + + void transform(const cpx_type* src, cpx_type* dst) + { + kf_work(0, dst, src, 1, 1); + } + +private: + void kf_work(int stage, cpx_type* Fout, const cpx_type* f, size_t fstride, size_t in_stride) + { + int p = _stageRadix[stage]; + int m = _stageRemainder[stage]; + cpx_type * Fout_beg = Fout; + cpx_type * Fout_end = Fout + p * m; + + if(m == 1) { + do { + *Fout = *f; + f += fstride * in_stride; + } while(++Fout != Fout_end); + } else { + do { + // recursive call: + // DFT of size m*p performed by doing + // p instances of smaller DFTs of size m, + // each one takes a decimated version of the input + kf_work(stage + 1, Fout, f, fstride * p, in_stride); + f += fstride * in_stride; + } while((Fout += m) != Fout_end); + } + + Fout = Fout_beg; + + // recombine the p smaller DFTs + switch(p) { + case 2: + kf_bfly2(Fout, fstride, m); + break; + case 3: + kf_bfly3(Fout, fstride, m); + break; + case 4: + kf_bfly4(Fout, fstride, m); + break; + case 5: + kf_bfly5(Fout, fstride, m); + break; + default: + kf_bfly_generic(Fout, fstride, m, p); + break; + } + } + + // these were #define macros in the original kiss_fft + void C_ADD(cpx_type& c, const cpx_type& a, const cpx_type& b) + { + c = a + b; + } + void C_MUL(cpx_type& c, const cpx_type& a, const cpx_type& b) + { + //c = a * b; + c = cpx_type(a.real() * b.real() - a.imag() * b.imag(), a.real() * b.imag() + a.imag() * b.real()); + } + void C_SUB(cpx_type& c, const cpx_type& a, const cpx_type& b) + { + c = a - b; + } + void C_ADDTO(cpx_type& c, const cpx_type& a) + { + c += a; + } + void C_FIXDIV(cpx_type&, int) + { + } // NO-OP for float types + scalar_type S_MUL(const scalar_type& a, const scalar_type& b) + { + return a * b; + } + scalar_type HALF_OF(const scalar_type& a) + { + return a * .5; + } + void C_MULBYSCALAR(cpx_type& c, const scalar_type& a) + { + c *= a; + } + + void kf_bfly2(cpx_type* Fout, const size_t fstride, int m) + { + for(int k = 0; k < m; ++k) { + //cpx_type t = Fout[m + k] * _traits.twiddle(k * fstride); + cpx_type t; + C_MUL(t, Fout[m + k], _traits.twiddle(k * fstride)); + Fout[m + k] = Fout[k] - t; + Fout[k] += t; + } + } + + void kf_bfly4(cpx_type* Fout, const size_t fstride, const size_t m) + { + cpx_type scratch[7]; + int negative_if_inverse = _inverse * -2 + 1; + for(size_t k = 0; k < m; ++k) { + //scratch[0] = Fout[k + m] * _traits.twiddle(k * fstride); + C_MUL(scratch[0], Fout[k + m], _traits.twiddle(k * fstride)); + C_MUL(scratch[1], Fout[k + 2 * m], _traits.twiddle(k * fstride * 2)); + C_MUL(scratch[2], Fout[k + 3 * m], _traits.twiddle(k * fstride * 3)); + scratch[5] = Fout[k] - scratch[1]; + + Fout[k] += scratch[1]; + scratch[3] = scratch[0] + scratch[2]; + scratch[4] = scratch[0] - scratch[2]; + scratch[4] = cpx_type(scratch[4].imag() * negative_if_inverse, -scratch[4].real() * negative_if_inverse); + + Fout[k + 2 * m] = Fout[k] - scratch[3]; + Fout[k] += scratch[3]; + Fout[k + m] = scratch[5] + scratch[4]; + Fout[k + 3 * m] = scratch[5] - scratch[4]; + } + } + + void kf_bfly3(cpx_type* Fout, const size_t fstride, const size_t m) + { + size_t k = m; + const size_t m2 = 2 * m; + cpx_type* tw1; + cpx_type* tw2; + cpx_type scratch[5]; + cpx_type epi3; + epi3 = _twiddles[fstride * m]; + tw1 = tw2 = &_twiddles[0]; + + do { + C_FIXDIV(*Fout, 3); + C_FIXDIV(Fout[m], 3); + C_FIXDIV(Fout[m2], 3); + + C_MUL(scratch[1], Fout[m], *tw1); + C_MUL(scratch[2], Fout[m2], *tw2); + + C_ADD(scratch[3], scratch[1], scratch[2]); + C_SUB(scratch[0], scratch[1], scratch[2]); + tw1 += fstride; + tw2 += fstride * 2; + + Fout[m] = cpx_type(Fout->real() - HALF_OF(scratch[3].real()), Fout->imag() - HALF_OF(scratch[3].imag())); + + C_MULBYSCALAR(scratch[0], epi3.imag()); + + C_ADDTO(*Fout, scratch[3]); + + Fout[m2] = cpx_type(Fout[m].real() + scratch[0].imag(), Fout[m].imag() - scratch[0].real()); + + C_ADDTO(Fout[m], cpx_type(-scratch[0].imag(), scratch[0].real())); + ++Fout; + } while(--k); + } + + void kf_bfly5(cpx_type* Fout, const size_t fstride, const size_t m) + { + cpx_type* Fout0; + cpx_type* Fout1; + cpx_type* Fout2; + cpx_type* Fout3; + cpx_type* Fout4; + size_t u; + cpx_type scratch[13]; + cpx_type* twiddles = &_twiddles[0]; + cpx_type* tw; + cpx_type ya, yb; + ya = twiddles[fstride * m]; + yb = twiddles[fstride * 2 * m]; + + Fout0 = Fout; + Fout1 = Fout0 + m; + Fout2 = Fout0 + 2 * m; + Fout3 = Fout0 + 3 * m; + Fout4 = Fout0 + 4 * m; + + tw = twiddles; + for(u = 0; u < m; ++u) { + C_FIXDIV(*Fout0, 5); + C_FIXDIV(*Fout1, 5); + C_FIXDIV(*Fout2, 5); + C_FIXDIV(*Fout3, 5); + C_FIXDIV(*Fout4, 5); + scratch[0] = *Fout0; + + C_MUL(scratch[1], *Fout1, tw[u * fstride]); + C_MUL(scratch[2], *Fout2, tw[2 * u * fstride]); + C_MUL(scratch[3], *Fout3, tw[3 * u * fstride]); + C_MUL(scratch[4], *Fout4, tw[4 * u * fstride]); + + C_ADD(scratch[7], scratch[1], scratch[4]); + C_SUB(scratch[10], scratch[1], scratch[4]); + C_ADD(scratch[8], scratch[2], scratch[3]); + C_SUB(scratch[9], scratch[2], scratch[3]); + + C_ADDTO(*Fout0, scratch[7]); + C_ADDTO(*Fout0, scratch[8]); + + scratch[5] = scratch[0] + cpx_type(S_MUL(scratch[7].real(), ya.real()) + S_MUL(scratch[8].real(), yb.real()), S_MUL(scratch[7].imag(), ya.real()) + + S_MUL(scratch[8].imag(), yb.real())); + + scratch[6] = cpx_type(S_MUL(scratch[10].imag(), ya.imag()) + S_MUL(scratch[9].imag(), yb.imag()), -S_MUL(scratch[10].real(), ya.imag()) - S_MUL( + scratch[9].real(), yb.imag())); + + C_SUB(*Fout1, scratch[5], scratch[6]); + C_ADD(*Fout4, scratch[5], scratch[6]); + + scratch[11] = scratch[0] + cpx_type(S_MUL(scratch[7].real(), yb.real()) + S_MUL(scratch[8].real(), ya.real()), S_MUL(scratch[7].imag(), yb.real()) + + S_MUL(scratch[8].imag(), ya.real())); + + scratch[12] = cpx_type(-S_MUL(scratch[10].imag(), yb.imag()) + S_MUL(scratch[9].imag(), ya.imag()), S_MUL(scratch[10].real(), yb.imag()) - S_MUL( + scratch[9].real(), ya.imag())); + + C_ADD(*Fout2, scratch[11], scratch[12]); + C_SUB(*Fout3, scratch[11], scratch[12]); + + ++Fout0; + ++Fout1; + ++Fout2; + ++Fout3; + ++Fout4; + } + } + + /* perform the butterfly for one stage of a mixed radix FFT */ + void kf_bfly_generic(cpx_type* Fout, const size_t fstride, int m, int p) + { + int u; + int k; + int q1; + int q; + cpx_type* twiddles = &_twiddles[0]; + cpx_type t; + int Norig = _nfft; + cpx_type* scratchbuf = new cpx_type[p]; + + for(u = 0; u < m; ++u) { + k = u; + for(q1 = 0; q1 < p; ++q1) { + scratchbuf[q1] = Fout[k]; + C_FIXDIV(scratchbuf[q1], p); + k += m; + } + + k = u; + for(q1 = 0; q1 < p; ++q1) { + int twidx = 0; + Fout[k] = scratchbuf[0]; + for(q = 1; q < p; ++q) { + twidx += fstride * k; + if(twidx >= Norig) + twidx -= Norig; + C_MUL(t, scratchbuf[q], twiddles[twidx]); + C_ADDTO(Fout[k], t); + } + k += m; + } + } + + delete[] scratchbuf; + } + + int _nfft; + bool _inverse; + std::vector _twiddles; + std::vector _stageRadix; + std::vector _stageRemainder; + traits_type _traits; +}; +#endif diff --git a/android/app/src/main/cpp/dsp/mimochannel.cpp b/android/app/src/main/cpp/dsp/mimochannel.cpp new file mode 100644 index 0000000..fbd2caf --- /dev/null +++ b/android/app/src/main/cpp/dsp/mimochannel.cpp @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2019, 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "mimochannel.h" + +MIMOChannel::MIMOChannel() +{ +} + +MIMOChannel::~MIMOChannel() +{ +} diff --git a/android/app/src/main/cpp/dsp/mimochannel.h b/android/app/src/main/cpp/dsp/mimochannel.h new file mode 100644 index 0000000..4a22eda --- /dev/null +++ b/android/app/src/main/cpp/dsp/mimochannel.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_MIMOCHANNEL_H +#define SDRBASE_MIMOCHANNEL_H + +#include "export.h" +#include "dsp/dsptypes.h" +#include "util/message.h" + + +class SDRBASE_API MIMOChannel { +public: + MIMOChannel(); + virtual ~MIMOChannel(); + + virtual void startSinks() = 0; + virtual void stopSinks() = 0; + virtual void startSources() = 0; + virtual void stopSources() = 0; + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex) = 0; + virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples, unsigned int sourceIndex) = 0; + virtual void pushMessage(Message *msg) = 0; + virtual QString getMIMOName() = 0; +}; + +#endif // SDRBASE_MIMOCHANNEL_H diff --git a/android/app/src/main/cpp/dsp/misc.h b/android/app/src/main/cpp/dsp/misc.h new file mode 100644 index 0000000..209304a --- /dev/null +++ b/android/app/src/main/cpp/dsp/misc.h @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2017 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +// ---------------------------------------------------------------------------- +// misc.h -- Miscellaneous helper functions +// +// Copyright (C) 2006-2008 +// Dave Freese, W1HKJ +// +// This file is part of fldigi. These filters were adapted from code contained +// in the gmfsk source code distribution. +// +// Fldigi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Fldigi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with fldigi. If not, see . +// ---------------------------------------------------------------------------- + +#ifndef _MISC_H +#define _MISC_H + +#include + +inline float sinc(float x) +{ + return (fabs(x) < 1e-10) ? 1.0 : (sin(M_PI * x) / (M_PI * x)); +} + +inline float cosc(float x) +{ + return (fabs(x) < 1e-10) ? 0.0 : ((1.0 - cos(M_PI * x)) / (M_PI * x)); +} + +template +inline T clamp(T x, T min, T max) +{ + return (x < min) ? min : ((x > max) ? max : x); +} + +/// This is always called with an int weight +inline float decayavg(float average, float input, int weight) +{ + if (weight <= 1) return input; + return ( ( input - average ) / (float)weight ) + average ; +} + +// following are defined inline to provide best performance +inline float blackman(float x) +{ + return (0.42 - 0.50 * cos(2 * M_PI * x) + 0.08 * cos(4 * M_PI * x)); +} + +inline float hamming(float x) +{ + return 0.54 - 0.46 * cos(2 * M_PI * x); +} + +inline float hanning(float x) +{ + return 0.5 - 0.5 * cos(2 * M_PI * x); +} + +inline float rcos( float t, float T, float alpha=1.0 ) +{ + if( t == 0 ) return 1.0; + float taT = T / (2.0 * alpha); + if( fabs(t) == taT ) return ((alpha/2.0) * sin(M_PI/(2.0*alpha))); + return (sin(M_PI*t/T)/(M_PI*t/T))*cos(alpha*M_PI*t/T)/(1.0-(t/taT)*(t/taT)); +} + +#endif diff --git a/android/app/src/main/cpp/dsp/morsedemod.cpp b/android/app/src/main/cpp/dsp/morsedemod.cpp new file mode 100644 index 0000000..014220e --- /dev/null +++ b/android/app/src/main/cpp/dsp/morsedemod.cpp @@ -0,0 +1,168 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/morse.h" + +#include "morsedemod.h" + +MESSAGE_CLASS_DEFINITION(MorseDemod::MsgReportIdent, Message) + +MorseDemod::MorseDemod() : + m_movingAverageIdent(5000), + m_prevBit(0), + m_bitTime(0) +{ +} + +void MorseDemod::reset() +{ + m_binSampleCnt = 0; + m_binCnt = 0; + m_identNoise = 0.0001f; + for (int i = 0; i < m_identBins; i++) + { + m_identMaxs[i] = 0.0f; + } + m_ident = ""; +} + +void MorseDemod::applyChannelSettings(int channelSampleRate) +{ + if (channelSampleRate <= 0) { + return; + } + m_samplesPerDot7wpm = channelSampleRate*60/(50*7); + m_samplesPerDot10wpm = channelSampleRate*60/(50*10); + + m_ncoIdent.setFreq(-1020, channelSampleRate); // +-50Hz source offset allowed + m_bandpassIdent.create(1001, channelSampleRate, 970.0f, 1070.0f); // Ident at 1020 + + m_lowpassIdent.create(301, channelSampleRate, 100.0f); + m_movingAverageIdent.resize(m_samplesPerDot10wpm/5); // Needs to be short enough for noise floor calculation + + reset(); +} + +void MorseDemod::applySettings(int identThreshold) +{ + m_identThreshold = identThreshold; + reset(); +} + +void MorseDemod::processOneSample(const Complex &magc) +{ + // Filter to remove voice + Complex c1 = m_bandpassIdent.filter(magc); + // Remove ident sub-carrier offset + c1 *= m_ncoIdent.nextIQ(); + // Filter other signals + Complex c2 = std::abs(m_lowpassIdent.filter(c1)); + + // Filter noise with moving average (moving average preserves edges) + m_movingAverageIdent(c2.real()); + Real mav = m_movingAverageIdent.asFloat(); + + // Calculate noise floor + if (mav > m_identMaxs[m_binCnt]) + m_identMaxs[m_binCnt] = mav; + m_binSampleCnt++; + if (m_binSampleCnt >= m_samplesPerDot10wpm/4) + { + // Calc minimum of maximums + m_identNoise = 1.0f; + for (int i = 0; i < m_identBins; i++) + { + m_identNoise = std::min(m_identNoise, m_identMaxs[i]); + } + m_binSampleCnt = 0; + m_binCnt++; + if (m_binCnt == m_identBins) + m_binCnt = 0; + m_identMaxs[m_binCnt] = 0.0f; + + // Prevent divide by zero + if (m_identNoise == 0.0f) + m_identNoise = 1e-20f; + } + + // CW demod + int bit = (mav / m_identNoise) >= m_identThreshold; + //m_stream << mav << "," << m_identNoise << "," << bit << "," << (mav / m_identNoise) << "\n"; + if ((m_prevBit == 0) && (bit == 1)) + { + if (m_bitTime > 7*m_samplesPerDot10wpm) + { + if (m_ident.trimmed().size() > 2) // Filter out noise that may appear as one or two characters + { + qDebug() << "MorseDemod::processOneSample:" << m_ident << " " << Morse::toString(m_ident); + + if (getMessageQueueToChannel()) + { + MorseDemod::MsgReportIdent *msg = MorseDemod::MsgReportIdent::create(m_ident); + getMessageQueueToChannel()->push(msg); + } + } + m_ident = ""; + } + else if (m_bitTime > 2.5*m_samplesPerDot10wpm) + { + m_ident.append(" "); + } + m_bitTime = 0; + } + else if (bit == 1) + { + m_bitTime++; + } + else if ((m_prevBit == 1) && (bit == 0)) + { + if (m_bitTime > 2*m_samplesPerDot10wpm) + { + m_ident.append("-"); + } + else if (m_bitTime > 0.2*m_samplesPerDot10wpm) + { + m_ident.append("."); + } + m_bitTime = 0; + } + else + { + m_bitTime++; + if (m_bitTime > 10*m_samplesPerDot7wpm) + { + m_ident = m_ident.simplified(); + if (m_ident.trimmed().size() > 2) // Filter out noise that may appear as one or two characters + { + qDebug() << "MorseDemod::processOneSample:" << m_ident << " " << Morse::toString(m_ident); + + if (getMessageQueueToChannel()) + { + MorseDemod::MsgReportIdent *msg = MorseDemod::MsgReportIdent::create(m_ident); + getMessageQueueToChannel()->push(msg); + } + + } + m_ident = ""; + m_bitTime = 0; + } + } + m_prevBit = bit; +} + diff --git a/android/app/src/main/cpp/dsp/morsedemod.h b/android/app/src/main/cpp/dsp/morsedemod.h new file mode 100644 index 0000000..c6865cf --- /dev/null +++ b/android/app/src/main/cpp/dsp/morsedemod.h @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020, 2023 Jon Beniston, M7RCE // +// Copyright (C) 2020, 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_MORSEDEMOD_H_ +#define SDRBASE_DSP_MORSEDEMOD_H_ + +#include + +#include "dsp/nco.h" +#include "dsp/firfilter.h" +#include "util/movingaverage.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "export.h" + +// Morse code demodulator for use with VOR and ILS +class SDRBASE_API MorseDemod { + +public: + class SDRBASE_API MsgReportIdent : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QString getIdent() const { return m_ident; } + + static MsgReportIdent* create(QString ident) + { + return new MsgReportIdent(ident); + } + + private: + QString m_ident; + + MsgReportIdent(QString ident) : + Message(), + m_ident(ident) + { + } + }; + + MorseDemod(); + void processOneSample(const Complex &magc); + void applyChannelSettings(int channelSampleRate); + void applySettings(int identThreshold); + void reset(); + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; } + MessageQueue *getMessageQueueToChannel() const { return m_messageQueueToChannel; } + +private: + + MessageQueue *m_messageQueueToChannel; + NCO m_ncoIdent; + Bandpass m_bandpassIdent; + Lowpass m_lowpassIdent; + MovingAverageUtilVar m_movingAverageIdent; + static const int m_identBins = 20; + Real m_identMaxs[m_identBins]; + Real m_identNoise; + int m_binSampleCnt; + int m_binCnt; + int m_samplesPerDot7wpm; + int m_samplesPerDot10wpm; + int m_prevBit; + int m_bitTime; + QString m_ident; + + int m_identThreshold; + +}; + +#endif /* SDRBASE_DSP_MORSEDEMOD_H_ */ + diff --git a/android/app/src/main/cpp/dsp/movingaverage.h b/android/app/src/main/cpp/dsp/movingaverage.h new file mode 100644 index 0000000..1b6a954 --- /dev/null +++ b/android/app/src/main/cpp/dsp/movingaverage.h @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_MOVINGAVERAGE_H +#define INCLUDE_MOVINGAVERAGE_H + +#include +#include +#include + +template class MovingAverage { +public: + MovingAverage(int historySize, Type initial) : m_index(0) + { + resize(historySize, initial); + } + + void resize(int historySize, Type initial) + { + m_history.resize(historySize); + std::fill(m_history.begin(), m_history.end(), initial); + m_sum = (Type) m_history.size() * initial; + m_index = 0; + } + + void feed(Type value) + { + Type& oldest = m_history[m_index]; + m_sum += value - oldest; + oldest = value; + + if (m_index < m_history.size() - 1) { + m_index++; + } else { + m_index = 0; + } + } + + void fill(Type value) + { + std::fill(m_history.begin(), m_history.end(), value); + m_sum = (Type) m_history.size() * value; + } + + Type average() const + { + return m_sum / (Type) m_history.size(); + } + + Type sum() const + { + return m_sum; + } + + int historySize() const + { + return m_history.size(); + } + +protected: + std::vector m_history; + Type m_sum; + uint32_t m_index; +}; + +#endif // INCLUDE_MOVINGAVERAGE_H diff --git a/android/app/src/main/cpp/dsp/nco.cpp b/android/app/src/main/cpp/dsp/nco.cpp new file mode 100644 index 0000000..3d28bbc --- /dev/null +++ b/android/app/src/main/cpp/dsp/nco.cpp @@ -0,0 +1,108 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "dsp/nco.h" + + +Real NCO::m_table[NCO::TableSize]; +bool NCO::m_tableInitialized = false; + +void NCO::initTable() +{ + if(m_tableInitialized) + return; + + for(int i = 0; i < TableSize; i++) + m_table[i] = cos((2.0 * M_PI * i) / TableSize); + + m_tableInitialized = true; +} + +NCO::NCO() +{ + initTable(); + m_phase = 0; + m_phaseIncrement = 0; +} + +void NCO::setFreq(Real freq, Real sampleRate) +{ + m_phaseIncrement = (freq * TableSize) / sampleRate; + qDebug("NCO freq: %f phase inc %d", freq, m_phaseIncrement); +} + +float NCO::next() +{ + nextPhase(); + return m_table[m_phase]; +} + +Complex NCO::nextIQ() +{ + nextPhase(); + return Complex(m_table[m_phase], -m_table[(m_phase + TableSize / 4) % TableSize]); +} + +Complex NCO::nextQI() +{ + nextPhase(); + return Complex(-m_table[(m_phase + TableSize / 4) % TableSize], m_table[m_phase]); +} + +void NCO::nextIQMul(Real& i, Real& q) +{ + nextPhase(); + Real x = i; + Real y = q; + const Real& u = m_table[m_phase]; + const Real& v = -m_table[(m_phase + TableSize / 4) % TableSize]; + i = x*u - y*v; + q = x*v + y*u; +} + +float NCO::get() +{ + return m_table[m_phase]; +} + +Complex NCO::getIQ() +{ + return Complex(m_table[m_phase], -m_table[(m_phase + TableSize / 4) % TableSize]); +} + +void NCO::getIQ(Complex& c) +{ + c.real(m_table[m_phase]); + c.imag(-m_table[(m_phase + TableSize / 4) % TableSize]); +} + +Complex NCO::getQI() +{ + return Complex(-m_table[(m_phase + TableSize / 4) % TableSize], m_table[m_phase]); +} + +void NCO::getQI(Complex& c) +{ + c.imag(m_table[m_phase]); + c.real(-m_table[(m_phase + TableSize / 4) % TableSize]); +} diff --git a/android/app/src/main/cpp/dsp/nco.h b/android/app/src/main/cpp/dsp/nco.h new file mode 100644 index 0000000..28956f3 --- /dev/null +++ b/android/app/src/main/cpp/dsp/nco.h @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_NCO_H +#define INCLUDE_NCO_H + +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API NCO { +private: + enum { + TableSize = (1 << 12), + }; + static Real m_table[TableSize]; + static bool m_tableInitialized; + + static void initTable(); + + int m_phaseIncrement; + int m_phase; + +public: + NCO(); + + void setFreq(Real freq, Real sampleRate); + void setPhase(int phase) { m_phase = phase; } + + void nextPhase() //!< Increment phase + { + m_phase += m_phaseIncrement; + while(m_phase >= TableSize) + m_phase -= TableSize; + while(m_phase < 0) + m_phase += TableSize; + } + + Real next(); //!< Return next real sample + Complex nextIQ(); //!< Return next complex sample + Complex nextQI(); //!< Return next complex sample (reversed) + void nextIQMul(Real& i, Real& q); //!< multiply I,Q separately with next sample + Real get(); //!< Return current real sample (no phase increment) + Complex getIQ(); //!< Return current complex sample (no phase increment) + void getIQ(Complex& c); //!< Sets to the current complex sample (no phase increment) + Complex getQI(); //!< Return current complex sample (no phase increment, reversed) + void getQI(Complex& c); //!< Sets to the current complex sample (no phase increment, reversed) +}; + +#endif // INCLUDE_NCO_H diff --git a/android/app/src/main/cpp/dsp/ncof.cpp b/android/app/src/main/cpp/dsp/ncof.cpp new file mode 100644 index 0000000..ebe2bc8 --- /dev/null +++ b/android/app/src/main/cpp/dsp/ncof.cpp @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2019, 2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "dsp/ncof.h" + +Real NCOF::m_table[NCOF::TableSize+1]; +bool NCOF::m_tableInitialized = false; +float NCOF::m_tableSizeLimit = (float) NCOF::TableSize; + +void NCOF::initTable() +{ + if(m_tableInitialized) { + return; + } + + for(int i = 0; i <= TableSize; i++) { + m_table[i] = cos((2.0 * M_PI * i) / TableSize); + } + + m_tableInitialized = true; +} + +NCOF::NCOF() +{ + initTable(); + m_phase = 0.0f; + m_phaseIncrement = 0.0f; +} + +void NCOF::setFreq(Real freq, Real sampleRate) +{ + m_phaseIncrement = sampleRate == 0 ? 0 : (freq * TableSize) / sampleRate; + qDebug("NCOF::setFreq: freq: %f sr: %f m_phaseIncrement: %f", freq, sampleRate, m_phaseIncrement); +} + +float NCOF::next() +{ + int phase = nextPhase(); + return m_table[phase]; +} + +Complex NCOF::nextIQ() +{ + int phase = nextPhase(); + return Complex(m_table[phase], -m_table[(phase + TableSize / 4) % TableSize]); +} + +Complex NCOF::nextIQ(float imbalance) +{ + int phase = nextPhase(); + int phaseQ = imbalance < 0.0 ? phase + (int) (imbalance*TableSize) : phase; + int phaseI = imbalance < 0.0 ? phase : phase + (int) (imbalance*TableSize); + return Complex(m_table[phaseI % TableSize], -m_table[(phaseQ + TableSize / 4) % TableSize]); +} + +Complex NCOF::nextQI() +{ + int phase = nextPhase(); + return Complex(-m_table[(phase + TableSize / 4) % TableSize], m_table[phase]); +} + +float NCOF::get() +{ + return m_table[(int) m_phase]; +} + +Complex NCOF::getIQ() +{ + return Complex(m_table[(int) m_phase], -m_table[((int) m_phase + TableSize / 4) % TableSize]); +} + +void NCOF::getIQ(Complex& c) +{ + c.real(m_table[(int) m_phase]); + c.imag(-m_table[((int) m_phase + TableSize / 4) % TableSize]); +} + +Complex NCOF::getQI() +{ + return Complex(-m_table[((int) m_phase + TableSize / 4) % TableSize], m_table[(int) m_phase]); +} + +void NCOF::getQI(Complex& c) +{ + c.imag(m_table[(int) m_phase]); + c.real(-m_table[((int) m_phase + TableSize / 4) % TableSize]); +} diff --git a/android/app/src/main/cpp/dsp/ncof.h b/android/app/src/main/cpp/dsp/ncof.h new file mode 100644 index 0000000..a2f6010 --- /dev/null +++ b/android/app/src/main/cpp/dsp/ncof.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_NCOF_H +#define INCLUDE_NCOF_H + +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API NCOF { +private: + enum { + TableSize = (1 << 12), + }; + static Real m_table[TableSize+1]; + static bool m_tableInitialized; + static float m_tableSizeLimit; + + static void initTable(); + + Real m_phaseIncrement; + Real m_phase; + +public: + NCOF(); + + void setFreq(Real freq, Real sampleRate); + void setPhase(Real phase) { m_phase = phase; } + + int nextPhase() //!< Increment phase and return its integer value + { + m_phase += m_phaseIncrement; + while(m_phase >= m_tableSizeLimit) { + m_phase -= TableSize; + } + while(m_phase < 0.0) { + m_phase += TableSize; + } + return (int) m_phase; + } + + Real next(); //!< Return next real sample + Complex nextIQ(); //!< Return next complex sample + Complex nextIQ(float imbalance); //!< Return next complex sample with an imbalance factor on I + Complex nextQI(); //!< Return next complex sample (reversed) + Real get(); //!< Return current real sample (no phase increment) + Complex getIQ(); //!< Return current complex sample (no phase increment) + void getIQ(Complex& c); //!< Sets to the current complex sample (no phase increment) + Complex getQI(); //!< Return current complex sample (no phase increment, reversed) + void getQI(Complex& c); //!< Sets to the current complex sample (no phase increment, reversed) +}; + +#endif // INCLUDE_NCO_H diff --git a/android/app/src/main/cpp/dsp/phasediscri.h b/android/app/src/main/cpp/dsp/phasediscri.h new file mode 100644 index 0000000..2d589eb --- /dev/null +++ b/android/app/src/main/cpp/dsp/phasediscri.h @@ -0,0 +1,214 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2017, 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSP_PHASEDISCRI_H_ +#define INCLUDE_DSP_PHASEDISCRI_H_ + +#include +#include "dsp/dsptypes.h" + +class PhaseDiscriminators +{ +public: + + PhaseDiscriminators() : + m_fmScaling(1.0f) + { + reset(); + } + + /** + * Reset stored values + */ + void reset() + { + m_m1Sample = 0; + m_m2Sample = 0; + m_fltPreviousI = 0.0f; + m_fltPreviousQ = 0.0f; + m_fltPreviousI2 = 0.0f; + m_fltPreviousQ2 = 0.0f; + m_prevArg = 0.0f; + } + + /** + * Scaling factor so that resulting excursion maps to [-1,+1] + */ + void setFMScaling(Real fmScaling) + { + m_fmScaling = fmScaling; + } + + /** + * Standard discriminator using atan2. On modern processors this is as efficient as the non atan2 one. + * This is better for high fidelity. + */ + Real phaseDiscriminator(const Complex& sample) + { + Complex d(std::conj(m_m1Sample) * sample); + m_m1Sample = sample; + return (std::atan2(d.imag(), d.real()) / M_PI) * m_fmScaling; + } + + /** + * Discriminator with phase detection using atan2 and frequency by derivation. + * This yields a precise deviation to sample rate ratio: Sample rate => +/-1.0 + */ + Real phaseDiscriminatorDelta(const Complex& sample, double& magsq, Real& fmDev) + { + Real fltI = sample.real(); + Real fltQ = sample.imag(); + magsq = fltI*fltI + fltQ*fltQ; + + Real curArg = atan2_approximation2((float) fltQ, (float) fltI); + fmDev = (curArg - m_prevArg) / M_PI; + m_prevArg = curArg; + + if (fmDev < -1.0f) { + fmDev += 2.0f; + } else if (fmDev > 1.0f) { + fmDev -= 2.0f; + } + + return fmDev * m_fmScaling; + } + + /** + * Alternative without atan at the expense of a slight distortion on very wideband signals + * http://www.embedded.com/design/configurable-systems/4212086/DSP-Tricks--Frequency-demodulation-algorithms- + * in addition it needs scaling by instantaneous magnitude squared and volume (0..10) adjustment factor + */ + Real phaseDiscriminator2(const Complex& sample) + { + Real ip = sample.real() - m_m2Sample.real(); + Real qp = sample.imag() - m_m2Sample.imag(); + Real h1 = m_m1Sample.real() * qp; + Real h2 = m_m1Sample.imag() * ip; + + m_m2Sample = m_m1Sample; + m_m1Sample = sample; + + //return ((h1 - h2) / M_PI_2) * m_fmScaling; + return (h1 - h2) * m_fmScaling; + } + + /** + * Second alternative + */ + Real phaseDiscriminator3(const Complex& sample, long double& magsq, Real& fltVal) + { + Real fltI = sample.real(); + Real fltQ = sample.imag(); + double fltNorm; + Real fltNormI; + Real fltNormQ; + //Real fltVal; + + magsq = fltI*fltI + fltQ*fltQ; + fltNorm = std::sqrt(magsq); + + fltNormI= fltI/fltNorm; + fltNormQ= fltQ/fltNorm; + + fltVal = m_fltPreviousI*(fltNormQ - m_fltPreviousQ2); + fltVal -= m_fltPreviousQ*(fltNormI - m_fltPreviousI2); + fltVal += 2.0f; + fltVal /= 4.0f; // normally it is /4 + + m_fltPreviousQ2 = m_fltPreviousQ; + m_fltPreviousI2 = m_fltPreviousI; + + m_fltPreviousQ = fltNormQ; + m_fltPreviousI = fltNormI; + + return fltVal * m_fmScaling; + } + +private: + Complex m_m1Sample; + Complex m_m2Sample; + Real m_fmScaling; + Real m_fltPreviousI; + Real m_fltPreviousQ; + Real m_fltPreviousI2; + Real m_fltPreviousQ2; + Real m_prevArg; + + float atan2_approximation1(float y, float x) + { + //http://pubs.opengroup.org/onlinepubs/009695399/functions/atan2.html + //Volkan SALMA + + const float ONEQTR_PI = M_PI / 4.0; + const float THRQTR_PI = 3.0 * M_PI / 4.0; + float r, angle; + float abs_y = std::fabs(y) + 1e-10f; // kludge to prevent 0/0 condition + if ( x < 0.0f ) + { + r = (x + abs_y) / (abs_y - x); + angle = THRQTR_PI; + } + else + { + r = (x - abs_y) / (x + abs_y); + angle = ONEQTR_PI; + } + angle += (0.1963f * r * r - 0.9817f) * r; + if ( y < 0.0f ) + return( -angle ); // negate if in quad III or IV + else + return( angle ); + + + } + + #define PI_FLOAT float(M_PI) + #define PIBY2_FLOAT float(M_PI_2) + // |error| < 0.005 + float atan2_approximation2( float y, float x ) + { + if ( x == 0.0f ) + { + if ( y > 0.0f ) return PIBY2_FLOAT; + if ( y == 0.0f ) return 0.0f; + return -PIBY2_FLOAT; + } + float atan; + float z = y/x; + if ( std::fabs( z ) < 1.0f ) + { + atan = z/(1.0f + 0.28f*z*z); + if ( x < 0.0f ) + { + if ( y < 0.0f ) return atan - PI_FLOAT; + return atan + PI_FLOAT; + } + } + else + { + atan = PIBY2_FLOAT - z/(z*z + 0.28f); + if ( y < 0.0f ) return atan - PI_FLOAT; + } + return atan; + } +}; + +#endif /* INCLUDE_DSP_PHASEDISCRI_H_ */ diff --git a/android/app/src/main/cpp/dsp/phaselock.cpp b/android/app/src/main/cpp/dsp/phaselock.cpp new file mode 100644 index 0000000..ad6f0b6 --- /dev/null +++ b/android/app/src/main/cpp/dsp/phaselock.cpp @@ -0,0 +1,367 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015, 2017-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2016 Ziga S // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "dsp/phaselock.h" + + +// Construct phase-locked loop. +PhaseLock::PhaseLock(Real freq, Real bandwidth, Real minsignal) +{ + /* + * This is a type-2, 4th order phase-locked loop. + * + * Open-loop transfer function: + * G(z) = K * (z - q1) / ((z - p1) * (z - p2) * (z - 1) * (z - 1)) + * K = 3.788 * (bandwidth * 2 * Pi)**3 + * q1 = exp(-0.1153 * bandwidth * 2*Pi) + * p1 = exp(-1.146 * bandwidth * 2*Pi) + * p2 = exp(-5.331 * bandwidth * 2*Pi) + * + * I don't understand what I'm doing; hopefully it will work. + */ + + // Set min/max locking frequencies. + m_minfreq = (freq - bandwidth) * 2.0 * M_PI; + m_maxfreq = (freq + bandwidth) * 2.0 * M_PI; + + // Set valid signal threshold. + m_minsignal = minsignal; + m_lock_delay = int(20.0 / bandwidth); + m_lock_cnt = 0; + m_pilot_level = 0; + m_psin = 0.0; + m_pcos = 1.0; + + // Create 2nd order filter for I/Q representation of phase error. + // Filter has two poles, unit DC gain. + double p1 = exp(-1.146 * bandwidth * 2.0 * M_PI); + double p2 = exp(-5.331 * bandwidth * 2.0 * M_PI); + m_phasor_a1 = - p1 - p2; + m_phasor_a2 = p1 * p2; + m_phasor_b0 = 1 + m_phasor_a1 + m_phasor_a2; + + // Create loop filter to stabilize the loop. + double q1 = exp(-0.1153 * bandwidth * 2.0 * M_PI); + m_loopfilter_b0 = 0.62 * bandwidth * 2.0 * M_PI; + m_loopfilter_b1 = - m_loopfilter_b0 * q1; + + // After the loop filter, the phase error is integrated to produce + // the frequency. Then the frequency is integrated to produce the phase. + // These integrators form the two remaining poles, both at z = 1. + + // Initialize frequency and phase. + m_freq = freq * 2.0 * M_PI; + m_phase = 0; + + m_phasor_i1 = 0; + m_phasor_i2 = 0; + m_phasor_q1 = 0; + m_phasor_q2 = 0; + m_loopfilter_x1 = 0; + + // Initialize PPS generator. + m_pilot_periods = 0; + m_pps_cnt = 0; + m_sample_cnt = 0; +} + + +void PhaseLock::configure(Real freq, Real bandwidth, Real minsignal) +{ + qDebug("PhaseLock::configure: freq: %f bandwidth: %f minsignal: %f", freq, bandwidth, minsignal); + /* + * This is a type-2, 4th order phase-locked loop. + * + * Open-loop transfer function: + * G(z) = K * (z - q1) / ((z - p1) * (z - p2) * (z - 1) * (z - 1)) + * K = 3.788 * (bandwidth * 2 * Pi)**3 + * q1 = exp(-0.1153 * bandwidth * 2*Pi) + * p1 = exp(-1.146 * bandwidth * 2*Pi) + * p2 = exp(-5.331 * bandwidth * 2*Pi) + * + * I don't understand what I'm doing; hopefully it will work. + */ + + // Set min/max locking frequencies. + m_minfreq = (freq - bandwidth) * 2.0 * M_PI; + m_maxfreq = (freq + bandwidth) * 2.0 * M_PI; + + // Set valid signal threshold. + m_minsignal = minsignal; + m_lock_delay = int(20.0 / bandwidth); + m_lock_cnt = 0; + m_pilot_level = 0; + + // Create 2nd order filter for I/Q representation of phase error. + // Filter has two poles, unit DC gain. + double p1 = exp(-1.146 * bandwidth * 2.0 * M_PI); + double p2 = exp(-5.331 * bandwidth * 2.0 * M_PI); + m_phasor_a1 = - p1 - p2; + m_phasor_a2 = p1 * p2; + m_phasor_b0 = 1 + m_phasor_a1 + m_phasor_a2; + + // Create loop filter to stabilize the loop. + double q1 = exp(-0.1153 * bandwidth * 2.0 * M_PI); + m_loopfilter_b0 = 0.62 * bandwidth * 2.0 * M_PI; + m_loopfilter_b1 = - m_loopfilter_b0 * q1; + + // After the loop filter, the phase error is integrated to produce + // the frequency. Then the frequency is integrated to produce the phase. + // These integrators form the two remaining poles, both at z = 1. + + // Initialize frequency and phase. + m_freq = freq * 2.0 * M_PI; + m_phase = 0; + + m_phasor_i1 = 0; + m_phasor_i2 = 0; + m_phasor_q1 = 0; + m_phasor_q2 = 0; + m_loopfilter_x1 = 0; + + // Initialize PPS generator. + m_pilot_periods = 0; + m_pps_cnt = 0; + m_sample_cnt = 0; +} + + +// Process samples. Bufferized version +void PhaseLock::process(const std::vector& samples_in, std::vector& samples_out) +{ + unsigned int n = samples_in.size(); + + samples_out.resize(n); + + bool was_locked = (m_lock_cnt >= m_lock_delay); + m_pps_events.clear(); + + if (n > 0) + m_pilot_level = 1000.0; + + for (unsigned int i = 0; i < n; i++) { + + // Generate locked pilot tone. + Real psin = sin(m_phase); + Real pcos = cos(m_phase); + + // Generate double-frequency output. + // sin(2*x) = 2 * sin(x) * cos(x) + samples_out[i] = 2 * psin * pcos; + + // Multiply locked tone with input. + Real x = samples_in[i]; + Real phasor_i = psin * x; + Real phasor_q = pcos * x; + + // Run IQ phase error through low-pass filter. + phasor_i = m_phasor_b0 * phasor_i + - m_phasor_a1 * m_phasor_i1 + - m_phasor_a2 * m_phasor_i2; + phasor_q = m_phasor_b0 * phasor_q + - m_phasor_a1 * m_phasor_q1 + - m_phasor_a2 * m_phasor_q2; + m_phasor_i2 = m_phasor_i1; + m_phasor_i1 = phasor_i; + m_phasor_q2 = m_phasor_q1; + m_phasor_q1 = phasor_q; + + // Convert I/Q ratio to estimate of phase error. + Real phase_err; + if (phasor_i > std::abs(phasor_q)) { + // We are within +/- 45 degrees from lock. + // Use simple linear approximation of arctan. + phase_err = phasor_q / phasor_i; + } else if (phasor_q > 0) { + // We are lagging more than 45 degrees behind the input. + phase_err = 1; + } else { + // We are more than 45 degrees ahead of the input. + phase_err = -1; + } + + // Detect pilot level (conservative). + m_pilot_level = std::min(m_pilot_level, phasor_i); + + // Run phase error through loop filter and update frequency estimate. + m_freq += m_loopfilter_b0 * phase_err + + m_loopfilter_b1 * m_loopfilter_x1; + m_loopfilter_x1 = phase_err; + + // Limit frequency to allowable range. + m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); + + // Update locked phase. + m_phase += m_freq; + if (m_phase > 2.0 * M_PI) { + m_phase -= 2.0 * M_PI; + m_pilot_periods++; + + // Generate pulse-per-second. + if (m_pilot_periods == pilot_frequency) { + m_pilot_periods = 0; + if (was_locked) { + struct PpsEvent ev; + ev.pps_index = m_pps_cnt; + ev.sample_index = m_sample_cnt + i; + ev.block_position = double(i) / double(n); + m_pps_events.push_back(ev); + m_pps_cnt++; + } + } + } + } + + // Update lock status. + if (2 * m_pilot_level > m_minsignal) { + if (m_lock_cnt < m_lock_delay) + m_lock_cnt += n; + } else { + m_lock_cnt = 0; + } + + // Drop PPS events when pilot not locked. + if (m_lock_cnt < m_lock_delay) { + m_pilot_periods = 0; + m_pps_cnt = 0; + m_pps_events.clear(); + } + + // Update sample counter. + m_sample_cnt += n; +} + + +// Process samples. Multiple output +void PhaseLock::process(const Real& sample_in, Real *samples_out) +{ + m_pps_events.clear(); + + // Generate locked pilot tone. + m_psin = sin(m_phase); + m_pcos = cos(m_phase); + + // Generate output + processPhase(samples_out); + + // Multiply locked tone with input. + Real phasor_i = m_psin * sample_in; + Real phasor_q = m_pcos * sample_in; + + // Actual PLL + process_phasor(phasor_i, phasor_q); +} + +void PhaseLock::process(const Real& real_in, const Real& imag_in, Real *samples_out) +{ + m_pps_events.clear(); + + // Generate locked pilot tone. + m_psin = sin(m_phase); + m_pcos = cos(m_phase); + + // Generate output + processPhase(samples_out); + + // Multiply locked tone with input. + Real phasor_i = m_psin * real_in - m_pcos * imag_in; + Real phasor_q = m_pcos * real_in + m_psin * imag_in; + + // Actual PLL + process_phasor(phasor_i, phasor_q); +} + +void PhaseLock::process_phasor(Real& phasor_i, Real& phasor_q) +{ + // Run IQ phase error through low-pass filter. + phasor_i = m_phasor_b0 * phasor_i + - m_phasor_a1 * m_phasor_i1 + - m_phasor_a2 * m_phasor_i2; + phasor_q = m_phasor_b0 * phasor_q + - m_phasor_a1 * m_phasor_q1 + - m_phasor_a2 * m_phasor_q2; + m_phasor_i2 = m_phasor_i1; + m_phasor_i1 = phasor_i; + m_phasor_q2 = m_phasor_q1; + m_phasor_q1 = phasor_q; + + // Convert I/Q ratio to estimate of phase error. + Real phase_err; + if (phasor_i > std::abs(phasor_q)) { + // We are within +/- 45 degrees from lock. + // Use simple linear approximation of arctan. + phase_err = phasor_q / phasor_i; + } else if (phasor_q > 0) { + // We are lagging more than 45 degrees behind the input. + phase_err = 1; + } else { + // We are more than 45 degrees ahead of the input. + phase_err = -1; + } + + // Detect pilot level (conservative). + // m_pilot_level = std::min(m_pilot_level, phasor_i); + m_pilot_level = phasor_i; + + // Run phase error through loop filter and update frequency estimate. + m_freq += m_loopfilter_b0 * phase_err + + m_loopfilter_b1 * m_loopfilter_x1; + m_loopfilter_x1 = phase_err; + + // Limit frequency to allowable range. + m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); + + // Update locked phase. + m_phase += m_freq; + if (m_phase > 2.0 * M_PI) + { + m_phase -= 2.0 * M_PI; + m_pilot_periods++; + + // Generate pulse-per-second. + if (m_pilot_periods == pilot_frequency) + { + m_pilot_periods = 0; + } + } + + // Update lock status. + if (2 * m_pilot_level > m_minsignal) + { + if (m_lock_cnt < m_lock_delay) + { + m_lock_cnt += 1; // n + } + } + else + { + m_lock_cnt = 0; + } + + // Drop PPS events when pilot not locked. + if (m_lock_cnt < m_lock_delay) { + m_pilot_periods = 0; + m_pps_cnt = 0; + m_pps_events.clear(); + } + + // Update sample counter. + m_sample_cnt += 1; // n +} diff --git a/android/app/src/main/cpp/dsp/phaselock.h b/android/app/src/main/cpp/dsp/phaselock.h new file mode 100644 index 0000000..b245c07 --- /dev/null +++ b/android/app/src/main/cpp/dsp/phaselock.h @@ -0,0 +1,186 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SDRBASE_DSP_PHASELOCK +#define INCLUDE_SDRBASE_DSP_PHASELOCK + +#include +#include "dsp/dsptypes.h" +#include "export.h" + +/** Phase-locked loop mainly for broadcadt FM stereo pilot. */ +class SDRBASE_API PhaseLock +{ +public: + + /** Expected pilot frequency (used for PPS events). */ + static const int pilot_frequency = 19000; + + /** Timestamp event produced once every 19000 pilot periods. */ + struct PpsEvent + { + quint64 pps_index; + quint64 sample_index; + double block_position; + }; + + /** + * Construct phase-locked loop. + * + * freq :: 19 kHz center frequency relative to sample freq + * (0.5 is Nyquist) + * bandwidth :: bandwidth relative to sample frequency + * minsignal :: minimum pilot amplitude + */ + PhaseLock(Real freq, Real bandwidth, Real minsignal); + + virtual ~PhaseLock() + {} + + /** + * Change phase locked loop parameters + * + * freq :: 19 kHz center frequency relative to sample freq + * (0.5 is Nyquist) + * bandwidth :: bandwidth relative to sample frequency + * minsignal :: minimum pilot amplitude + */ + void configure(Real freq, Real bandwidth, Real minsignal); + + /** + * Process samples and extract 19 kHz pilot tone. + * Generate phase-locked 38 kHz tone with unit amplitude. + * Bufferized version with input and output vectors + */ + void process(const std::vector& samples_in, std::vector& samples_out); + + /** + * Process samples and track a pilot tone. Generate samples for single or multiple phase-locked + * signals. Implement the processPhase virtual method to produce the output samples. + * In flow version. Ex: Use 19 kHz stereo pilot tone to generate 38 kHz (stereo) and 57 kHz + * pilots (see RDSPhaseLock class below). + * This is the in flow version + */ + void process(const Real& sample_in, Real *samples_out); + void process(const Real& real_in, const Real& imag_in, Real *samples_out); + + /** Return true if the phase-locked loop is locked. */ + bool locked() const + { + return m_lock_cnt >= m_lock_delay; + } + + /** Return detected amplitude of pilot signal. */ + Real get_pilot_level() const + { + return 2 * m_pilot_level; + } + +protected: + Real m_phase; + Real m_psin; + Real m_pcos; + /** + * Callback method to produce multiple outputs from the current phase value in m_phase + * and/or the sin and cos values in m_psin and m_pcos + */ + virtual void processPhase(Real *samples_out) const { (void) samples_out; } + +private: + Real m_minfreq, m_maxfreq; + Real m_phasor_b0, m_phasor_a1, m_phasor_a2; + Real m_phasor_i1, m_phasor_i2, m_phasor_q1, m_phasor_q2; + Real m_loopfilter_b0, m_loopfilter_b1; + Real m_loopfilter_x1; + Real m_freq; + Real m_minsignal; + Real m_pilot_level; + int m_lock_delay; + int m_lock_cnt; + int m_pilot_periods; + quint64 m_pps_cnt; + quint64 m_sample_cnt; + std::vector m_pps_events; + + void process_phasor(Real& phasor_i, Real& phasor_q); +}; + +class SimplePhaseLock : public PhaseLock +{ +public: + SimplePhaseLock(Real freq, Real bandwidth, Real minsignal) : + PhaseLock(freq, bandwidth, minsignal) + {} + + virtual ~SimplePhaseLock() + {} + +protected: + virtual void processPhase(Real *samples_out) const + { + samples_out[0] = m_psin; // f Pilot + samples_out[1] = m_pcos; // f Pilot + } +}; + +class StereoPhaseLock : public PhaseLock +{ +public: + StereoPhaseLock(Real freq, Real bandwidth, Real minsignal) : + PhaseLock(freq, bandwidth, minsignal) + {} + + virtual ~StereoPhaseLock() + {} + +protected: + virtual void processPhase(Real *samples_out) const + { + samples_out[0] = m_psin; // f Pilot + // Generate double-frequency output. + // sin(2*x) = 2 * sin(x) * cos(x) + samples_out[1] = 2.0 * m_psin * m_pcos; // 2f Pilot sin + // cos(2*x) = 2 * cos(x) * cos(x) - 1 + samples_out[2] = (2.0 * m_pcos * m_pcos) - 1.0; // 2f Pilot cos + } +}; + + +class RDSPhaseLock : public PhaseLock +{ +public: + RDSPhaseLock(Real freq, Real bandwidth, Real minsignal) : + PhaseLock(freq, bandwidth, minsignal) + {} + + virtual ~RDSPhaseLock() + {} + +protected: + virtual void processPhase(Real *samples_out) const + { + samples_out[0] = m_psin; // Pilot signal (f) + // Generate double-frequency output. + // sin(2*x) = 2 * sin(x) * cos(x) + samples_out[1] = 2.0 * m_psin * m_pcos; // Pilot signal (2f) + // cos(2*x) = 2 * cos(x) * cos(x) - 1 + samples_out[2] = (2.0 * m_pcos * m_pcos) - 1.0; // 2f Pilot cos + samples_out[3] = m_phase; // Pilot phase + } +}; + +#endif // INCLUDE_SDRBASE_DSP_PHASELOCK diff --git a/android/app/src/main/cpp/dsp/phaselockcomplex.cpp b/android/app/src/main/cpp/dsp/phaselockcomplex.cpp new file mode 100644 index 0000000..f9d453d --- /dev/null +++ b/android/app/src/main/cpp/dsp/phaselockcomplex.cpp @@ -0,0 +1,226 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "phaselockcomplex.h" + +PhaseLockComplex::PhaseLockComplex() : + m_a1(1.0), + m_a2(1.0), + m_b0(1.0), + m_b1(1.0), + m_b2(1.0), + m_v0(0.0), + m_v1(0.0), + m_v2(0.0), + m_deltaPhi(0.0), + m_phiHat(0.0), + m_phiHatPrev(0.0), + m_y(1.0, 0.0), + m_p(1.0, 0.0), + m_yRe(1.0), + m_yIm(0.0), + m_freq(0.0), + m_freqPrev(0.0), + m_freqTest(0.0), + m_lockCount(0), + m_lockFreq(0.026f), + m_pskOrder(1), + m_lockTime(480), + m_lockTimeCount(0) +{ +} + +void PhaseLockComplex::computeCoefficients(Real wn, Real zeta, Real K) +{ + double t1 = K/(wn*wn); // + double t2 = 2*zeta/wn - 1/K; // + + double b0 = 2*K*(1.+t2/2.0f); + double b1 = 2*K*2.; + double b2 = 2*K*(1.-t2/2.0f); + + double a0 = 1 + t1/2.0f; + double a1 = -t1; + double a2 = -1 + t1/2.0f; + + qDebug("PhaseLockComplex::computeCoefficients: b_raw: %f %f %f", b0, b1, b2); + qDebug("PhaseLockComplex::computeCoefficients: a_raw: %f %f %f", a0, a1, a2); + + m_b0 = b0 / a0; + m_b1 = b1 / a0; + m_b2 = b2 / a0; + + // a0 = 1.0 is implied + m_a1 = a1 / a0; + m_a2 = a2 / a0; + + qDebug("PhaseLockComplex::computeCoefficients: b: %f %f %f", m_b0, m_b1, m_b2); + qDebug("PhaseLockComplex::computeCoefficients: a: 1.0 %f %f", m_a1, m_a2); + + reset(); +} + +void PhaseLockComplex::setPskOrder(unsigned int order) +{ + m_pskOrder = order > 0 ? order : 1; + reset(); +} + +void PhaseLockComplex::setSampleRate(unsigned int sampleRate) +{ + m_lockTime = sampleRate / 100; // 10ms for order 1 + m_lockFreq = (2.0*M_PI*(m_pskOrder > 1 ? 6.0 : 1.0)) / sampleRate; // +/- 6 Hz frequency swing + reset(); +} + +void PhaseLockComplex::reset() +{ + // reset filter accumulators and phase + m_v0 = 0.0f; + m_v1 = 0.0f; + m_v2 = 0.0f; + m_deltaPhi = 0.0f; + m_phiHat = 0.0f; + m_phiHatPrev = 0.0f; + m_y.real(1.0); + m_y.imag(0.0); + m_p.real(1.0); + m_p.imag(0.0); + m_yRe = 1.0f; + m_yIm = 0.0f; + m_freq = 0.0f; + m_freqPrev = 0.0f; + m_freqTest = 0.0f; + m_lockCount = 0; + m_lockTimeCount = 0; +} + +void PhaseLockComplex::feed(float re, float im) +{ + m_yRe = cos(m_phiHat); + m_yIm = sin(m_phiHat); + m_y.real(m_yRe); + m_y.imag(m_yIm); + std::complex x(re, im); + m_deltaPhi = std::arg(x * std::conj(m_y)); + + // bring phase 0 on any of the PSK symbols + if (m_pskOrder > 1) { + m_deltaPhi = normalizeAngle(m_pskOrder*m_deltaPhi); + } + + // advance buffer + m_v2 = m_v1; // shift center register to upper register + m_v1 = m_v0; // shift lower register to center register + + // compute new lower register + m_v0 = m_deltaPhi - m_v1*m_a1 - m_v2*m_a2; + + // compute new output + m_phiHat = m_v0*m_b0 + m_v1*m_b1 + m_v2*m_b2; + + // prevent saturation + if (m_phiHat > 2.0*M_PI) + { + m_v0 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_v1 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_v2 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_phiHat -= 2.0*M_PI; + } + + if (m_phiHat < -2.0*M_PI) + { + m_v0 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_v1 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_v2 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_phiHat += 2.0*M_PI; + } + + // lock and frequency estimation + if (m_pskOrder > 1) + { + float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); + m_freq = m_expAvg.feed(dPhi); + + if (m_lockTimeCount < m_lockTime-1) + { + m_lockTimeCount++; + } + else + { + float dF = m_freq - m_freqTest; + + if ((dF > -m_lockFreq) && (dF < m_lockFreq)) + { + if (m_lockCount < 20) { + m_lockCount++; + } + } + else + { + if (m_lockCount > 0) { + m_lockCount--; + } + } + + m_freqTest = m_freq; + m_lockTimeCount = 0; + } + + m_phiHatPrev = m_phiHat; + } + else + { + m_freqTest = normalizeAngle(m_phiHat - m_phiHatPrev); + m_freq = m_expAvg.feed(m_freqTest); + + float dFreq = m_freqTest - m_freqPrev; + + if ((dFreq > -0.01) && (dFreq < 0.01)) + { + if (m_lockCount < (m_lockTime-1)) { // [0..479] + m_lockCount++; + } + } + else + { + m_lockCount = 0; + } + + m_phiHatPrev = m_phiHat; + m_freqPrev = m_freqTest; + } +} + +float PhaseLockComplex::normalizeAngle(float angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} diff --git a/android/app/src/main/cpp/dsp/phaselockcomplex.h b/android/app/src/main/cpp/dsp/phaselockcomplex.h new file mode 100644 index 0000000..03ae46a --- /dev/null +++ b/android/app/src/main/cpp/dsp/phaselockcomplex.h @@ -0,0 +1,110 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_PHASELOCKCOMPLEX_H_ +#define SDRBASE_DSP_PHASELOCKCOMPLEX_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +/** General purpose Phase-locked loop using complex analytic signal input. */ +class SDRBASE_API PhaseLockComplex +{ +public: + PhaseLockComplex(); + /** Compute loop filter parameters (active PI design) + * \param wn PLL bandwidth relative to Nyquist frequency + * \param zeta PLL damping factor + * \param K PLL loop gain + * */ + void computeCoefficients(Real wn, Real zeta, Real K); + /** Set the PSK order for the phase comparator + * \param order 0,1: no PSK (CW), 2: BPSK, 4: QPSK, 8: 8-PSK, ... use powers of two for real cases + */ + void setPskOrder(unsigned int order); + /** Set sample rate information only for frequency and lock condition calculation */ + void setSampleRate(unsigned int sampleRate); + void reset(); + /** Feed PLL with a new signa sample */ + void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_yRe; } + float getImag() const { return m_yIm; } + bool locked() const { return m_pskOrder > 1 ? m_lockCount > 10 : m_lockCount > m_lockTime-2; } + float getFreq() const { return m_freq; } + float getDeltaPhi() const { return m_deltaPhi; } + float getPhiHat() const { return m_phiHat; } + +private: + class ExpAvg + { + public: + ExpAvg() : m_a0(0.999), m_a1(0.001), m_y1(0.0f) + {} + void setAlpha(const float& alpha) + { + m_a0 = alpha; + m_a1 = 1.0 - alpha; + } + float feed(const float& x) + { + float y = m_a1*x + m_a0*m_y1; + m_y1 = y; + return y; + } + private: + float m_a0; //!< alpha + float m_a1; //!< 1 - alpha + float m_y1; + }; + + /** Normalize angle in radians into the [-pi,+pi] region */ + static float normalizeAngle(float angle); + + // a0 = 1 is implied + float m_a1; + float m_a2; + float m_b0; + float m_b1; + float m_b2; + float m_v0; + float m_v1; + float m_v2; + float m_deltaPhi; + float m_phiHat; + float m_phiHatPrev; + std::complex m_y; + std::complex m_p; + float m_yRe; + float m_yIm; + float m_freq; + float m_freqPrev; + float m_freqTest; + int m_lockCount; + float m_lockFreq; + unsigned int m_pskOrder; + int m_lockTime; + int m_lockTimeCount; + ExpAvg m_expAvg; +}; + +#endif /* SDRBASE_DSP_PHASELOCKCOMPLEX_H_ */ diff --git a/android/app/src/main/cpp/dsp/physicalunit.h b/android/app/src/main/cpp/dsp/physicalunit.h new file mode 100644 index 0000000..71849cc --- /dev/null +++ b/android/app/src/main/cpp/dsp/physicalunit.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PHYSICALUNIT_H +#define INCLUDE_PHYSICALUNIT_H + +namespace Unit { + enum Physical { + None, + Frequency, + Information, + Percent, + Decibel, + DecibelMilliWatt, + DecibelMicroVolt, + AngleDegrees, + Time, + TimeHMS, + Volt, + Scientific, + SUnits + }; +}; + +#endif // INCLUDE_PHYSICALUNIT_H diff --git a/android/app/src/main/cpp/dsp/projector.cpp b/android/app/src/main/cpp/dsp/projector.cpp new file mode 100644 index 0000000..135d8b6 --- /dev/null +++ b/android/app/src/main/cpp/dsp/projector.cpp @@ -0,0 +1,416 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019, 2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "projector.h" + +Projector::Projector(ProjectionType projectionType) : + m_projectionType(projectionType), + m_prevArg(0.0f), + m_cache(nullptr), + m_cacheMaster(true) +{ +} + +Projector::~Projector() +{ +} + +Real Projector::run(const Sample& s) +{ + Real v; + + if ((m_cache) && !m_cacheMaster) { + return m_cache[(int) m_projectionType]; + } + else + { + switch (m_projectionType) + { + case ProjectionImag: + v = s.m_imag / SDR_RX_SCALEF; + break; + case ProjectionMagLin: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + v = std::sqrt(magsq); + } + break; + case ProjectionMagSq: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + v = re*re + im*im; + } + break; + case ProjectionDMagSq: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + Real curMagSq = re*re + im*im; + v = curMagSq - m_prevVal; + m_prevVal = curMagSq; + } + break; + case ProjectionMagDB: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + v = log10f(magsq) * 10.0f; + } + break; + case ProjectionPhase: + v = std::atan2((float) s.m_imag, (float) s.m_real) / M_PI; // normalize + break; + case ProjectionDOAP: + { + // calculate phase. Assume phase difference between two sources at half wavelength distance with sources axis as reference (positive side) + // cos(theta) = phi / 2*pi*k + Real p = std::atan2((float) s.m_imag, (float) s.m_real); // do not normalize phi (phi in -pi..+pi) + v = acos(p/M_PI) / M_PI; // normalize theta + } + break; + case ProjectionDOAN: + { + // calculate phase. Assume phase difference between two sources at half wavelength distance with sources axis as reference (negative source) + Real p = std::atan2((float) s.m_imag, (float) s.m_real); // do not normalize phi (phi in -pi..+pi) + v = -acos(p/M_PI) / M_PI; // normalize theta + } + break; + case ProjectionDPhase: + { + Real curArg = std::atan2((float) s.m_imag, (float) s.m_real); + Real dPhi = (curArg - m_prevArg) / M_PI; + m_prevArg = curArg; + + if (dPhi < -1.0f) { + dPhi += 2.0f; + } else if (dPhi > 1.0f) { + dPhi -= 2.0f; + } + + v = dPhi; + } + break; + case ProjectionBPSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(2*arg) / (2.0*M_PI); // generic estimation around 0 + // mapping on 2 symbols + if (arg < -M_PI/2) { + v -= 1.0/2; + } else if (arg < M_PI/2) { + v += 1.0/2; + } else if (arg < M_PI) { + v -= 1.0/2; + } + } + break; + case ProjectionQPSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(4*arg) / (4.0*M_PI); // generic estimation around 0 + // mapping on 4 symbols + if (arg < -3*M_PI/4) { + v -= 3.0/4; + } else if (arg < -M_PI/4) { + v -= 1.0/4; + } else if (arg < M_PI/4) { + v += 1.0/4; + } else if (arg < 3*M_PI/4) { + v += 3.0/4; + } else if (arg < M_PI) { + v -= 3.0/4; + } + } + break; + case Projection8PSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(8*arg) / (8.0*M_PI); // generic estimation around 0 + // mapping on 8 symbols + if (arg < -7*M_PI/8) { + v -= 7.0/8; + } else if (arg < -5*M_PI/8) { + v -= 5.0/8; + } else if (arg < -3*M_PI/8) { + v -= 3.0/8; + } else if (arg < -M_PI/8) { + v -= 1.0/8; + } else if (arg < M_PI/8) { + v += 1.0/8; + } else if (arg < 3*M_PI/8) { + v += 3.0/8; + } else if (arg < 5*M_PI/8) { + v += 5.0/8; + } else if (arg < 7*M_PI/8) { + v += 7.0/8; + } else if (arg < M_PI) { + v -= 7.0/8; + } + } + break; + case Projection16PSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(16*arg) / (16.0*M_PI); // generic estimation around 0 + // mapping on 16 symbols + if (arg < -15*M_PI/16) { + v -= 15.0/16; + } else if (arg < -13*M_PI/16) { + v -= 13.0/6; + } else if (arg < -11*M_PI/16) { + v -= 11.0/16; + } else if (arg < -9*M_PI/16) { + v -= 9.0/16; + } else if (arg < -7*M_PI/16) { + v -= 7.0/16; + } else if (arg < -5*M_PI/16) { + v -= 5.0/16; + } else if (arg < -3*M_PI/16) { + v -= 3.0/16; + } else if (arg < -M_PI/16) { + v -= 1.0/16; + } else if (arg < M_PI/16) { + v += 1.0/16; + } else if (arg < 3.0*M_PI/16) { + v += 3.0/16; + } else if (arg < 5.0*M_PI/16) { + v += 5.0/16; + } else if (arg < 7.0*M_PI/16) { + v += 7.0/16; + } else if (arg < 9.0*M_PI/16) { + v += 9.0/16; + } else if (arg < 11.0*M_PI/16) { + v += 11.0/16; + } else if (arg < 13.0*M_PI/16) { + v += 13.0/16; + } else if (arg < 15.0*M_PI/16) { + v += 15.0/16; + } else if (arg < M_PI) { + v -= 15.0/16; + } + } + break; + case ProjectionReal: + default: + v = s.m_real / SDR_RX_SCALEF; + break; + } + + if (m_cache) { + m_cache[(int) m_projectionType] = v; + } + + return v; + } +} + +Real Projector::run(const std::complex& s) +{ + Real v; + + if ((m_cache) && !m_cacheMaster) { + return m_cache[(int) m_projectionType]; + } + else + { + switch (m_projectionType) + { + case ProjectionImag: + v = s.imag(); + break; + case ProjectionMagLin: + v = std::abs(s); + break; + case ProjectionMagSq: + v = std::norm(s); + break; + case ProjectionDMagSq: + { + Real curMagSq = std::norm(s); + v = curMagSq - m_prevVal; + m_prevVal = curMagSq; + } + break; + case ProjectionMagDB: + { + Real magsq = std::norm(s); + v = log10f(magsq) * 10.0f; + } + break; + case ProjectionPhase: + v = std::arg(s) / M_PI; // normalize + break; + case ProjectionDOAP: + { + // calculate phase. Assume phase difference between two sources at half wavelength distance with sources axis as reference (positive side) + // cos(theta) = phi / 2*pi*k + Real p = std::arg(s); // do not normalize phi (phi in -pi..+pi) + v = acos(p/M_PI) / M_PI; // normalize theta + } + break; + case ProjectionDOAN: + { + // calculate phase. Assume phase difference between two sources at half wavelength distance with sources axis as reference (negative source) + Real p = std::arg(s); // do not normalize phi (phi in -pi..+pi) + v = -acos(p/M_PI) / M_PI; // normalize theta + } + break; + case ProjectionDPhase: + { + Real curArg = std::arg(s); + Real dPhi = (curArg - m_prevArg) / M_PI; + m_prevArg = curArg; + + if (dPhi < -1.0f) { + dPhi += 2.0f; + } else if (dPhi > 1.0f) { + dPhi -= 2.0f; + } + + v = dPhi; + } + break; + case ProjectionBPSK: + { + Real arg = std::arg(s); + v = normalizeAngle(2*arg) / (2.0*M_PI); // generic estimation around 0 + // mapping on 2 symbols + if (arg < -M_PI/2) { + v -= 1.0/2; + } else if (arg < M_PI/2) { + v += 1.0/2; + } else if (arg < M_PI) { + v -= 1.0/2; + } + } + break; + case ProjectionQPSK: + { + Real arg = std::arg(s); + v = normalizeAngle(4*arg) / (4.0*M_PI); // generic estimation around 0 + // mapping on 4 symbols + if (arg < -3*M_PI/4) { + v -= 3.0/4; + } else if (arg < -M_PI/4) { + v -= 1.0/4; + } else if (arg < M_PI/4) { + v += 1.0/4; + } else if (arg < 3*M_PI/4) { + v += 3.0/4; + } else if (arg < M_PI) { + v -= 3.0/4; + } + } + break; + case Projection8PSK: + { + Real arg = std::arg(s); + v = normalizeAngle(8*arg) / (8.0*M_PI); // generic estimation around 0 + // mapping on 8 symbols + if (arg < -7*M_PI/8) { + v -= 7.0/8; + } else if (arg < -5*M_PI/8) { + v -= 5.0/8; + } else if (arg < -3*M_PI/8) { + v -= 3.0/8; + } else if (arg < -M_PI/8) { + v -= 1.0/8; + } else if (arg < M_PI/8) { + v += 1.0/8; + } else if (arg < 3*M_PI/8) { + v += 3.0/8; + } else if (arg < 5*M_PI/8) { + v += 5.0/8; + } else if (arg < 7*M_PI/8) { + v += 7.0/8; + } else if (arg < M_PI) { + v -= 7.0/8; + } + } + break; + case Projection16PSK: + { + Real arg = std::arg(s); + v = normalizeAngle(16*arg) / (16.0*M_PI); // generic estimation around 0 + // mapping on 16 symbols + if (arg < -15*M_PI/16) { + v -= 15.0/16; + } else if (arg < -13*M_PI/16) { + v -= 13.0/6; + } else if (arg < -11*M_PI/16) { + v -= 11.0/16; + } else if (arg < -9*M_PI/16) { + v -= 9.0/16; + } else if (arg < -7*M_PI/16) { + v -= 7.0/16; + } else if (arg < -5*M_PI/16) { + v -= 5.0/16; + } else if (arg < -3*M_PI/16) { + v -= 3.0/16; + } else if (arg < -M_PI/16) { + v -= 1.0/16; + } else if (arg < M_PI/16) { + v += 1.0/16; + } else if (arg < 3.0*M_PI/16) { + v += 3.0/16; + } else if (arg < 5.0*M_PI/16) { + v += 5.0/16; + } else if (arg < 7.0*M_PI/16) { + v += 7.0/16; + } else if (arg < 9.0*M_PI/16) { + v += 9.0/16; + } else if (arg < 11.0*M_PI/16) { + v += 11.0/16; + } else if (arg < 13.0*M_PI/16) { + v += 13.0/16; + } else if (arg < 15.0*M_PI/16) { + v += 15.0/16; + } else if (arg < M_PI) { + v -= 15.0/16; + } + } + break; + case ProjectionReal: + default: + v = s.real(); + break; + } + + if (m_cache) { + m_cache[(int) m_projectionType] = v; + } + + return v; + } +} + +Real Projector::normalizeAngle(Real angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} diff --git a/android/app/src/main/cpp/dsp/projector.h b/android/app/src/main/cpp/dsp/projector.h new file mode 100644 index 0000000..10f3a2b --- /dev/null +++ b/android/app/src/main/cpp/dsp/projector.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_PROJECTOR_H +#define SDRBASE_DSP_PROJECTOR_H + +#include "export.h" +#include "dsp/dsptypes.h" + +class SDRBASE_API Projector +{ +public: + enum ProjectionType + { + ProjectionReal = 0, //!< Extract real part + ProjectionImag, //!< Extract imaginary part + ProjectionMagLin, //!< Calculate linear magnitude or modulus + ProjectionMagSq, //!< Calculate linear squared magnitude or power + ProjectionDMagSq, //!< Calculate time derivative of linear squared magnitude or power + ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude + ProjectionPhase, //!< Calculate phase + ProjectionDOAP, //!< Calculate ambiguous DOA from phase as phase difference (assuming positive) + ProjectionDOAN, //!< Calculate ambiguous DOA from phase as phase difference (assuming negative) + ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate + ProjectionBPSK, //!< Phase comparator BPSK evaluation + ProjectionQPSK, //!< Phase comparator QPSK evaluation + Projection8PSK, //!< Phase comparator 8-PSK evaluation + Projection16PSK, //!< Phase comparator 16-PSK evaluation + nbProjectionTypes //!< Gives the number of projections in the enum + }; + + Projector(ProjectionType projectionType); + ~Projector(); + + ProjectionType getProjectionType() const { return m_projectionType; } + void settProjectionType(ProjectionType projectionType) { m_projectionType = projectionType; } + void setCache(Real *cache) { m_cache = cache; } + void setCacheMaster(bool cacheMaster) { m_cacheMaster = cacheMaster; } + + Real run(const Sample& s); + Real run(const std::complex& s); + +private: + static Real normalizeAngle(Real angle); + ProjectionType m_projectionType; + Real m_prevArg; + Real m_prevVal; + Real *m_cache; + bool m_cacheMaster; +}; + +#endif // SDRBASE_DSP_PROJECTOR_H diff --git a/android/app/src/main/cpp/dsp/raisedcosine.h b/android/app/src/main/cpp/dsp/raisedcosine.h new file mode 100644 index 0000000..e2bf48e --- /dev/null +++ b/android/app/src/main/cpp/dsp/raisedcosine.h @@ -0,0 +1,175 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2021, 2023 Jon Beniston, M7RCE // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_RAISEDCOSINE_H +#define INCLUDE_RAISEDCOSINE_H + +#include +#include +#include "dsp/dsptypes.h" + +// Raised-cosine low-pass filter for pulse shaping, without intersymbol interference (ISI) +// https://en.wikipedia.org/wiki/Raised-cosine_filter +// This could be optimised in to a polyphase filter, as samplesPerSymbol-1 inputs +// to filter() should be zero, as the data is upsampled to the sample rate +template class RaisedCosine { +public: + RaisedCosine() : m_ptr(0) { } + + // beta - roll-off factor + // symbolSpan - number of symbols over which the filter is spread + // samplesPerSymbol - number of samples per symbol + // normaliseUpsampledAmplitude - when true, scale the filter such that an upsampled + // (by samplesPerSymbol) bipolar sequence (E.g. [1 0 0 -1 0 0..]) has maximum + // output values close to (1,-1) + void create(double beta, int symbolSpan, int samplesPerSymbol, bool normaliseUpsampledAmplitude = false) + { + int nTaps = symbolSpan * samplesPerSymbol + 1; + int i; + + // check constraints + if(!(nTaps & 1)) { + qDebug("Raised cosine filter has to have an odd number of taps"); + nTaps++; + } + + // make room + m_samples.resize(nTaps); + for(int i = 0; i < nTaps; i++) + m_samples[i] = 0; + m_ptr = 0; + m_taps.resize(nTaps / 2 + 1); + + // calculate filter taps + for(i = 0; i < nTaps / 2 + 1; i++) + { + double t = (i - (nTaps / 2)) / (double)samplesPerSymbol; + double denominator = 1.0 - std::pow(2.0 * beta * t, 2.0); + double sinc; + + if (denominator != 0.0) + { + if (t == 0) + sinc = 1.0; + else + sinc = sin(M_PI*t)/(M_PI*t); + m_taps[i] = sinc * (cos(M_PI*beta*t) / denominator) / (double)samplesPerSymbol; + } + else + m_taps[i] = beta * sin(M_PI/(2.0*beta)) / (2.0*samplesPerSymbol); + } + + // normalize + if (!normaliseUpsampledAmplitude) + { + // normalize energy + double sum = 0; + for(i = 0; i < (int)m_taps.size() - 1; i++) + sum += std::pow(m_taps[i], 2.0) * 2; + sum += std::pow(m_taps[i], 2.0); + sum = std::sqrt(sum); + for(i = 0; i < (int)m_taps.size(); i++) + m_taps[i] /= sum; + } + else + { + // Calculate maximum output of filter, assuming upsampled bipolar input E.g. [1 0 0 -1 0 0..] + // This doesn't necessarily include the centre tap, as ISI there should be zero, + // it's often at the midpoint between two symbols. However, depending on beta, + // the input that produces the worst case can vary, so we currently try them all + double maxGain = 0; + for (int input = 0; input < (1 << symbolSpan); input++) + { + double maxV = 0; + for(int i = 0; i < nTaps; i++) { + m_samples[i] = 0; + } + for (int i = 0; i < symbolSpan; i++) + { + Type sym = (input >> i) & 1 ? 1 : -1; + for (int j = 0; j < samplesPerSymbol; j++) + { + Type out; + if (j == 1) { + out = filter(sym); + } else { + out = filter(0); + } + double outAbs = abs(out); + if (outAbs > maxV) { + maxV = outAbs; + } + } + } + if (maxV > maxGain) { + maxGain = maxV; + } + } + + // Scale up so maximum out is 1 + for(i = 0; i < (int)m_taps.size(); i++) + m_taps[i] /= maxGain; + } + } + + Type filter(Type sample) + { + Type acc = 0; + unsigned int n_samples = m_samples.size(); + unsigned int n_taps = m_taps.size() - 1; + unsigned int a = m_ptr; + unsigned int b = a == n_samples - 1 ? 0 : a + 1; + + m_samples[m_ptr] = sample; + + for (unsigned int i = 0; i < n_taps; ++i) + { + acc += (m_samples[a] + m_samples[b]) * m_taps[i]; + + a = (a == 0) ? n_samples - 1 : a - 1; + b = (b == n_samples - 1) ? 0 : b + 1; + } + + acc += m_samples[a] * m_taps[n_taps]; + + m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1; + + return acc; + } + + // Print taps as a Matlab vector + void printTaps(const char *name) + { + printf("%s = [", name); + for (int i = 0; i <= m_taps.size() - 1; ++i) { + printf("%g ", m_taps[i]); + } + for (int i = m_taps.size() - 2; i >= 0; --i) { + printf("%g ", m_taps[i]); + } + printf("];\n"); + } + +private: + std::vector m_taps; + std::vector m_samples; + unsigned int m_ptr; +}; + +#endif // INCLUDE_RAISEDCOSINE_H diff --git a/android/app/src/main/cpp/dsp/recursivefilters.cpp b/android/app/src/main/cpp/dsp/recursivefilters.cpp new file mode 100644 index 0000000..30cd9e7 --- /dev/null +++ b/android/app/src/main/cpp/dsp/recursivefilters.cpp @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "recursivefilters.h" + +SecondOrderRecursiveFilter::SecondOrderRecursiveFilter(float samplingFrequency, float centerFrequency, float r) : + m_r(r), + m_frequencyRatio(centerFrequency/samplingFrequency), + m_f(cos(2.0*M_PI*(m_frequencyRatio))) +{ + init(); +} + +SecondOrderRecursiveFilter::~SecondOrderRecursiveFilter() +{} + +void SecondOrderRecursiveFilter::setFrequencies(float samplingFrequency, float centerFrequency) +{ + m_frequencyRatio = centerFrequency / samplingFrequency; + m_f = cos(2.0*M_PI*m_frequencyRatio); + init(); +} + +void SecondOrderRecursiveFilter::setR(float r) +{ + m_r = r; + init(); +} + +short SecondOrderRecursiveFilter::run(short sample) +{ + m_v[0] = ((1.0f - m_r) * (float) sample) + (2.0f * m_r * m_f * m_v[1]) - (m_r * m_r * m_v[2]); + float y = m_v[0] - m_v[2]; + m_v[2] = m_v[1]; + m_v[1] = m_v[0]; + + return (short) y; +} + +float SecondOrderRecursiveFilter::run(float sample) +{ + m_v[0] = ((1.0f - m_r) * sample) + (2.0f * m_r * m_f * m_v[1]) - (m_r * m_r * m_v[2]); + float y = m_v[0] - m_v[2]; + m_v[2] = m_v[1]; + m_v[1] = m_v[0]; + + return y; +} + +void SecondOrderRecursiveFilter::init() +{ + for (int i = 0; i < 3; i++) + { + m_v[i] = 0.0f; + } +} + diff --git a/android/app/src/main/cpp/dsp/recursivefilters.h b/android/app/src/main/cpp/dsp/recursivefilters.h new file mode 100644 index 0000000..4b160f7 --- /dev/null +++ b/android/app/src/main/cpp/dsp/recursivefilters.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_RECURSIVEFILTERS_H_ +#define SDRBASE_DSP_RECURSIVEFILTERS_H_ + +#include "export.h" + +/** + * \Brief: This is a second order bandpass filter using recursive method. r is in range ]0..1[ the higher the steeper the filter. + * inspired by:http://www.ece.umd.edu/~tretter/commlab/c6713slides/FSKSlides.pdf + */ +class SDRBASE_API SecondOrderRecursiveFilter +{ +public: + SecondOrderRecursiveFilter(float samplingFrequency, float centerFrequency, float r); + ~SecondOrderRecursiveFilter(); + + void setFrequencies(float samplingFrequency, float centerFrequency); + void setR(float r); + short run(short sample); + float run(float sample); + +private: + void init(); + + float m_r; + float m_frequencyRatio; + float m_f; + float m_v[3]; +}; + + + +#endif /* SDRBASE_DSP_RECURSIVEFILTERS_H_ */ diff --git a/android/app/src/main/cpp/dsp/replaybuffer.h b/android/app/src/main/cpp/dsp/replaybuffer.h new file mode 100644 index 0000000..9e52ab0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/replaybuffer.h @@ -0,0 +1,223 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_REPLAYBUFFER_H +#define INCLUDE_REPLAYBUFFER_H + +#include +#include +#include + +#include + +#include "dsp/wavfilerecord.h" + +// Circular buffer for storing and replaying IQ samples +// lock/unlock should be called manually before write/read/getSize +// (so it only needs to be locked once for write then multiple reads). +template +class ReplayBuffer { + +public: + ReplayBuffer() : + m_data(1000000*2, 0), + m_write(0), + m_read(0), + m_readOffset(0), + m_count(0) + { + } + + bool useReplay() const + { + return (m_readOffset > 0) || m_loop; + } + + void setSize(float lengthInSeconds, int sampleRate) + { + QMutexLocker locker(&m_mutex); + unsigned int newSize = lengthInSeconds * sampleRate * 2; + unsigned int oldSize = m_data.size(); + + if (newSize == oldSize) { + return; + } + + // Save most recent data + if (m_write >= newSize) + { + memmove(&m_data[0], &m_data[m_write-newSize], newSize); + m_write = 0; + m_count = newSize; + m_data.resize(newSize); + } + else if (newSize < oldSize) + { + memmove(&m_data[m_write], &m_data[oldSize-(newSize-m_write)], newSize-m_write); + m_count = std::min(m_count, newSize); + m_data.resize(newSize); + } + else + { + m_data.resize(newSize); + memmove(&m_data[newSize-(oldSize-m_write)], &m_data[m_write], oldSize-m_write); + } + } + + // lock()/unlock() should be called before/after calling this function + int getSize() const { + return m_data.size(); + } + + void setLoop(bool loop) { + m_loop = loop; + } + + bool getLoop() const { + return m_loop; + } + + // Copy count samples into circular buffer (1 I and Q pair should have count = 2) + // When loop is set, samples aren't copied, but write pointer is still updated + // lock()/unlock() should be called before/after calling this function + void write(const T* data, unsigned int count) + { + unsigned int totalLen = count; + while (totalLen > 0) + { + unsigned int len = std::min((unsigned int)m_data.size() - m_write, totalLen); + if (!m_loop) { + memcpy(&m_data[m_write], data, len * sizeof(T)); + } + m_write += len; + if (m_write >= m_data.size()) { + m_write = 0; + } + m_count += len; + if (m_count > m_data.size()) { + m_count = m_data.size(); + } + data += len; + totalLen -= len; + } + } + + // Get pointer to count samples - actual number available is returned + // lock()/unlock() should be called before/after calling this function + unsigned int read(unsigned int count, const T*& ptr) + { + unsigned int totalLen = count; + unsigned int len = std::min((unsigned int)m_data.size() - m_read, totalLen); + ptr = &m_data[m_read]; + m_read += len; + if (m_read >= m_data.size()) { + m_read = 0; + } + return len; + } + + void setReadOffset(unsigned int offset) + { + QMutexLocker locker(&m_mutex); + m_readOffset = offset; + offset = std::min(offset, (unsigned int)(m_data.size() - 1)); + int read = m_write - offset; + while (read < 0) { + read += m_data.size(); + } + m_read = (unsigned int) read; + } + + unsigned int getReadOffset() + { + return m_readOffset; + } + + // Save buffer to .wav file + void save(const QString& filename, quint32 sampleRate, quint64 centerFrequency) + { + QMutexLocker locker(&m_mutex); + + WavFileRecord wavFile(sampleRate, centerFrequency); + QString baseName = filename; + QFileInfo fileInfo(baseName); + QString suffix = fileInfo.suffix(); + if (!suffix.isEmpty()) { + baseName.chop(suffix.length() + 1); + } + wavFile.setFileName(baseName); + + wavFile.startRecording(); + int offset = m_write + m_data.size() - m_count; + for (unsigned int i = 0; i < m_count; i += 2) + { + int idx = (i + offset) % m_data.size(); + qint16 l = conv(m_data[idx]); + qint16 r = conv(m_data[idx+1]); + wavFile.write(l, r); + } + wavFile.stopRecording(); + } + + void clear() + { + QMutexLocker locker(&m_mutex); + std::fill(m_data.begin(), m_data.end(), 0); + m_count = 0; + } + + void lock() + { + m_mutex.lock(); + } + + void unlock() + { + m_mutex.unlock(); + } + +private: + std::vector m_data; + unsigned int m_write; // Write index + unsigned int m_read; // Read index + unsigned int m_readOffset; + unsigned int m_count; // Count of number of valid samples in the buffer + bool m_loop; + QMutex m_mutex; + + qint16 conv(quint8 data) const + { + return (data - 128) << 8; + } + + qint16 conv(qint16 data) const + { + return data; + } + + qint16 conv(qint32 data) const + { + return data >> 16; + } + + qint16 conv(float data) const + { + return (qint16)(data * SDR_RX_SCALEF); + } +}; + +#endif // INCLUDE_REPLAYBUFFER_H diff --git a/android/app/src/main/cpp/dsp/rootraisedcosine.h b/android/app/src/main/cpp/dsp/rootraisedcosine.h new file mode 100644 index 0000000..7765456 --- /dev/null +++ b/android/app/src/main/cpp/dsp/rootraisedcosine.h @@ -0,0 +1,141 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_ROOTRAISEDCOSINE_H +#define INCLUDE_ROOTRAISEDCOSINE_H + +#include +#include +#include "dsp/dsptypes.h" + +// Root-raised-cosine low-pass filter for pulse shaping, without intersymbol interference (ISI) +// https://en.wikipedia.org/wiki/Root-raised-cosine_filter +// This could be optimised in to a polyphase filter, as samplesPerSymbol-1 inputs +// to filter() should be zero, as the data is upsampled to the sample rate +template class RootRaisedCosine { +public: + RootRaisedCosine() : m_ptr(0) { } + + // beta - roll-off factor + // symbolSpan - number of symbols over which the filter is spread + // samplesPerSymbol - number of samples per symbol + // normaliseUpsampledAmplitude - when true, scale the filter such that an upsampled + // (by samplesPerSymbol) bipolar sequence (E.g. [1 0 0 -1 0 0..]) has maximum + // output values close to (1,-1) + void create(double beta, int symbolSpan, int samplesPerSymbol, bool normaliseUpsampledAmplitude = false) + { + int nTaps = symbolSpan * samplesPerSymbol + 1; + int i, j; + + // check constraints + if(!(nTaps & 1)) { + qDebug("Root raised cosine filter has to have an odd number of taps"); + nTaps++; + } + + // make room + m_samples.resize(nTaps); + for(int i = 0; i < nTaps; i++) + m_samples[i] = 0; + m_ptr = 0; + m_taps.resize(nTaps / 2 + 1); + + // calculate filter taps + for(i = 0; i < nTaps / 2 + 1; i++) + { + double t = (i - (nTaps / 2)) / (double)samplesPerSymbol; + double Ts = 1.0; + + double numerator = 1.0/Ts * (sin(M_PI * t / Ts * (1.0-beta)) + 4.0*beta*t/Ts*cos(M_PI*t/Ts*(1+beta))); + double b = (4.0 * beta * t / Ts); + double denominator = M_PI * t / Ts * (1-b*b); + + if ((numerator == 0.0) && (denominator == 0.0)) + m_taps[i] = 1.0/Ts * (1.0+beta*(4.0/M_PI-1.0)); + else if (denominator == 0.0) + m_taps[i] = beta/(Ts*sqrt(2.0)) * ((1+2.0/M_PI)*sin(M_PI/(4.0*beta)) + (1.0-2.0/M_PI)*cos(M_PI/(4.0*beta))); + else + m_taps[i] = numerator/denominator; + } + + // normalize + if (!normaliseUpsampledAmplitude) + { + // normalize energy + double sum = 0; + for(i = 0; i < (int)m_taps.size() - 1; i++) + sum += std::pow(m_taps[i], 2.0) * 2; + sum += std::pow(m_taps[i], 2.0); + sum = std::sqrt(sum); + for(i = 0; i < (int)m_taps.size(); i++) + m_taps[i] /= sum; + } + else + { + // Calculate maximum output of filter, assuming upsampled bipolar input E.g. [1 0 0 -1 0 0..] + // This doesn't necessarily include the centre tap, so we try each offset + double maxGain = 0.0; + for (i = 0; i < samplesPerSymbol; i++) + { + double g = 0.0; + for (j = 0; j < (int)m_taps.size() - 1; j += samplesPerSymbol) + g += std::fabs(2.0 * m_taps[j]); + if ((i & 1) == 0) + g += std::fabs(m_taps[j]); + if (g > maxGain) + maxGain = g; + } + // Scale up so maximum out is 1 + for(i = 0; i < (int)m_taps.size(); i++) + m_taps[i] /= maxGain; + } + } + + Type filter(Type sample) + { + Type acc = 0; + unsigned int n_samples = m_samples.size(); + unsigned int n_taps = m_taps.size() - 1; + unsigned int a = m_ptr; + unsigned int b = a == n_samples - 1 ? 0 : a + 1; + + m_samples[m_ptr] = sample; + + for (unsigned int i = 0; i < n_taps; ++i) + { + acc += (m_samples[a] + m_samples[b]) * m_taps[i]; + + a = (a == 0) ? n_samples - 1 : a - 1; + b = (b == n_samples - 1) ? 0 : b + 1; + } + + acc += m_samples[a] * m_taps[n_taps]; + + m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1; + + return acc; + } + +private: + std::vector m_taps; + std::vector m_samples; + unsigned int m_ptr; +}; + +#endif // INCLUDE_ROOTRAISEDCOSINE_H diff --git a/android/app/src/main/cpp/dsp/samplemififo.cpp b/android/app/src/main/cpp/dsp/samplemififo.cpp new file mode 100644 index 0000000..e340f32 --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplemififo.cpp @@ -0,0 +1,381 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "samplemififo.h" + +void SampleMIFifo::init(unsigned int nbStreams, unsigned int size) +{ + QMutexLocker mutexLocker(&m_mutex); + m_nbStreams = nbStreams; + m_size = size; + m_fill = 0; + m_head = 0; + m_data.resize(nbStreams); + m_vFill.clear(); + m_vHead.clear(); + + for (unsigned int stream = 0; stream < nbStreams; stream++) + { + m_data[stream].resize(size); + m_vFill.push_back(0); + m_vHead.push_back(0); + } +} + +void SampleMIFifo::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_fill = 0; + m_head = 0; + for (unsigned int stream = 0; stream < m_nbStreams; stream++) + { + m_vFill[stream] = 0; + m_vHead[stream] = 0; + } +} + +SampleMIFifo::SampleMIFifo(QObject *parent) : + QObject(parent), + m_nbStreams(0), + m_size(0), + m_fill(0), + m_head(0) +{ +} + +SampleMIFifo::SampleMIFifo(unsigned int nbStreams, unsigned int size, QObject *parent) : + QObject(parent) +{ + init(nbStreams, size); +} + +SampleMIFifo::~SampleMIFifo() +{ + qDebug("SampleMIFifo::~SampleMIFifo: m_fill: %u", m_fill); + qDebug("SampleMIFifo::~SampleMIFifo: m_head: %u", m_head); + + for (unsigned int stream = 0; stream < m_data.size(); stream++) + { + qDebug("SampleMIFifo::~SampleMIFifo: m_data[%u] size: %lu", stream, m_data[stream].size()); + qDebug("SampleMIFifo::~SampleMIFifo: m_vFill[%u] %u", stream, m_vFill[stream]); + qDebug("SampleMIFifo::~SampleMIFifo: m_vHead[%u] %u", stream, m_vHead[stream]); + } +} + +void SampleMIFifo::writeSync(const quint8* data, unsigned int count) +{ + QMutexLocker mutexLocker(&m_mutex); + unsigned int spaceLeft = m_size - m_fill; + unsigned int size = count / sizeof(Sample); + + if (size > m_size) + { + qWarning("SampleMIFifo::writeSync: input size %u greater that FIFO size %u: truncating input", size, m_size); + size = m_size; + count = m_size * sizeof(Sample); + } + + for (unsigned int stream = 0; stream < m_data.size(); stream++) + { + if (size <= spaceLeft) + { + std::copy(&data[stream*count], &data[stream*count] + count, m_data[stream].begin() + m_fill); + m_fill += size; + } + else + { + unsigned int remaining = size - spaceLeft; + unsigned int bytesLeft = spaceLeft*sizeof(Sample); + std::copy(&data[stream*count], &data[stream*count] + bytesLeft, m_data[stream].begin() + m_fill); + std::copy(&data[stream*count] + bytesLeft, &data[stream*count] + count, m_data[stream].begin()); + m_fill = remaining; + } + } + + emit dataSyncReady(); +} + +void SampleMIFifo::writeSync(const std::vector& vbegin, unsigned int size) +{ + if ((m_data.size() == 0) || (m_data.size() != vbegin.size())) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + unsigned int spaceLeft = m_size - m_fill; + + if (size > m_size) + { + qWarning("SampleMIFifo::writeSync: input size %u greater that FIFO size %u: truncating input", size, m_size); + size = m_size; + } + + if (size <= spaceLeft) + { + for (unsigned int stream = 0; stream < m_data.size(); stream++) { + std::copy(vbegin[stream], vbegin[stream] + size, m_data[stream].begin() + m_fill); + } + + m_fill += size; + } + else + { + unsigned int remaining = size - spaceLeft; + + for (unsigned int stream = 0; stream < m_data.size(); stream++) + { + std::copy(vbegin[stream], vbegin[stream] + spaceLeft, m_data[stream].begin() + m_fill); + std::copy(vbegin[stream] + spaceLeft, vbegin[stream] + size, m_data[stream].begin()); + } + + m_fill = remaining; + } + + emit dataSyncReady(); +} + +void SampleMIFifo::readSync( + std::vector vpart1Begin, std::vector vpart1End, + std::vector vpart2Begin, std::vector vpart2End +) +{ + if (m_data.size() == 0) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + vpart1Begin.resize(m_nbStreams); + vpart1End.resize(m_nbStreams); + vpart2Begin.resize(m_nbStreams); + vpart2End.resize(m_nbStreams); + + if (m_head < m_fill) + { + for (unsigned int stream = 0; stream < m_data.size(); stream++) + { + *vpart1Begin[stream] = m_data[stream].begin() + m_head; + *vpart1End[stream] = m_data[stream].begin() + m_fill; + *vpart2Begin[stream] = m_data[stream].end(); + *vpart2End[stream] = m_data[stream].end(); + } + } + else + { + for (unsigned int stream = 0; stream < m_data.size(); stream++) + { + *vpart1Begin[stream] = m_data[stream].begin() + m_head; + *vpart1End[stream] = m_data[stream].end(); + *vpart2Begin[stream] = m_data[stream].begin(); + *vpart2End[stream] = m_data[stream].begin() + m_fill; + } + } + + m_head = m_fill; +} + +void SampleMIFifo::readSync( + std::vector& vpart1Begin, std::vector& vpart1End, + std::vector& vpart2Begin, std::vector& vpart2End +) +{ + if (m_data.size() == 0) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + vpart1Begin.resize(m_nbStreams); + vpart1End.resize(m_nbStreams); + vpart2Begin.resize(m_nbStreams); + vpart2End.resize(m_nbStreams); + + + if (m_head < m_fill) + { + for (unsigned int stream = 0; stream < m_data.size(); stream++) + { + vpart1Begin[stream] = m_head; + vpart1End[stream] = m_fill; + vpart2Begin[stream] = 0; + vpart2End[stream] = 0; + } + } + else + { + for (unsigned int stream = 0; stream < m_data.size(); stream++) + { + vpart1Begin[stream] = m_head; + vpart1End[stream] = m_size; + vpart2Begin[stream] = 0; + vpart2End[stream] = m_fill; + } + } + + m_head = m_fill; +} + +void SampleMIFifo::readSync( + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End +) +{ + if (m_data.size() == 0) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + + if (m_head < m_fill) + { + ipart1Begin = m_head; + ipart1End = m_fill; + ipart2Begin = 0; + ipart2End = 0; + } + else + { + ipart1Begin = m_head; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = m_fill; + } + + m_head = m_fill; +} + +void SampleMIFifo::writeAsync(const quint8* data, unsigned int count, unsigned int stream) +{ + if (stream >= m_nbStreams) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + unsigned int spaceLeft = m_size - m_vFill[stream]; + unsigned int size = count / sizeof(Sample); + + if (size > m_size) + { + qWarning("SampleMIFifo::writeAsync: input size %u greater that FIFO size %u: truncating input", size, m_size); + size = m_size; + count = m_size * sizeof(Sample); + } + + if (size <= spaceLeft) + { + std::copy(&data[stream*count], &data[stream*count] + count, m_data[stream].begin() + m_vFill[stream]); + m_vFill[stream] += size; + } + else + { + unsigned int remaining = size - spaceLeft; + unsigned int bytesLeft = spaceLeft * sizeof(Sample); + std::copy(&data[stream*count], &data[stream*count] + bytesLeft, m_data[stream].begin() + m_vFill[stream]); + std::copy(&data[stream*count] + bytesLeft, &data[stream*count] + count, m_data[stream].begin()); + m_vFill[stream] = remaining; + } + + emit dataAsyncReady(stream); +} + +void SampleMIFifo::writeAsync(const SampleVector::const_iterator& begin, unsigned int size, unsigned int stream) +{ + if (stream >= m_nbStreams) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + unsigned int spaceLeft = m_size < m_vFill[stream] ? 0 : m_size - m_vFill[stream]; + + if (size > m_size) + { + qWarning("SampleMIFifo::writeAsync: input size %u greater that FIFO size %u: truncating input", size, m_size); + size = m_size; + } + + if (size <= spaceLeft) + { + std::copy(begin, begin + size, m_data[stream].begin() + m_vFill[stream]); + m_vFill[stream] += size; + } + else + { + unsigned int remaining = size - spaceLeft; + std::copy(begin, begin + spaceLeft, m_data[stream].begin() + m_vFill[stream]); + std::copy(begin + spaceLeft, begin + size, m_data[stream].begin()); + m_vFill[stream] = remaining; + } + + emit dataAsyncReady(stream); +} + +void SampleMIFifo::readAsync( + SampleVector::const_iterator* part1Begin, SampleVector::const_iterator* part1End, + SampleVector::const_iterator* part2Begin, SampleVector::const_iterator* part2End, + unsigned int stream) +{ + if (stream >= m_nbStreams) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + + if (m_vHead[stream] < m_vFill[stream]) + { + *part1Begin = m_data[stream].begin() + m_vHead[stream]; + *part1End = m_data[stream].begin() + m_vFill[stream]; + *part2Begin = m_data[stream].begin(); + *part2End = m_data[stream].begin(); + } + else + { + *part1Begin = m_data[stream].begin() + m_vHead[stream]; + *part1End = m_data[stream].end(); + *part2Begin = m_data[stream].begin(); + *part2End = m_data[stream].begin() + m_vFill[stream]; + } + + m_vHead[stream] = m_vFill[stream]; +} + +void SampleMIFifo::readAsync( + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End, + unsigned int stream) +{ + if (stream >= m_data.size()) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + + if (m_vHead[stream] < m_vFill[stream]) + { + ipart1Begin = m_vHead[stream]; + ipart1End = m_vFill[stream]; + ipart2Begin = m_size; + ipart2End = m_size; + } + else + { + ipart1Begin = m_vHead[stream]; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = m_vFill[stream]; + } + + m_vHead[stream] = m_vFill[stream]; +} diff --git a/android/app/src/main/cpp/dsp/samplemififo.h b/android/app/src/main/cpp/dsp/samplemififo.h new file mode 100644 index 0000000..96aeaea --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplemififo.h @@ -0,0 +1,102 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019, 2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SAMPLEMIFIFO_H +#define INCLUDE_SAMPLEMIFIFO_H + +#include +#include +#include +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API SampleMIFifo : public QObject { + Q_OBJECT + +public: + SampleMIFifo(QObject *parent = nullptr); + SampleMIFifo(unsigned int nbStreams, unsigned int size, QObject *parent = nullptr); + ~SampleMIFifo(); + void init(unsigned int nbStreams, unsigned int size); + void reset(); + + void writeSync(const quint8* data, unsigned int count); //!< de-interleaved data in input with count bytes for each stream + void writeSync(const std::vector& vbegin, unsigned int size); + void readSync( + std::vector vpart1Begin, std::vector vpart1End, + std::vector vpart2Begin, std::vector vpart2End + ); + void readSync( + std::vector& vPart1Begin, std::vector& vPart1End, + std::vector& vPart2Begin, std::vector& vPart2End + ); + void readSync( + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End + ); + + void writeAsync(const quint8* data, unsigned int count, unsigned int stream); + void writeAsync(const SampleVector::const_iterator& begin, unsigned int inputSize, unsigned int stream); + void readAsync( + SampleVector::const_iterator* part1Begin, SampleVector::const_iterator* part1End, + SampleVector::const_iterator* part2Begin, SampleVector::const_iterator* part2End, + unsigned int stream); + void readAsync( + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End, + unsigned int stream); + + + const std::vector& getData() { return m_data; } + const SampleVector& getData(unsigned int stream) { return m_data[stream]; } + unsigned int getNbStreams() const { return m_data.size(); } + + inline unsigned int fillSync() + { + QMutexLocker mutexLocker(&m_mutex); + unsigned int fill = m_head <= m_fill ? m_fill - m_head : m_size - (m_head - m_fill); + return fill; + } + + inline unsigned int fillAsync(unsigned int stream) + { + if (stream >= m_nbStreams) { + return 0; + } + + QMutexLocker mutexLocker(&m_mutex); + unsigned int fill = m_vHead[stream] <= m_vFill[stream] ? m_vFill[stream] - m_vHead[stream] : m_size - (m_vHead[stream] - m_vFill[stream]); + return fill; + } + +signals: + void dataSyncReady(); + void dataAsyncReady(int streamIndex); + +private: + std::vector m_data; + unsigned int m_nbStreams; + unsigned int m_size; + unsigned int m_fill; //!< Number of samples written from beginning of samples vector (sync) + unsigned int m_head; //!< Number of samples read from beginning of samples vector (sync) + std::vector m_vFill; //!< Number of samples written from beginning of samples vector (async) + std::vector m_vHead; //!< Number of samples read from beginning of samples vector (async) + QRecursiveMutex m_mutex; +}; + +#endif // INCLUDE_SAMPLEMIFIFO_H diff --git a/android/app/src/main/cpp/dsp/samplemofifo.cpp b/android/app/src/main/cpp/dsp/samplemofifo.cpp new file mode 100644 index 0000000..aa9a0ba --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplemofifo.cpp @@ -0,0 +1,236 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019, 2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// Copyright (C) 2023 Daniele Forsi // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "samplemofifo.h" + +const unsigned int SampleMOFifo::m_rwDivisor = 2; +const unsigned int SampleMOFifo::m_guardDivisor = 10; + +SampleMOFifo::SampleMOFifo(QObject *parent) : + QObject(parent), + m_nbStreams(0) +{} + +SampleMOFifo::SampleMOFifo(unsigned int nbStreams, unsigned int size, QObject *parent) : + QObject(parent) +{ + init(nbStreams, size); +} + +void SampleMOFifo::init(unsigned int nbStreams, unsigned int size) +{ + QMutexLocker mutexLocker(&m_mutex); + m_data.resize(nbStreams); + m_vReadCount.resize(nbStreams); + m_vReadHead.resize(nbStreams); + m_vWriteHead.resize(nbStreams); + m_nbStreams = nbStreams; + resize(size); +} + +void SampleMOFifo::resize(unsigned int size) +{ + QMutexLocker mutexLocker(&m_mutex); + m_size = size; + m_lowGuard = m_size / m_guardDivisor; + m_highGuard = m_size - (m_size/m_guardDivisor); + m_midPoint = m_size / m_rwDivisor; + + for (unsigned int stream = 0; stream < m_nbStreams; stream++) { + m_data[stream].resize(size); + } + + reset(); +} + +void SampleMOFifo::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_readCount = 0; + m_readHead = 0; + m_writeHead = m_midPoint; + + for (unsigned int stream = 0; stream < m_nbStreams; stream++) + { + m_vReadCount[stream] = 0; + m_vReadHead[stream] = 0; + m_vWriteHead[stream] = m_midPoint; + } +} + +SampleMOFifo::~SampleMOFifo() +{} + +void SampleMOFifo::readSync( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to read + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets +) +{ + QMutexLocker mutexLocker(&m_mutex); + unsigned int spaceLeft = m_size - m_readHead; + m_readCount = m_readCount + amount < m_size ? m_readCount + amount : m_size; // cannot exceed FIFO size + + if (amount <= spaceLeft) + { + ipart1Begin = m_readHead; + ipart1End = m_readHead + amount; + ipart2Begin = m_size; + ipart2End = m_size; + m_readHead += amount; + } + else + { + unsigned int remaining = (amount < m_size ? amount : m_size) - spaceLeft; + ipart1Begin = m_readHead; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = remaining; + m_readHead = remaining; + } + + emit dataReadSync(); +} + +void SampleMOFifo::writeSync( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to write + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets +) +{ + QMutexLocker mutexLocker(&m_mutex); + unsigned int rwDelta = m_writeHead >= m_readHead ? m_writeHead - m_readHead : m_size - (m_readHead - m_writeHead); + + if (rwDelta < m_lowGuard) + { + qWarning("SampleMOFifo::write: underrun (write too slow) using %d old samples", m_midPoint - m_lowGuard); + m_writeHead = m_readHead + m_midPoint < m_size ? m_readHead + m_midPoint : m_readHead + m_midPoint - m_size; + } + else if (rwDelta > m_highGuard) + { + qWarning("SampleMOFifo::write: overrun (read too slow) dropping %d samples", m_highGuard - m_midPoint); + m_writeHead = m_readHead + m_midPoint < m_size ? m_readHead + m_midPoint : m_readHead + m_midPoint - m_size; + } + + unsigned int spaceLeft = m_size - m_writeHead; + + if (amount <= spaceLeft) + { + ipart1Begin = m_writeHead; + ipart1End = m_writeHead + amount; + ipart2Begin = m_size; + ipart2End = m_size; + m_writeHead += amount; + } + else + { + unsigned int remaining = (amount < m_size ? amount : m_size) - spaceLeft; + ipart1Begin = m_writeHead; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = remaining; + m_writeHead = remaining; + } + + m_readCount = amount < m_readCount ? m_readCount - amount : 0; // cannot be less than 0 +} + +void SampleMOFifo::readAsync( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End, + unsigned int stream +) +{ + QMutexLocker mutexLocker(&m_mutex); + unsigned int spaceLeft = m_size - m_vReadHead[stream]; + m_vReadCount[stream] = m_vReadCount[stream] + amount < m_size ? m_vReadCount[stream] + amount : m_size; // cannot exceed FIFO size + + if (amount <= spaceLeft) + { + ipart1Begin = m_vReadHead[stream]; + ipart1End = m_vReadHead[stream] + amount; + ipart2Begin = m_size; + ipart2End = m_size; + m_vReadHead[stream] += amount; + } + else + { + unsigned int remaining = (amount < m_size ? amount : m_size) - spaceLeft; + ipart1Begin = m_vReadHead[stream]; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = remaining; + m_vReadHead[stream] = remaining; + } + + emit dataReadAsync(stream); +} + +void SampleMOFifo::writeAsync( //!< in place write with given amount + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End, + unsigned int stream +) +{ + QMutexLocker mutexLocker(&m_mutex); + unsigned int rwDelta = m_vWriteHead[stream] >= m_vReadHead[stream] ? + m_vWriteHead[stream] - m_vReadHead[stream] : m_size - (m_vReadHead[stream] - m_vWriteHead[stream]); + + if (rwDelta < m_lowGuard) + { + qWarning("SampleMOFifo::write: underrun on stream %u (write too slow) using %d old samples", stream, m_midPoint - m_lowGuard); + m_vWriteHead[stream] = m_vReadHead[stream] + m_midPoint < m_size ? + m_vReadHead[stream] + m_midPoint : m_vReadHead[stream] + m_midPoint - m_size; + } + else if (rwDelta > m_highGuard) + { + qWarning("SampleMOFifo::write: overrun on stream %u (read too slow) dropping %d samples", stream, m_highGuard - m_midPoint); + m_vWriteHead[stream] = m_vReadHead[stream] + m_midPoint < m_size ? + m_vReadHead[stream] + m_midPoint : m_vReadHead[stream] + m_midPoint - m_size; + } + + unsigned int spaceLeft = m_size - m_vWriteHead[stream]; + + if (amount <= spaceLeft) + { + ipart1Begin = m_vWriteHead[stream]; + ipart1End = m_vWriteHead[stream] + amount; + ipart2Begin = m_size; + ipart2End = m_size; + m_vWriteHead[stream] += amount; + } + else + { + unsigned int remaining = (amount < m_size ? amount : m_size) - spaceLeft; + ipart1Begin = m_vWriteHead[stream]; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = remaining; + m_vWriteHead[stream] = remaining; + } + + m_vReadCount[stream] = amount < m_vReadCount[stream] ? m_vReadCount[stream] - amount : 0; // cannot be less than 0 +} + +unsigned int SampleMOFifo::getSizePolicy(unsigned int sampleRate) +{ + return (sampleRate/100)*64; // .64s +} diff --git a/android/app/src/main/cpp/dsp/samplemofifo.h b/android/app/src/main/cpp/dsp/samplemofifo.h new file mode 100644 index 0000000..9419da9 --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplemofifo.h @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SAMPLEMOFIFO_H +#define INCLUDE_SAMPLEMOFIFO_H + +#include +#include +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API SampleMOFifo : public QObject { + Q_OBJECT + +public: + SampleMOFifo(QObject *parent = nullptr); + SampleMOFifo(unsigned int nbStreams, unsigned int size, QObject *parent = nullptr); + ~SampleMOFifo(); + + void init(unsigned int nbStreams, unsigned int size); + void resize(unsigned int size); + void reset(); + + void readSync( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to read + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets + ); + + void writeSync( //!< in place write with given amount + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to write + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets + ); + + void readAsync( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End, + unsigned int stream + ); + + void writeAsync( //!< in place write with given amount + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, + unsigned int& ipart2Begin, unsigned int& ipart2End, + unsigned int stream + ); + + std::vector& getData() { return m_data; } + SampleVector& getData(unsigned int stream) { return m_data[stream]; } + unsigned int getNbStreams() const { return m_data.size(); } + + unsigned int remainderSync() + { + QMutexLocker mutexLocker(&m_mutex); + return m_readCount; + } + + unsigned int remainderAsync(unsigned int stream) + { + if (stream >= m_nbStreams) { + return 0; + } + + QMutexLocker mutexLocker(&m_mutex); + return m_vReadCount[stream]; + } + + static unsigned int getSizePolicy(unsigned int sampleRate); + static const unsigned int m_rwDivisor; + static const unsigned int m_guardDivisor; + +signals: + void dataReadSync(); + void dataReadAsync(int streamIndex); + +private: + std::vector m_data; + unsigned int m_nbStreams; + unsigned int m_size; + unsigned int m_lowGuard; + unsigned int m_highGuard; + unsigned int m_midPoint; + unsigned int m_readCount; + unsigned int m_readHead; + unsigned int m_writeHead; + std::vector m_vReadCount; + std::vector m_vReadHead; + std::vector m_vWriteHead; + QRecursiveMutex m_mutex; +}; + +#endif // INCLUDE_SAMPLEMOFIFO_H diff --git a/android/app/src/main/cpp/dsp/samplesimplefifo.cpp b/android/app/src/main/cpp/dsp/samplesimplefifo.cpp new file mode 100644 index 0000000..d8b1ca4 --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplesimplefifo.cpp @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "samplesimplefifo.h" + +void SampleSimpleFifo::create(unsigned int s) +{ + m_size = 0; + m_fill = 0; + m_head = 0; + m_tail = 0; + + m_data.resize(s); + m_size = m_data.size(); +} + +void SampleSimpleFifo::reset() +{ + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +SampleSimpleFifo::SampleSimpleFifo() : + m_data() +{ + m_size = 0; + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +SampleSimpleFifo::SampleSimpleFifo(int size) : + m_data() +{ + create(size); +} + +SampleSimpleFifo::SampleSimpleFifo(const SampleSimpleFifo& other) : + m_data(other.m_data) +{ + m_size = m_data.size(); + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +SampleSimpleFifo::~SampleSimpleFifo() +{ + m_size = 0; +} + +bool SampleSimpleFifo::setSize(int size) +{ + create(size); + return m_data.size() == (unsigned int) size; +} + +unsigned int SampleSimpleFifo::write(SampleVector::const_iterator begin, SampleVector::const_iterator end) +{ + unsigned int count = end - begin; + unsigned int total; + unsigned int remaining; + unsigned int len; + + total = count; + remaining = total; + + while (remaining > 0) + { + len = std::min(remaining, m_size - m_tail); + std::copy(begin, begin + len, m_data.begin() + m_tail); + m_tail += len; + m_tail %= m_size; + m_fill += len; + begin += len; + remaining -= len; + } + + if (m_fill >= m_size) // has wrapped around at least once + { + m_head = m_tail; + m_fill = m_size; + } + + return m_fill; +} + +unsigned int SampleSimpleFifo::readBegin(unsigned int count, + SampleVector::iterator* part1Begin, SampleVector::iterator* part1End, + SampleVector::iterator* part2Begin, SampleVector::iterator* part2End) +{ + unsigned int total; + unsigned int remaining; + unsigned int len; + unsigned int head = m_head; + + total = count; + remaining = total; + + if (remaining > 0) + { + len = std::min(remaining, m_size - head); + *part1Begin = m_data.begin() + head; + *part1End = m_data.begin() + head + len; + head += len; + head %= m_size; + remaining -= len; + } + else + { + *part1Begin = m_data.end(); + *part1End = m_data.end(); + } + + if (remaining > 0) + { + len = std::min(remaining, m_size - head); + *part2Begin = m_data.begin() + head; + *part2End = m_data.begin() + head + len; + } + else + { + *part2Begin = m_data.end(); + *part2End = m_data.end(); + } + + return total; +} + +unsigned int SampleSimpleFifo::readCommit(unsigned int count) +{ + if (count > m_fill) + { + qCritical("SampleSinkFifo::readCommit: cannot commit more than available samples"); + count = m_fill; + } + + m_head = (m_head + count) % m_size; + m_fill -= count; + + return count; +} diff --git a/android/app/src/main/cpp/dsp/samplesimplefifo.h b/android/app/src/main/cpp/dsp/samplesimplefifo.h new file mode 100644 index 0000000..1712f34 --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplesimplefifo.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SAMPLESIMPLEFIFO_H +#define INCLUDE_SAMPLESIMPLEFIFO_H + +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API SampleSimpleFifo { +public: + SampleSimpleFifo(); + SampleSimpleFifo(int size); + SampleSimpleFifo(const SampleSimpleFifo& other); + ~SampleSimpleFifo(); + + bool setSize(int size); + void reset(); + unsigned int getSize() { return m_size; } + unsigned int write(SampleVector::const_iterator begin, SampleVector::const_iterator end); + unsigned int readBegin(unsigned int count, + SampleVector::iterator* part1Begin, SampleVector::iterator* part1End, + SampleVector::iterator* part2Begin, SampleVector::iterator* part2End); + unsigned int readCommit(unsigned int count); + +private: + SampleVector m_data; //!< FIFO container + unsigned int m_size; //!< size of FIFO (above) + unsigned int m_fill; //!< number of samples in FIFO + unsigned int m_head; //!< read point + unsigned int m_tail; //!< write point + + void create(unsigned int s); +}; + +#endif // INCLUDE_SAMPLESIMPLEFIFO_H diff --git a/android/app/src/main/cpp/dsp/samplesinkfifo.cpp b/android/app/src/main/cpp/dsp/samplesinkfifo.cpp new file mode 100644 index 0000000..a0fd0be --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplesinkfifo.cpp @@ -0,0 +1,363 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016, 2018-2019, 2021-2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "maincore.h" +#include "samplesinkfifo.h" + +//#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +void SampleSinkFifo::create(unsigned int s) +{ + m_fill = 0; + m_head = 0; + m_tail = 0; + + m_data.resize(s); + m_size = m_data.size(); +} + +void SampleSinkFifo::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_suppressed = -1; + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +SampleSinkFifo::SampleSinkFifo(QObject* parent) : + QObject(parent), + m_data(), + m_total(0), + m_writtenSignalCount(0), + m_writtenSignalRateDivider(1) +{ + m_suppressed = -1; + m_size = 0; + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +SampleSinkFifo::SampleSinkFifo(int size, QObject* parent) : + QObject(parent), + m_data(), + m_total(0), + m_writtenSignalCount(0), + m_writtenSignalRateDivider(1) +{ + m_suppressed = -1; + create(size); +} + +SampleSinkFifo::SampleSinkFifo(const SampleSinkFifo& other) : + QObject(other.parent()), + m_data(other.m_data), + m_total(0), + m_writtenSignalCount(0), + m_writtenSignalRateDivider(1) +{ + m_suppressed = -1; + m_size = m_data.size(); + m_fill = 0; + m_head = 0; + m_tail = 0; +} + +SampleSinkFifo::~SampleSinkFifo() +{ + QMutexLocker mutexLocker(&m_mutex); + m_size = 0; +} + +bool SampleSinkFifo::setSize(int size) +{ + QMutexLocker mutexLocker(&m_mutex); + create(size); + return m_data.size() == (unsigned int)size; +} + +void SampleSinkFifo::setWrittenSignalRateDivider(unsigned int divider) +{ + QMutexLocker mutexLocker(&m_mutex); + m_writtenSignalRateDivider = divider; +} + +unsigned int SampleSinkFifo::write(const quint8* data, unsigned int count) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_size == 0) { + return 0; + } + + unsigned int total; + unsigned int remaining; + unsigned int len; + const Sample* begin = (const Sample*)data; + count /= sizeof(Sample); + + total = std::min(count, m_size - m_fill); + + if (total < count) + { + if (m_suppressed < 0) + { + m_suppressed = 0; + m_msgRateTimer.start(); + qCritical("SampleSinkFifo::write: (%s) overflow - dropping %u samples", + qPrintable(m_label), count - total); + emit overflow(count - total); + } + else + { + if (m_msgRateTimer.elapsed() > 2500) + { + qCritical("SampleSinkFifo::write: (%s) %u messages dropped", qPrintable(m_label), m_suppressed); + qCritical("SampleSinkFifo::write: (%s) overflow - dropping %u samples", + qPrintable(m_label), count - total); + emit overflow(count - total); + m_suppressed = -1; + } + else + { + m_suppressed++; + } + } + } + + remaining = total; + + while (remaining > 0) + { + len = std::min(remaining, m_size - m_tail); + std::copy(begin, begin + len, m_data.begin() + m_tail); + m_tail += len; + m_tail %= m_size; + m_fill += len; + begin += len; + remaining -= len; + } + + if (m_fill > 0) { + emit dataReady(); + } + + m_total += total; + + if (++m_writtenSignalCount >= m_writtenSignalRateDivider) + { + emit written(m_total, MainCore::instance()->getElapsedNsecs()); + m_total = 0; + m_writtenSignalCount = 0; + } + + return total; +} + +unsigned int SampleSinkFifo::write(SampleVector::const_iterator begin, SampleVector::const_iterator end) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_size == 0) { + return 0; + } + + unsigned int count = end - begin; + unsigned int total; + unsigned int remaining; + unsigned int len; + + total = std::min(count, m_size - m_fill); + + if (total < count) + { + if (m_suppressed < 0) + { + m_suppressed = 0; + m_msgRateTimer.start(); + qCritical("SampleSinkFifo::write: (%s) overflow - dropping %u samples", + qPrintable(m_label), count - total); + emit overflow(count - total); + } + else + { + if (m_msgRateTimer.elapsed() > 2500) + { + qCritical("SampleSinkFifo::write: (%s) %u messages dropped", qPrintable(m_label), m_suppressed); + qCritical("SampleSinkFifo::write: (%s) overflow - dropping %u samples", + qPrintable(m_label), count - total); + emit overflow(count - total); + m_suppressed = -1; + } + else + { + m_suppressed++; + } + } + } + + remaining = total; + + while (remaining > 0) + { + len = std::min(remaining, m_size - m_tail); + std::copy(begin, begin + len, m_data.begin() + m_tail); + m_tail += len; + m_tail %= m_size; + m_fill += len; + begin += len; + remaining -= len; + } + + if (m_fill > 0) { + emit dataReady(); + } + + m_total += total; + + if (++m_writtenSignalCount >= m_writtenSignalRateDivider) + { + emit written(m_total, MainCore::instance()->getElapsedNsecs()); + m_total = 0; + m_writtenSignalCount = 0; + } + + return total; +} + +unsigned int SampleSinkFifo::read(SampleVector::iterator begin, SampleVector::iterator end) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_size == 0) { + return 0; + } + + unsigned int count = end - begin; + unsigned int total; + unsigned int remaining; + unsigned int len; + + total = std::min(count, m_fill); + + if (total < count) + { + qCritical("SampleSinkFifo::read: (%s) underflow - missing %u samples", + qPrintable(m_label), count - total); + emit underflow(count - total); + } + + remaining = total; + + while (remaining > 0) + { + len = std::min(remaining, m_size - m_head); + std::copy(m_data.begin() + m_head, m_data.begin() + m_head + len, begin); + m_head += len; + m_head %= m_size; + m_fill -= len; + begin += len; + remaining -= len; + } + + return total; +} + +unsigned int SampleSinkFifo::readBegin(unsigned int count, + SampleVector::iterator* part1Begin, SampleVector::iterator* part1End, + SampleVector::iterator* part2Begin, SampleVector::iterator* part2End) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_size == 0) { + return 0; + } + + unsigned int total; + unsigned int remaining; + unsigned int len; + unsigned int head = m_head; + + total = std::min(count, m_fill); + + if (total < count) + { + qCritical("SampleSinkFifo::readBegin: (%s) underflow - missing %u samples", + qPrintable(m_label), count - total); + emit underflow(count - total); + } + + remaining = total; + + if (remaining > 0) + { + len = std::min(remaining, m_size - head); + *part1Begin = m_data.begin() + head; + *part1End = m_data.begin() + head + len; + head += len; + head %= m_size; + remaining -= len; + } + else + { + *part1Begin = m_data.end(); + *part1End = m_data.end(); + } + + if (remaining > 0) + { + len = std::min(remaining, m_size - head); + *part2Begin = m_data.begin() + head; + *part2End = m_data.begin() + head + len; + } + else + { + *part2Begin = m_data.end(); + *part2End = m_data.end(); + } + + return total; +} + +unsigned int SampleSinkFifo::readCommit(unsigned int count) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_size == 0) { + return 0; + } + + if (count > m_fill) + { + qCritical("SampleSinkFifo::readCommit: (%s) cannot commit more than available samples", qPrintable(m_label)); + count = m_fill; + } + + m_head = (m_head + count) % m_size; + m_fill -= count; + + return count; +} + +unsigned int SampleSinkFifo::getSizePolicy(unsigned int sampleRate) +{ + return (sampleRate/100)*64; // .64s +} diff --git a/android/app/src/main/cpp/dsp/samplesinkfifo.h b/android/app/src/main/cpp/dsp/samplesinkfifo.h new file mode 100644 index 0000000..c6715dd --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplesinkfifo.h @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SAMPLEFIFO_H +#define INCLUDE_SAMPLEFIFO_H + +#include +#include +#include +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API SampleSinkFifo : public QObject { + Q_OBJECT + +private: + QElapsedTimer m_msgRateTimer; + int m_suppressed; + SampleVector m_data; + int m_total; + unsigned int m_writtenSignalCount; + unsigned int m_writtenSignalRateDivider; + QRecursiveMutex m_mutex; + + unsigned int m_size; + unsigned int m_fill; + unsigned int m_head; + unsigned int m_tail; + QString m_label; + + void create(unsigned int s); + +public: + SampleSinkFifo(QObject* parent = nullptr); + SampleSinkFifo(int size, QObject* parent = nullptr); + SampleSinkFifo(const SampleSinkFifo& other); + ~SampleSinkFifo(); + + bool setSize(int size); + void reset(); + void setWrittenSignalRateDivider(unsigned int divider); + inline unsigned int size() { QMutexLocker mutexLocker(&m_mutex); unsigned int size = m_size; return size; } + inline unsigned int fill() { QMutexLocker mutexLocker(&m_mutex); unsigned int fill = m_fill; return fill; } + + unsigned int write(const quint8* data, unsigned int count); + unsigned int write(SampleVector::const_iterator begin, SampleVector::const_iterator end); + + unsigned int read(SampleVector::iterator begin, SampleVector::iterator end); + + unsigned int readBegin(unsigned int count, + SampleVector::iterator* part1Begin, SampleVector::iterator* part1End, + SampleVector::iterator* part2Begin, SampleVector::iterator* part2End); + unsigned int readCommit(unsigned int count); + void setLabel(const QString& label) { m_label = label; } + static unsigned int getSizePolicy(unsigned int sampleRate); + +signals: + void dataReady(); + void written(int nsamples, qint64 timestamp); + void overflow(int nsamples); + void underflow(int nsamples); +}; + +#endif // INCLUDE_SAMPLEFIFO_H diff --git a/android/app/src/main/cpp/dsp/samplesourcefifo.cpp b/android/app/src/main/cpp/dsp/samplesourcefifo.cpp new file mode 100644 index 0000000..8ae0b19 --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplesourcefifo.cpp @@ -0,0 +1,137 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Daniele Forsi // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "samplesourcefifo.h" + +const unsigned int SampleSourceFifo::m_rwDivisor = 2; +const unsigned int SampleSourceFifo::m_guardDivisor = 10; + +SampleSourceFifo::SampleSourceFifo(QObject *parent) : + QObject(parent) +{} + +SampleSourceFifo::SampleSourceFifo(unsigned int size, QObject *parent) : + QObject(parent) +{ + resize(size); +} + +void SampleSourceFifo::resize(unsigned int size) +{ + QMutexLocker mutexLocker(&m_mutex); + m_size = size; + m_lowGuard = m_size / m_guardDivisor; + m_highGuard = m_size - (m_size/m_guardDivisor); + m_midPoint = m_size / m_rwDivisor; + m_readCount = 0; + m_readHead = 0; + m_writeHead = m_midPoint; + m_data.resize(size); +} + +void SampleSourceFifo::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_readCount = 0; + m_readHead = 0; + m_writeHead = m_midPoint; +} + +SampleSourceFifo::~SampleSourceFifo() +{} + +void SampleSourceFifo::read( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to read + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets +) +{ + QMutexLocker mutexLocker(&m_mutex); + unsigned int spaceLeft = m_size - m_readHead; + m_readCount = m_readCount + amount < m_size ? m_readCount + amount : m_size; // cannot exceed FIFO size + + if (amount <= spaceLeft) + { + ipart1Begin = m_readHead; + ipart1End = m_readHead + amount; + ipart2Begin = m_size; + ipart2End = m_size; + m_readHead += amount; + } + else + { + unsigned int remaining = (amount < m_size ? amount : m_size) - spaceLeft; + ipart1Begin = m_readHead; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = remaining; + m_readHead = remaining; + } + + emit dataRead(); +} + +void SampleSourceFifo::write( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to write + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets +) +{ + QMutexLocker mutexLocker(&m_mutex); + unsigned int rwDelta = m_writeHead >= m_readHead ? m_writeHead - m_readHead : m_size - (m_readHead - m_writeHead); + + if (rwDelta < m_lowGuard) + { + qWarning("SampleSourceFifo::write: underrun (write too slow) using %d old samples", m_midPoint - m_lowGuard); + m_writeHead = m_readHead + m_midPoint < m_size ? m_readHead + m_midPoint : m_readHead + m_midPoint - m_size; + } + else if (rwDelta > m_highGuard) + { + qWarning("SampleSourceFifo::write: overrun (read too slow) dropping %d samples", m_highGuard - m_midPoint); + m_writeHead = m_readHead + m_midPoint < m_size ? m_readHead + m_midPoint : m_readHead + m_midPoint - m_size; + } + + unsigned int spaceLeft = m_size - m_writeHead; + + if (amount <= spaceLeft) + { + ipart1Begin = m_writeHead; + ipart1End = m_writeHead + amount; + ipart2Begin = m_size; + ipart2End = m_size; + m_writeHead += amount; + } + else + { + unsigned int remaining = (amount < m_size ? amount : m_size) - spaceLeft; + ipart1Begin = m_writeHead; + ipart1End = m_size; + ipart2Begin = 0; + ipart2End = remaining; + m_writeHead = remaining; + } + + m_readCount = amount < m_readCount ? m_readCount - amount : 0; // cannot be less than 0 +} + +unsigned int SampleSourceFifo::getSizePolicy(unsigned int sampleRate) +{ + return (sampleRate/100)*64; // .64s +} diff --git a/android/app/src/main/cpp/dsp/samplesourcefifo.h b/android/app/src/main/cpp/dsp/samplesourcefifo.h new file mode 100644 index 0000000..f026bc0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/samplesourcefifo.h @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2016, 2018-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_SAMPLESOURCEFIFO_H_ +#define SDRBASE_DSP_SAMPLESOURCEFIFO_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API SampleSourceFifo : public QObject { + Q_OBJECT +public: + SampleSourceFifo(QObject *parent = nullptr); + SampleSourceFifo(unsigned int size, QObject *parent = nullptr); + ~SampleSourceFifo(); + void resize(unsigned int size); + void reset(); + + SampleVector& getData() { return m_data; } + void read( + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to read + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets + ); + void write( //!< in place write + unsigned int amount, + unsigned int& ipart1Begin, unsigned int& ipart1End, // first part offsets where to write + unsigned int& ipart2Begin, unsigned int& ipart2End // second part offsets + ); + unsigned int remainder() + { + QMutexLocker mutexLocker(&m_mutex); + return m_readCount; + } + /** returns ratio of off center over buffer size with sign: negative read lags and positive read leads */ + float getRWBalance() const + { + int delta; + if (m_writeHead > m_readHead) { + delta = (m_size/m_rwDivisor) - (m_writeHead - m_readHead); + } else { + delta = (m_readHead - m_writeHead) - (m_size/m_rwDivisor); + } + return delta / (float) m_size; + } + unsigned int size() const { return m_size; } + + static unsigned int getSizePolicy(unsigned int sampleRate); + static const unsigned int m_rwDivisor; + static const unsigned int m_guardDivisor; + +signals: + void dataRead(); + +private: + SampleVector m_data; + unsigned int m_size; + unsigned int m_lowGuard; + unsigned int m_highGuard; + unsigned int m_midPoint; + unsigned int m_readHead; + unsigned int m_writeHead; + unsigned int m_readCount; + QMutex m_mutex; +}; + +#endif // SDRBASE_DSP_SAMPLESOURCEFIFO_H_ diff --git a/android/app/src/main/cpp/dsp/scopevis.cpp b/android/app/src/main/cpp/dsp/scopevis.cpp new file mode 100644 index 0000000..d69297b --- /dev/null +++ b/android/app/src/main/cpp/dsp/scopevis.cpp @@ -0,0 +1,1258 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017-2021, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "scopevis.h" +#include "spectrumvis.h" +#include "dsp/dspcommands.h" +#include "dsp/glscopeinterface.h" + +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgConfigureScopeVis, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisAddTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisChangeTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisRemoveTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisMoveTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisFocusOnTrigger, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisAddTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisChangeTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisRemoveTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisMoveTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisFocusOnTrace, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGOneShot, Message) +MESSAGE_CLASS_DEFINITION(ScopeVis::MsgScopeVisNGMemoryTrace, Message) + + +ScopeVis::ScopeVis() : + m_glScope(nullptr), + m_spectrumVis(nullptr), + m_ssbSpectrum(false), + m_preTriggerDelay(0), + m_livePreTriggerDelay(0), + m_currentTriggerIndex(0), + m_focusedTriggerIndex(0), + m_triggerState(TriggerUntriggered), + m_focusedTraceIndex(0), + m_nbStreams(1), + m_traceChunkSize(GLScopeSettings::m_traceChunkDefaultSize), + m_traceSize(GLScopeSettings::m_traceChunkDefaultSize), + m_liveTraceSize(GLScopeSettings::m_traceChunkDefaultSize), + m_nbSamples(0), + m_timeBase(1), + m_timeOfsProMill(0), + m_traceStart(true), + m_triggerLocation(0), + m_sampleRate(0), + m_liveSampleRate(0), + m_traceDiscreteMemory(GLScopeSettings::m_nbTraceMemories), + m_freeRun(true), + m_maxTraceDelay(0), + m_triggerOneShot(false), + m_triggerWaitForReset(false), + m_currentTraceMemoryIndex(0) +{ + setObjectName("ScopeVis"); + m_traceDiscreteMemory.resize(GLScopeSettings::m_traceChunkDefaultSize); // arbitrary + m_convertBuffers.resize(GLScopeSettings::m_traceChunkDefaultSize); + + for (int i = 0; i < (int) Projector::nbProjectionTypes; i++) { + m_projectorCache[i] = 0.0; + } + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +ScopeVis::~ScopeVis() +{ + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + for (std::vector::iterator it = m_triggerConditions.begin(); it != m_triggerConditions.end(); ++ it) { + delete *it; + } +} + +void ScopeVis::setGLScope(GLScopeInterface* glScope) +{ + m_glScope = glScope; + m_glScope->setTraces(&m_traces.m_tracesData, &m_traces.m_traces[0]); +} + +void ScopeVis::setLiveRate(int sampleRate) +{ + m_liveSampleRate = sampleRate; + + if (m_currentTraceMemoryIndex == 0) { // update only in live mode + setSampleRate(m_liveSampleRate); + } +} + +void ScopeVis::setSampleRate(int sampleRate) +{ + qDebug("ScopeVis::setSampleRate: %d S/s", sampleRate); + m_sampleRate = sampleRate; + + if (m_glScope) { + m_glScope->setSampleRate(m_sampleRate); + } +} + +void ScopeVis::setTraceSize(uint32_t traceSize, bool emitSignal) +{ + m_traceSize = traceSize; + m_traces.resize(m_traceSize); + m_traceDiscreteMemory.resize(m_traceSize); + initTraceBuffers(); + + if (m_glScope) { + m_glScope->setTraceSize(m_traceSize, emitSignal); + } +} + +void ScopeVis::setPreTriggerDelay(uint32_t preTriggerDelay, bool emitSignal) +{ + m_preTriggerDelay = preTriggerDelay; + + if (m_glScope) { + m_glScope->setTriggerPre(m_preTriggerDelay, emitSignal); + } +} + +void ScopeVis::setNbStreams(uint32_t nbStreams) +{ + QMutexLocker configLocker(&m_mutex); + + if (m_nbStreams != nbStreams) + { + m_traceDiscreteMemory.setNbStreams(nbStreams); + m_convertBuffers.setNbStreams(nbStreams); + m_nbStreams = nbStreams; + } +} + +void ScopeVis::configure( + uint32_t traceSize, + uint32_t timeBase, + uint32_t timeOfsProMill, + uint32_t triggerPre, + bool freeRun +) +{ + QMutexLocker configLocker(&m_mutex); + + if (m_traceSize != traceSize) + { + setTraceSize(traceSize); + m_settings.m_traceLenMult = traceSize / getTraceChunkSize(); + m_triggerState = TriggerUntriggered; + m_traces.resetControls(); + } + + if (m_timeBase != timeBase) + { + m_timeBase = timeBase; + m_settings.m_time = timeBase; + + if (m_glScope) { + m_glScope->setTimeBase(m_timeBase); + } + } + + if (m_timeOfsProMill != timeOfsProMill) + { + m_timeOfsProMill = timeOfsProMill; + m_settings.m_timeOfs = timeOfsProMill; + + if (m_glScope) { + m_glScope->setTimeOfsProMill(m_timeOfsProMill); + } + } + + if (m_preTriggerDelay != triggerPre) + { + setPreTriggerDelay(triggerPre); + m_settings.m_trigPre = triggerPre; + } + + if (freeRun != m_freeRun) { + m_freeRun = freeRun; + } + + qDebug() << "ScopeVis::configure:" + << " m_nbStreams: " << m_nbStreams + << " m_traceSize: " << m_traceSize + << " m_timeOfsProMill: " << m_timeOfsProMill + << " m_preTriggerDelay: " << m_preTriggerDelay + << " m_freeRun: " << m_freeRun; + + if ((m_glScope) && (m_currentTraceMemoryIndex > 0)) { + processMemoryTrace(); + } +} + +void ScopeVis::configure( + GLScopeSettings::DisplayMode displayMode, + uint32_t traceIntensity, + uint32_t gridIntensity +) +{ + QMutexLocker configLocker(&m_mutex); + + m_settings.m_displayMode = displayMode; + m_settings.m_traceIntensity = traceIntensity; + m_settings.m_gridIntensity = gridIntensity; + + qDebug() << "ScopeVis::configure:" + << " displayMode: " << displayMode + << " traceIntensity: " << traceIntensity + << " gridIntensity: " << gridIntensity; +} + +void ScopeVis::addTrace(const GLScopeSettings::TraceData& traceData) +{ + qDebug() << "ScopeVis::addTrace:" + << " trace: " << m_traces.size() + << " m_streamIndex: " << traceData.m_streamIndex + << " m_amp: " << traceData.m_amp + << " m_ofs: " << traceData.m_ofs + << " m_traceDelay: " << traceData.m_traceDelay; + m_traces.addTrace(traceData, m_traceSize); + initTraceBuffers(); + updateMaxTraceDelay(); + computeDisplayTriggerLevels(); + updateGLScopeDisplay(); + m_settings.m_tracesData.push_back(traceData); +} + +void ScopeVis::changeTrace(const GLScopeSettings::TraceData& traceData, uint32_t traceIndex) +{ + qDebug() << "ScopeVis::changeTrace:" + << " trace: " << traceIndex + << " m_streamIndex: " << traceData.m_streamIndex + << " m_amp: " << traceData.m_amp + << " m_ofs: " << traceData.m_ofs + << " m_traceDelay: " << traceData.m_traceDelay; + bool doComputeTriggerLevelsOnDisplay = m_traces.isVerticalDisplayChange(traceData, traceIndex); + m_traces.changeTrace(traceData, traceIndex); + updateMaxTraceDelay(); + + if (doComputeTriggerLevelsOnDisplay) { + computeDisplayTriggerLevels(); + } + + updateGLScopeDisplay(); + + if (traceIndex < m_settings.m_tracesData.size()) { + m_settings.m_tracesData[traceIndex] = traceData; + } +} + +void ScopeVis::removeTrace(uint32_t traceIndex) +{ + qDebug() << "ScopeVis::removeTrace:" + << " trace: " << traceIndex; + m_traces.removeTrace(traceIndex); + updateMaxTraceDelay(); + computeDisplayTriggerLevels(); + updateGLScopeDisplay(); + + unsigned int iDest = 0; + + for (unsigned int iSource = 0; iSource < m_settings.m_tracesData.size(); iSource++) + { + if (iSource != traceIndex) { + m_settings.m_tracesData[iDest++] = m_settings.m_tracesData[iSource]; + } + } + + if (m_settings.m_tracesData.size() != 0) { + m_settings.m_tracesData.pop_back(); + } +} + +void ScopeVis::moveTrace(uint32_t traceIndex, bool upElseDown) +{ + qDebug() << "ScopeVis::moveTrace:" + << " trace: " << traceIndex + << " up: " << upElseDown; + m_traces.moveTrace(traceIndex, upElseDown); + computeDisplayTriggerLevels(); + updateGLScopeDisplay(); + + int nextTraceIndex = (traceIndex + (upElseDown ? 1 : -1)) % m_settings.m_tracesData.size(); + GLScopeSettings::TraceData nextTraceData = m_settings.m_tracesData[nextTraceIndex]; + m_settings.m_tracesData[nextTraceIndex] = m_settings.m_tracesData[traceIndex]; + m_settings.m_tracesData[traceIndex] = nextTraceData; +} + +void ScopeVis::focusOnTrace(uint32_t traceIndex) +{ + if (traceIndex < m_traces.m_tracesData.size()) + { + m_focusedTraceIndex = traceIndex; + computeDisplayTriggerLevels(); + + if (m_glScope) { + m_glScope->setFocusedTraceIndex(m_focusedTraceIndex); + } + + updateGLScopeDisplay(); + } +} + +void ScopeVis::addTrigger(const GLScopeSettings::TriggerData& triggerData) +{ + m_triggerConditions.push_back(new TriggerCondition(triggerData)); + m_triggerConditions.back()->initProjector(); + m_settings.m_triggersData.push_back(triggerData); +} + +void ScopeVis::changeTrigger(const GLScopeSettings::TriggerData& triggerData, uint32_t triggerIndex) +{ + if (triggerIndex < m_triggerConditions.size()) + { + m_triggerConditions[triggerIndex]->setData(triggerData); + + if (triggerIndex == m_focusedTriggerIndex) + { + computeDisplayTriggerLevels(); + + if (m_glScope) { + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex]->m_triggerData); + } + + updateGLScopeDisplay(); + } + } + + if (triggerIndex < m_settings.m_triggersData.size()) { + m_settings.m_triggersData[triggerIndex] = triggerData; + } +} + +void ScopeVis::removeTrigger(uint32_t triggerIndex) +{ + if (triggerIndex < m_triggerConditions.size()) + { + TriggerCondition *triggerCondition = m_triggerConditions[triggerIndex]; + m_triggerConditions.erase(m_triggerConditions.begin() + triggerIndex); + delete triggerCondition; + } + + unsigned int iDest = 0; + + for (unsigned int iSource = 0; iSource < m_settings.m_triggersData.size(); iSource++) + { + if (iSource != triggerIndex) { + m_settings.m_triggersData[iDest++] = m_settings.m_triggersData[iSource]; + } + } + + if (m_settings.m_triggersData.size() != 0) { + m_settings.m_triggersData.pop_back(); + } +} + +void ScopeVis::moveTrigger(uint32_t triggerIndex, bool upElseDown) +{ + int nextTriggerIndex = (triggerIndex + (upElseDown ? 1 : -1)) % m_triggerConditions.size(); + + TriggerCondition *nextTrigger = m_triggerConditions[nextTriggerIndex]; + m_triggerConditions[nextTriggerIndex] = m_triggerConditions[triggerIndex]; + m_triggerConditions[triggerIndex] = nextTrigger; + + computeDisplayTriggerLevels(); + + if (m_glScope) { + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex]->m_triggerData); + } + + updateGLScopeDisplay(); + + int nextTriggerIndexSettings = (triggerIndex + (upElseDown ? 1 : -1)) % m_settings.m_triggersData.size(); + GLScopeSettings::TriggerData nextTriggerData = m_settings.m_triggersData[nextTriggerIndexSettings]; + m_settings.m_triggersData[nextTriggerIndexSettings] = m_settings.m_triggersData[triggerIndex]; + m_settings.m_triggersData[triggerIndex] = nextTriggerData; +} + +void ScopeVis::focusOnTrigger(uint32_t triggerIndex) +{ + if (triggerIndex < m_triggerConditions.size()) + { + m_focusedTriggerIndex = triggerIndex; + computeDisplayTriggerLevels(); + + if (m_glScope) { + m_glScope->setFocusedTriggerData(m_triggerConditions[m_focusedTriggerIndex]->m_triggerData); + } + + updateGLScopeDisplay(); + } +} + +void ScopeVis::setOneShot(bool oneShot) +{ + Message* cmd = MsgScopeVisNGOneShot::create(oneShot); + getInputMessageQueue()->push(cmd); +} + +void ScopeVis::setMemoryIndex(uint32_t memoryIndex) +{ + Message* cmd = MsgScopeVisNGMemoryTrace::create(memoryIndex); + getInputMessageQueue()->push(cmd); +} + +void ScopeVis::feed(const std::vector& vbegin, int nbSamples) +{ + std::vector vcbegin; + std::vector& convertBuffers = m_convertBuffers.getBuffers(); + + if (nbSamples > (int) m_convertBuffers.size()) { + m_convertBuffers.resize(nbSamples); + } + + for (unsigned int s = 0; s < vbegin.size(); s++) + { + std::transform( + vbegin[s], + vbegin[s] + nbSamples, + convertBuffers[s].begin(), + [](const Sample& s) -> Complex { + return Complex{s.m_real / SDR_RX_SCALEF, s.m_imag / SDR_RX_SCALEF}; + } + ); + vcbegin.push_back(convertBuffers[s].begin()); + } + + feed(vcbegin, nbSamples); +} + +void ScopeVis::feed(const std::vector& vbegin, int nbSamples) +{ + if (vbegin.size() == 0) { + return; + } + + if (m_currentTraceMemoryIndex > 0) { // in memory mode live trace is suspended + return; + } + + if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process + return; + } + + if (m_triggerWaitForReset) + { + m_triggerLocation = 0; + m_mutex.unlock(); + return; + } + + if (m_freeRun) { + m_triggerLocation = nbSamples; + } + else if (m_triggerState == TriggerTriggered) { + m_triggerLocation = nbSamples; + } + else if (m_triggerState == TriggerUntriggered) { + m_triggerLocation = 0; + } + else { + m_triggerLocation = nbSamples; + } + + // ComplexVector::const_iterator begin(vbegin[0]); + //const SampleVector::const_iterator end = vbegin[0] + nbSamples; + int triggerPointToEnd; + int remainder = nbSamples; + std::vector nvbegin(vbegin); + + //while (begin < end) + while (remainder > 0) + { + if (remainder < (int) m_traceSize) // buffer smaller than trace size (end - bagin) < m_traceSize + { + triggerPointToEnd = -1; + processTrace(nvbegin, remainder, triggerPointToEnd); // use all buffer + m_triggerLocation = triggerPointToEnd < 0 ? 0 : triggerPointToEnd; // trim negative values + m_triggerLocation = m_triggerLocation > remainder ? remainder : m_triggerLocation; // trim past begin values + + remainder = 0; // effectively breaks out the loop + } + else // trace size fits in buffer + { + triggerPointToEnd = -1; + processTrace(nvbegin, m_traceSize, triggerPointToEnd); // use part of buffer to fit trace size + //m_triggerPoint = begin + m_traceSize - triggerPointToEnd; + m_triggerLocation = remainder + m_traceSize - triggerPointToEnd; // should always refer to end iterator + m_triggerLocation = m_triggerLocation < 0 ? 0 : m_triggerLocation; // trim negative values + m_triggerLocation = m_triggerLocation > remainder ? remainder : m_triggerLocation; // trim past begin values + + for (auto begin : nvbegin) { + begin += m_traceSize; + } + + remainder -= m_traceSize; + } + } + + m_mutex.unlock(); +} + +void ScopeVis::processMemoryTrace() +{ + if ((m_currentTraceMemoryIndex > 0) && (m_currentTraceMemoryIndex <= m_traceDiscreteMemory.maxIndex())) + { + int traceMemoryIndex = m_traceDiscreteMemory.currentIndex() - m_currentTraceMemoryIndex; // actual index in memory bank + + if (traceMemoryIndex < 0) { + traceMemoryIndex += GLScopeSettings::m_nbTraceMemories; + } + + std::vector mend; + m_traceDiscreteMemory.getEndPointAt(traceMemoryIndex, mend); + std::vector mbegin(mend.size()); + TraceBackDiscreteMemory::moveIt(mend, mbegin, -m_traceSize); + std::vector mbegin_tb(mbegin.size()); + TraceBackDiscreteMemory::moveIt(mbegin, mbegin_tb, -m_maxTraceDelay); + m_nbSamples = m_traceSize + m_maxTraceDelay; + + processTraces(mbegin_tb, m_maxTraceDelay, true); // traceback + processTraces(mbegin, m_traceSize, false); + } +} + +void ScopeVis::processTrace(const std::vector& vcbegin, int length, int& triggerPointToEnd) +{ + std::vector vbegin(vcbegin); + int firstRemainder = length; + + // memory storage + + m_traceDiscreteMemory.writeCurrent(vbegin, length); + + // Removed in 4.2.4 may cause trigger bug + // if (m_traceDiscreteMemory.current().absoluteFill() < m_traceSize) + // { + // return; // not enough samples in memory + // } + + // trigger process + + if ((m_freeRun) || (m_triggerConditions.size() == 0)) // immediate re-trigger + { + if (m_triggerState == TriggerUntriggered) + { + m_traceStart = true; // start trace processing + m_nbSamples = m_traceSize + m_maxTraceDelay; + m_triggerState = TriggerTriggered; + } + } + else if ((m_triggerState == TriggerUntriggered) || (m_triggerState == TriggerDelay)) // look for trigger or past trigger in delay mode + { + TriggerCondition* triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition + int processed = 0; + + while (firstRemainder > 0) + { + if (m_triggerState == TriggerDelay) // delayed trigger + { + if (triggerCondition->m_triggerDelayCount > 0) // skip samples during delay period + { + for (auto begin : vbegin) { + begin += triggerCondition->m_triggerDelayCount; + } + processed += triggerCondition->m_triggerDelayCount; + firstRemainder -= triggerCondition->m_triggerDelayCount; + triggerCondition->m_triggerDelayCount = 0; + continue; + } + else // process trigger + { + if (nextTrigger()) // move to next trigger and keep going + { + m_triggerComparator.reset(); + m_triggerState = TriggerUntriggered; + for (auto begin : vbegin) { + ++begin; + } + ++processed; + --firstRemainder; + continue; + } + else // this was the last trigger then start trace + { + m_traceStart = true; // start trace processing + m_nbSamples = m_traceSize + m_maxTraceDelay; + m_triggerComparator.reset(); + m_triggerState = TriggerTriggered; + triggerPointToEnd = firstRemainder; + break; + } + } + } + + uint32_t triggerStreamIndex = triggerCondition->m_triggerData.m_streamIndex; + const Complex& s = vbegin[triggerStreamIndex][processed]; + + if (m_triggerComparator.triggered(s, *triggerCondition)) // matched the current trigger + { + if (triggerCondition->m_triggerData.m_triggerDelay > 0) + { + triggerCondition->m_triggerDelayCount = triggerCondition->m_triggerData.m_triggerDelay; // initialize delayed samples counter + m_triggerState = TriggerDelay; + for (auto begin : vbegin) { + ++begin; + } + ++processed; + --firstRemainder; + continue; + } + + if (nextTrigger()) // move to next trigger and keep going + { + m_triggerComparator.reset(); + m_triggerState = TriggerUntriggered; + } + else // this was the last trigger then start trace + { + m_traceStart = true; // start of trace processing + m_nbSamples = m_traceSize + m_maxTraceDelay; + m_triggerComparator.reset(); + m_triggerState = TriggerTriggered; + triggerPointToEnd = firstRemainder; + break; + } + } + + for (auto begin : vbegin) { + ++begin; + } + + ++processed; + --firstRemainder; + } // look for trigger + } // untriggered or delayed + + // trace process + + if (m_triggerState == TriggerTriggered) + { + int remainder; + int count = firstRemainder; // number of samples in traceback buffer past the current point + std::vector mend; + m_traceDiscreteMemory.getCurrent(mend); + std::vector mbegin(mend.size()); + TraceBackDiscreteMemory::moveIt(mend, mbegin, -count); + + if (m_traceStart) // start of trace processing + { + // if trace time is 1s or more the display is progressive so we have to clear it first + + float traceTime = ((float) m_traceSize) / m_sampleRate; + + if (traceTime >= 1.0f) { + initTraceBuffers(); + } + + // process until begin point + + if (m_maxTraceDelay > 0) + { // trace back + std::vector tbegin(mbegin.size()); + TraceBackDiscreteMemory::moveIt(mbegin, tbegin, - m_preTriggerDelay - m_maxTraceDelay); + processTraces(tbegin, m_maxTraceDelay, true); + } + + if (m_preTriggerDelay > 0) + { // pre-trigger + std::vector tbegin(mbegin.size()); + TraceBackDiscreteMemory::moveIt(mbegin, tbegin, -m_preTriggerDelay); + processTraces(tbegin, m_preTriggerDelay); + } + + // process the rest of the trace + + remainder = processTraces(mbegin, count); + m_traceStart = false; + } + else // process the current trace + { + remainder = processTraces(mbegin, count); + } + + if (remainder >= 0) // finished + { + TraceBackDiscreteMemory::moveIt(mend, mbegin, -remainder); + m_traceDiscreteMemory.setCurrentEndPoint(mbegin); + m_traceDiscreteMemory.store(m_preTriggerDelay+remainder); // next memory trace. + m_triggerState = TriggerUntriggered; + m_triggerWaitForReset = m_triggerOneShot; + + //if (m_glScope) m_glScope->incrementTraceCounter(); + + // process remainder recursively + if (remainder != 0) + { + int mTriggerPointToEnd = -1; + processTrace(mbegin, remainder, mTriggerPointToEnd); + + if (mTriggerPointToEnd >= 0) { + triggerPointToEnd = mTriggerPointToEnd; + } + + //qDebug("ScopeVis::processTrace: process remainder recursively (%d %d)", mpoint, mTriggerPoint); + } + } + } +} + +bool ScopeVis::nextTrigger() +{ + TriggerCondition *triggerCondition = m_triggerConditions[m_currentTriggerIndex]; // current trigger condition + + if (triggerCondition->m_triggerData.m_triggerRepeat > 0) + { + if (triggerCondition->m_triggerCounter < triggerCondition->m_triggerData.m_triggerRepeat) + { + triggerCondition->m_triggerCounter++; + return true; // not final keep going + } + else + { + triggerCondition->m_triggerCounter = 0; // reset for next time + } + } + + if (m_triggerConditions.size() == 0) + { + m_currentTriggerIndex = 0; + return false; // final + } + else if (m_currentTriggerIndex < m_triggerConditions.size() - 1) // check if next trigger is available + { + m_currentTriggerIndex++; + return true; // not final keep going + } + else + { + // now this is really finished + m_currentTriggerIndex = 0; + return false; // final + } +} + +int ScopeVis::processTraces(const std::vector& vcbegin, int ilength, bool traceBack) +{ + std::vector vbegin(vcbegin); + uint32_t shift = (m_timeOfsProMill / 1000.0) * m_traceSize; + uint32_t length = m_traceSize / m_timeBase; + int remainder = ilength; + + if (m_spectrumVis) { + m_spectrumVis->feed(vcbegin[0], vcbegin[0] + ilength, m_ssbSpectrum); + } + + while ((remainder > 0) && (m_nbSamples > 0)) + { + std::vector::iterator itCtl = m_traces.m_tracesControl.begin(); + std::vector::iterator itData = m_traces.m_tracesData.begin(); + std::vector::iterator itTrace = m_traces.m_traces[m_traces.currentBufferIndex()].begin(); + + for (unsigned int ti = 0; itCtl != m_traces.m_tracesControl.end(); ++itCtl, ++itData, ++itTrace, ti++) + { + if (traceBack && ((remainder) > itData->m_traceDelay)) { // before start of trace + continue; + } + + Projector::ProjectionType projectionType = itData->m_projectionType; + + if ((*itCtl)->m_traceCount[m_traces.currentBufferIndex()] < m_traceSize) + { + uint32_t& traceCount = (*itCtl)->m_traceCount[m_traces.currentBufferIndex()]; // reference for code clarity + float v; + uint32_t streamIndex = itData->m_streamIndex; + + if (projectionType == Projector::ProjectionMagLin) + { + v = ((*itCtl)->m_projector.run(*vbegin[streamIndex]) - itData->m_ofs)*itData->m_amp - 1.0f; + } + else if (projectionType == Projector::ProjectionMagSq) + { + Real magsq = (*itCtl)->m_projector.run(*vbegin[streamIndex]); + v = (magsq - itData->m_ofs)*itData->m_amp - 1.0f; + + if ((traceCount >= shift) && (traceCount < shift+length)) // power display overlay values construction + { + if (traceCount == shift) + { + (*itCtl)->m_maxPow = 0.0f; + (*itCtl)->m_sumPow = 0.0f; + (*itCtl)->m_nbPow = 1; + } + + if (magsq > 0.0f) + { + if (magsq > (*itCtl)->m_maxPow) + { + (*itCtl)->m_maxPow = magsq; + } + + (*itCtl)->m_sumPow += magsq; + (*itCtl)->m_nbPow++; + } + } + + if ((m_nbSamples == 1) && ((*itCtl)->m_nbPow > 0)) // on last sample create power display overlay + { + double avgPow = (*itCtl)->m_sumPow / (*itCtl)->m_nbPow; + itData->m_textOverlay = QString("%1 %2").arg((*itCtl)->m_maxPow, 0, 'e', 2).arg(avgPow, 0, 'e', 2); + (*itCtl)->m_nbPow = 0; + } + } + else if (projectionType == Projector::ProjectionMagDB) + { + Real re = vbegin[streamIndex]->real(); + Real im = vbegin[streamIndex]->imag(); + double magsq = re*re + im*im; + float pdB = log10f(magsq) * 10.0f; + float p = pdB - (100.0f * itData->m_ofs); + v = ((p/50.0f) + 2.0f)*itData->m_amp - 1.0f; + + if ((traceCount >= shift) && (traceCount < shift+length)) // power display overlay values construction + { + if (traceCount == shift) + { + (*itCtl)->m_maxPow = 0.0f; + (*itCtl)->m_sumPow = 0.0f; + (*itCtl)->m_nbPow = 1; + } + + if (magsq > 0.0f) + { + if (magsq > (*itCtl)->m_maxPow) + { + (*itCtl)->m_maxPow = magsq; + } + + (*itCtl)->m_sumPow += magsq; + (*itCtl)->m_nbPow++; + } + } + + if ((m_nbSamples == 1) && ((*itCtl)->m_nbPow > 0)) // on last sample create power display overlay + { + double avgPow = log10f((*itCtl)->m_sumPow / (*itCtl)->m_nbPow)*10.0; + double peakPow = log10f((*itCtl)->m_maxPow)*10.0; + double peakToAvgPow = peakPow - avgPow; + itData->m_textOverlay = QString("%1 %2 %3").arg(peakPow, 0, 'f', 1).arg(avgPow, 0, 'f', 1).arg(peakToAvgPow, 4, 'f', 1, ' '); + (*itCtl)->m_nbPow = 0; + } + } + else + { + v = ((*itCtl)->m_projector.run(*vbegin[streamIndex]) - itData->m_ofs) * itData->m_amp; + } + + if(v > 1.0f) { + v = 1.0f; + } else if (v < -1.0f) { + v = -1.0f; + } + + (*itTrace)[2*traceCount] + = traceCount - shift; // display x + (*itTrace)[2*traceCount + 1] = v; // display y + traceCount++; + } // process one sample + } // loop on traces + + for (unsigned int i = 0; i < vbegin.size(); i++) { + ++vbegin[i]; + } + remainder--; + m_nbSamples--; + } // loop on samples + + float traceTime = ((float) m_traceSize) / m_sampleRate; + + if (m_glScope && (traceTime >= 1.0f)) { // display continuously if trace time is 1 second or more + m_glScope->newTraces(m_traces.m_traces, m_traces.currentBufferIndex(), &m_traces.m_projectionTypes); + } + + if (m_glScope && (m_nbSamples == 0)) // finished + { + // display only at trace end if trace time is less than 1 second + if (traceTime < 1.0f) + { +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + if (m_glScope->getProcessingTraceIndex().loadRelaxed() < 0) { + m_glScope->newTraces(m_traces.m_traces, m_traces.currentBufferIndex(), &m_traces.m_projectionTypes); + } +#else + if (m_glScope->getProcessingTraceIndex().load() < 0) { + m_glScope->newTraces(m_traces.m_traces, m_traces.currentBufferIndex(), &m_traces.m_projectionTypes); + } +#endif + } + + // switch to next buffer only if it is not being processed by the scope +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + if (m_glScope->getProcessingTraceIndex().loadRelaxed() != (((int) m_traces.currentBufferIndex() + 1) % 2)) { + m_traces.switchBuffer(); + } +#else + if (m_glScope->getProcessingTraceIndex().load() != (((int) m_traces.currentBufferIndex() + 1) % 2)) { + m_traces.switchBuffer(); + } +#endif + + return remainder; // return remainder count + } + else + { + return -1; // mark not finished + } +} + +void ScopeVis::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool ScopeVis::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + QMutexLocker configLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) message; + setLiveRate(notif.getSampleRate()); + qDebug() << "ScopeVis::handleMessage: DSPSignalNotification: m_sampleRate: " << m_sampleRate; + return true; + } + else if (MsgConfigureScopeVis::match(message)) + { + QMutexLocker configLocker(&m_mutex); + const MsgConfigureScopeVis& cmd = (const MsgConfigureScopeVis&) message; + applySettings(cmd.getSettings(), cmd.getForce()); + return true; + } + else if (MsgScopeVisAddTrigger::match(message)) + { + qDebug() << "ScopeVis::handleMessage: MsgScopeVisAddTrigger"; + QMutexLocker configLocker(&m_mutex); + MsgScopeVisAddTrigger& conf = (MsgScopeVisAddTrigger&) message; + addTrigger(conf.getTriggerData()); + return true; + } + else if (MsgScopeVisChangeTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisChangeTrigger& conf = (MsgScopeVisChangeTrigger&) message; + uint32_t triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisChangeTrigger: " << triggerIndex; + changeTrigger(conf.getTriggerData(), triggerIndex); + return true; + } + else if (MsgScopeVisRemoveTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisRemoveTrigger& conf = (MsgScopeVisRemoveTrigger&) message; + uint32_t triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisRemoveTrigger: " << triggerIndex; + removeTrigger(triggerIndex); + return true; + } + else if (MsgScopeVisMoveTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisMoveTrigger& conf = (MsgScopeVisMoveTrigger&) message; + int triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisMoveTrigger: " << triggerIndex; + + if (!conf.getMoveUp() && (triggerIndex == 0)) { + return true; + } + + moveTrigger(triggerIndex, conf.getMoveUp()); + return true; + } + else if (MsgScopeVisFocusOnTrigger::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisFocusOnTrigger& conf = (MsgScopeVisFocusOnTrigger&) message; + uint32_t triggerIndex = conf.getTriggerIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisFocusOnTrigger: " << triggerIndex; + focusOnTrigger(triggerIndex); + return true; + } + else if (MsgScopeVisAddTrace::match(message)) + { + qDebug() << "ScopeVis::handleMessage: MsgScopeVisAddTrace"; + QMutexLocker configLocker(&m_mutex); + MsgScopeVisAddTrace& conf = (MsgScopeVisAddTrace&) message; + addTrace(conf.getTraceData()); + return true; + } + else if (MsgScopeVisChangeTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisChangeTrace& conf = (MsgScopeVisChangeTrace&) message; + uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisChangeTrace: " << traceIndex; + changeTrace(conf.getTraceData(), traceIndex); + return true; + } + else if (MsgScopeVisRemoveTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisRemoveTrace& conf = (MsgScopeVisRemoveTrace&) message; + uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisRemoveTrace: " << traceIndex; + removeTrace(traceIndex); + return true; + } + else if (MsgScopeVisMoveTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisMoveTrace& conf = (MsgScopeVisMoveTrace&) message; + uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisMoveTrace: " << traceIndex; + moveTrace(traceIndex, conf.getMoveUp()); + return true; + } + else if (MsgScopeVisFocusOnTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisFocusOnTrace& conf = (MsgScopeVisFocusOnTrace&) message; + uint32_t traceIndex = conf.getTraceIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisFocusOnTrace: " << traceIndex; + focusOnTrace(traceIndex); + return true; + } + else if (MsgScopeVisNGOneShot::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGOneShot& conf = (MsgScopeVisNGOneShot&) message; + bool oneShot = conf.getOneShot(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGOneShot: oneShot:" << oneShot; + m_triggerOneShot = oneShot; + + if (m_triggerWaitForReset && !oneShot) { + m_triggerWaitForReset = false; + } + + return true; + } + else if (MsgScopeVisNGMemoryTrace::match(message)) + { + QMutexLocker configLocker(&m_mutex); + MsgScopeVisNGMemoryTrace& conf = (MsgScopeVisNGMemoryTrace&) message; + uint32_t memoryIndex = conf.getMemoryIndex(); + qDebug() << "ScopeVis::handleMessage: MsgScopeVisNGMemoryTrace: " << memoryIndex; + + if (memoryIndex != m_currentTraceMemoryIndex) + { + // transition from live mode + if (m_currentTraceMemoryIndex == 0) + { + m_liveTraceSize = m_traceSize; + m_livePreTriggerDelay = m_preTriggerDelay; + } + + m_currentTraceMemoryIndex = memoryIndex; + + // transition to live mode + if (m_currentTraceMemoryIndex == 0) + { + setLiveRate(m_liveSampleRate); // reset to live rate + setTraceSize(m_liveTraceSize, true); // reset to live trace size + setPreTriggerDelay(m_livePreTriggerDelay, true); // reset to live pre-trigger delay + } + else + { + processMemoryTrace(); + } + } + return true; + } + else + { + qDebug() << "ScopeVis::handleMessage" << message.getIdentifier() << " not handled"; + return false; + } +} + +void ScopeVis::applySettings(const GLScopeSettings& settings, bool force) +{ + (void) force; + + if (m_traces.size() > m_settings.m_tracesData.size()) + { + for (unsigned int i = m_traces.size(); i > m_settings.m_tracesData.size(); i--) { + removeTrace(i-1); + } + } + + for (unsigned int i = 0; i < m_settings.m_tracesData.size(); i++) + { + if (i < m_traces.size()) { // change trace + changeTrace(settings.m_tracesData[i], i); + } else { // add trace + addTrace(settings.m_tracesData[i]); + } + } + + m_settings = settings; +} + +void ScopeVis::updateMaxTraceDelay() +{ + int maxTraceDelay = 0; + bool allocateCache = false; + uint32_t projectorCounts[(int) Projector::nbProjectionTypes]; + memset(projectorCounts, 0, ((int) Projector::nbProjectionTypes)*sizeof(uint32_t)); + std::vector::iterator itData = m_traces.m_tracesData.begin(); + std::vector::iterator itCtrl = m_traces.m_tracesControl.begin(); + + for (; itData != m_traces.m_tracesData.end(); ++itData, ++itCtrl) + { + if (itData->m_traceDelay > maxTraceDelay) + { + maxTraceDelay = itData->m_traceDelay; + } + + if (itData->m_projectionType < 0) { + itData->m_projectionType = Projector::ProjectionReal; + } + + if (m_nbStreams <= 1) // Works only for single stream mode. Fixes #872 + { + if (projectorCounts[(int) itData->m_projectionType] > 0) + { + allocateCache = true; + (*itCtrl)->m_projector.setCacheMaster(false); + } + else + { + (*itCtrl)->m_projector.setCacheMaster(true); + } + } + + projectorCounts[(int) itData->m_projectionType]++; + } + + itCtrl = m_traces.m_tracesControl.begin(); + + for (; itCtrl != m_traces.m_tracesControl.end(); ++itCtrl) + { + if (allocateCache) { + (*itCtrl)->m_projector.setCache(m_projectorCache); + } else { + (*itCtrl)->m_projector.setCache(nullptr); + } + } + + m_maxTraceDelay = maxTraceDelay; +} + +void ScopeVis::initTraceBuffers() +{ + int shift = (m_timeOfsProMill / 1000.0) * m_traceSize; + + std::vector::iterator it0 = m_traces.m_traces[0].begin(); + std::vector::iterator it1 = m_traces.m_traces[1].begin(); + + for (; it0 != m_traces.m_traces[0].end(); ++it0, ++it1) + { + for (unsigned int i = 0; i < m_traceSize; i++) + { + (*it0)[2*i] = (i - shift); // display x + (*it0)[2*i + 1] = 0.0f; // display y + (*it1)[2*i] = (i - shift); // display x + (*it1)[2*i + 1] = 0.0f; // display y + } + } +} + +void ScopeVis::computeDisplayTriggerLevels() +{ + std::vector::iterator itData = m_traces.m_tracesData.begin(); + + for (; itData != m_traces.m_tracesData.end(); ++itData) + { + if ((m_focusedTriggerIndex < m_triggerConditions.size()) && (m_triggerConditions[m_focusedTriggerIndex]->m_projector.getProjectionType() == itData->m_projectionType)) + { + float level = m_triggerConditions[m_focusedTriggerIndex]->m_triggerData.m_triggerLevel; + float levelPowerLin = level + 1.0f; + float levelPowerdB = (100.0f * (level - 1.0f)); + float v; + + if ((itData->m_projectionType == Projector::ProjectionMagLin) || (itData->m_projectionType == Projector::ProjectionMagSq)) + { + v = (levelPowerLin - itData->m_ofs)*itData->m_amp - 1.0f; + } + else if (itData->m_projectionType == Projector::ProjectionMagDB) + { + float ofsdB = itData->m_ofs * 100.0f; + v = ((levelPowerdB + 100.0f - ofsdB)*itData->m_amp)/50.0f - 1.0f; + } + else + { + v = (level - itData->m_ofs) * itData->m_amp; + } + + if(v > 1.0f) { + v = 1.0f; + } else if (v < -1.0f) { + v = -1.0f; + } + + itData->m_triggerDisplayLevel = v; + } + else + { + itData->m_triggerDisplayLevel = 2.0f; + } + } +} + +void ScopeVis::updateGLScopeDisplay() +{ + if (!m_glScope) { + return; + } + + if (m_currentTraceMemoryIndex > 0) + { + m_glScope->setConfigChanged(); + processMemoryTrace(); + } + else + { + m_glScope->updateDisplay(); + } +} diff --git a/android/app/src/main/cpp/dsp/scopevis.h b/android/app/src/main/cpp/dsp/scopevis.h new file mode 100644 index 0000000..da1023c --- /dev/null +++ b/android/app/src/main/cpp/dsp/scopevis.h @@ -0,0 +1,1338 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017-2021, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2019 Martin Hauke // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_SCOPEVISNG_H_ +#define SDRBASE_DSP_SCOPEVISNG_H_ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/projector.h" +#include "dsp/glscopesettings.h" +#include "export.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "util/doublebuffer.h" + + +class GLScopeInterface; +class SpectrumVis; + +class SDRBASE_API ScopeVis : public QObject { + Q_OBJECT +public: + // === messages === + // --------------------------------------------- + class SDRBASE_API MsgConfigureScopeVis : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const GLScopeSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureScopeVis* create(const GLScopeSettings& settings, bool force) + { + return new MsgConfigureScopeVis(settings, force); + } + + private: + GLScopeSettings m_settings; + bool m_force; + + MsgConfigureScopeVis(const GLScopeSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisAddTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisAddTrigger* create( + const GLScopeSettings::TriggerData& triggerData) + { + return new MsgScopeVisAddTrigger(triggerData); + } + + const GLScopeSettings::TriggerData& getTriggerData() const { return m_triggerData; } + + private: + GLScopeSettings::TriggerData m_triggerData; + + MsgScopeVisAddTrigger(const GLScopeSettings::TriggerData& triggerData) : + m_triggerData(triggerData) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisChangeTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisChangeTrigger* create( + const GLScopeSettings::TriggerData& triggerData, uint32_t triggerIndex) + { + return new MsgScopeVisChangeTrigger(triggerData, triggerIndex); + } + + const GLScopeSettings::TriggerData& getTriggerData() const { return m_triggerData; } + uint32_t getTriggerIndex() const { return m_triggerIndex; } + + private: + GLScopeSettings::TriggerData m_triggerData; + uint32_t m_triggerIndex; + + MsgScopeVisChangeTrigger(const GLScopeSettings::TriggerData& triggerData, uint32_t triggerIndex) : + m_triggerData(triggerData), + m_triggerIndex(triggerIndex) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisRemoveTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisRemoveTrigger* create( + uint32_t triggerIndex) + { + return new MsgScopeVisRemoveTrigger(triggerIndex); + } + + uint32_t getTriggerIndex() const { return m_triggerIndex; } + + private: + uint32_t m_triggerIndex; + + MsgScopeVisRemoveTrigger(uint32_t triggerIndex) : + m_triggerIndex(triggerIndex) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisMoveTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisMoveTrigger* create( + uint32_t triggerIndex, + bool moveUpElseDown) + { + return new MsgScopeVisMoveTrigger(triggerIndex, moveUpElseDown); + } + + uint32_t getTriggerIndex() const { return m_triggerIndex; } + bool getMoveUp() const { return m_moveUpElseDown; } + + private: + uint32_t m_triggerIndex; + bool m_moveUpElseDown; + + MsgScopeVisMoveTrigger(uint32_t triggerIndex, bool moveUpElseDown) : + m_triggerIndex(triggerIndex), + m_moveUpElseDown(moveUpElseDown) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisFocusOnTrigger : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisFocusOnTrigger* create( + uint32_t triggerIndex) + { + return new MsgScopeVisFocusOnTrigger(triggerIndex); + } + + uint32_t getTriggerIndex() const { return m_triggerIndex; } + + private: + uint32_t m_triggerIndex; + + MsgScopeVisFocusOnTrigger(uint32_t triggerIndex) : + m_triggerIndex(triggerIndex) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisAddTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisAddTrace* create( + const GLScopeSettings::TraceData& traceData) + { + return new MsgScopeVisAddTrace(traceData); + } + + const GLScopeSettings::TraceData& getTraceData() const { return m_traceData; } + + private: + GLScopeSettings::TraceData m_traceData; + + MsgScopeVisAddTrace(const GLScopeSettings::TraceData& traceData) : + m_traceData(traceData) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisChangeTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisChangeTrace* create( + const GLScopeSettings::TraceData& traceData, uint32_t traceIndex) + { + return new MsgScopeVisChangeTrace(traceData, traceIndex); + } + + const GLScopeSettings::TraceData& getTraceData() const { return m_traceData; } + uint32_t getTraceIndex() const { return m_traceIndex; } + + private: + GLScopeSettings::TraceData m_traceData; + uint32_t m_traceIndex; + + MsgScopeVisChangeTrace(GLScopeSettings::TraceData traceData, uint32_t traceIndex) : + m_traceData(traceData), + m_traceIndex(traceIndex) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisRemoveTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisRemoveTrace* create( + uint32_t traceIndex) + { + return new MsgScopeVisRemoveTrace(traceIndex); + } + + uint32_t getTraceIndex() const { return m_traceIndex; } + + private: + uint32_t m_traceIndex; + + MsgScopeVisRemoveTrace(uint32_t traceIndex) : + m_traceIndex(traceIndex) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisMoveTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisMoveTrace* create( + uint32_t traceIndex, + bool moveUpElseDown) + { + return new MsgScopeVisMoveTrace(traceIndex, moveUpElseDown); + } + + uint32_t getTraceIndex() const { return m_traceIndex; } + bool getMoveUp() const { return m_moveUpElseDown; } + + private: + uint32_t m_traceIndex; + bool m_moveUpElseDown; + + MsgScopeVisMoveTrace(uint32_t traceIndex, bool moveUpElseDown) : + m_traceIndex(traceIndex), + m_moveUpElseDown(moveUpElseDown) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisFocusOnTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisFocusOnTrace* create( + uint32_t traceIndex) + { + return new MsgScopeVisFocusOnTrace(traceIndex); + } + + uint32_t getTraceIndex() const { return m_traceIndex; } + + private: + uint32_t m_traceIndex; + + MsgScopeVisFocusOnTrace(uint32_t traceIndex) : + m_traceIndex(traceIndex) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisNGOneShot : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGOneShot* create( + bool oneShot) + { + return new MsgScopeVisNGOneShot(oneShot); + } + + bool getOneShot() const { return m_oneShot; } + + private: + bool m_oneShot; + + MsgScopeVisNGOneShot(bool oneShot) : + m_oneShot(oneShot) + {} + }; + + // --------------------------------------------- + class SDRBASE_API MsgScopeVisNGMemoryTrace : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgScopeVisNGMemoryTrace* create( + uint32_t memoryIndex) + { + return new MsgScopeVisNGMemoryTrace(memoryIndex); + } + + uint32_t getMemoryIndex() const { return m_memoryIndex; } + + private: + uint32_t m_memoryIndex; + + MsgScopeVisNGMemoryTrace(uint32_t memoryIndex) : + m_memoryIndex(memoryIndex) + {} + }; + + ScopeVis(); + virtual ~ScopeVis(); + + void setGLScope(GLScopeInterface* glScope); + void setSpectrumVis(SpectrumVis *spectrumVis) { m_spectrumVis = spectrumVis; } + void setSSBSpectrum(bool ssbSpectrum) { m_ssbSpectrum = ssbSpectrum; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + + void setLiveRate(int sampleRate); + void setNbStreams(uint32_t nbStreams); + void configure( + uint32_t traceSize, + uint32_t timeBase, + uint32_t timeOfsProMill, + uint32_t triggerPre, + bool freeRun + ); + void configure( + GLScopeSettings::DisplayMode displayMode, + uint32_t traceIntensity, + uint32_t gridIntensity + ); + void setOneShot(bool oneShot); + void setMemoryIndex(uint32_t memoryIndex); + void setTraceChunkSize(uint32_t chunkSize) { m_traceChunkSize = chunkSize; } + uint32_t getTraceChunkSize() const { return m_traceChunkSize; } + + QByteArray serializeMemory() const + { + SimpleSerializer s(1); + + s.writeU32(1, m_traceSize); + s.writeU32(2, m_preTriggerDelay); + s.writeS32(3, m_sampleRate); + QByteArray buffer = m_traceDiscreteMemory.serialize(); + s.writeBlob(4, buffer); + + return s.final(); + } + + bool deserializeMemory(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + uint32_t traceSize, preTriggerDelay; + int sampleRate; + QByteArray buf; + bool traceDiscreteMemorySuccess; + + d.readU32(1, &traceSize, GLScopeSettings::m_traceChunkDefaultSize); + d.readU32(2, &preTriggerDelay, 0); + d.readS32(3, &sampleRate, 0); + setSampleRate(sampleRate); + setTraceSize(traceSize, true); + setPreTriggerDelay(preTriggerDelay, true); + d.readBlob(4, &buf); + traceDiscreteMemorySuccess = m_traceDiscreteMemory.deserialize(buf); + + if (traceDiscreteMemorySuccess && (m_glScope) && (m_currentTraceMemoryIndex > 0)) { + processMemoryTrace(); + } + + return traceDiscreteMemorySuccess; + } + else + { + return false; + } + } + + void getTriggerData(GLScopeSettings::TriggerData& triggerData, uint32_t triggerIndex) + { + if (triggerIndex < m_triggerConditions.size()) { + triggerData = m_triggerConditions[triggerIndex]->m_triggerData; + } + } + + void getTraceData(GLScopeSettings::TraceData& traceData, uint32_t traceIndex) + { + if (traceIndex < m_traces.m_tracesData.size()) { + traceData = m_traces.m_tracesData[traceIndex]; + } + } + + const GLScopeSettings::TriggerData& getTriggerData(uint32_t triggerIndex) const { return m_triggerConditions[triggerIndex]->m_triggerData; } + const std::vector& getTracesData() const { return m_traces.m_tracesData; } + uint32_t getNbTriggers() const { return m_triggerConditions.size(); } + uint32_t getNbTraces() const { return m_traces.size(); } + + void feed(const std::vector& vbegin, int nbSamples); + void feed(const std::vector& vbegin, int nbSamples); + //virtual void start(); + //virtual void stop(); + bool handleMessage(const Message& message); + int getTriggerLocation() const { return m_triggerLocation; } + bool getFreeRun() const { return m_freeRun; } + +private: + /** + * Trigger stuff + */ + enum TriggerState + { + TriggerUntriggered, //!< Trigger is not kicked off yet (or trigger list is empty) + TriggerTriggered, //!< Trigger has been kicked off + TriggerDelay, //!< Trigger conditions have been kicked off but it is waiting for delay before final kick off + }; + + struct TriggerCondition + { + public: + Projector m_projector; + GLScopeSettings::TriggerData m_triggerData; //!< Trigger data + bool m_prevCondition; //!< Condition (above threshold) at previous sample + uint32_t m_triggerDelayCount; //!< Counter of samples for delay + uint32_t m_triggerCounter; //!< Counter of trigger occurrences + uint32_t m_trues; //!< Count of true conditions for holdoff processing + uint32_t m_falses; //!< Count of false conditions for holdoff processing + + + TriggerCondition(const GLScopeSettings::TriggerData& triggerData) : + m_projector(Projector::ProjectionReal), + m_triggerData(triggerData), + m_prevCondition(false), + m_triggerDelayCount(0), + m_triggerCounter(0), + m_trues(0), + m_falses(0) + { + } + + ~TriggerCondition() + { + } + + void initProjector() + { + m_projector.settProjectionType(m_triggerData.m_projectionType); + } + + void releaseProjector() + { + } + + void setData(const GLScopeSettings::TriggerData& triggerData) + { + m_triggerData = triggerData; + + if (m_projector.getProjectionType() != m_triggerData.m_projectionType) + { + m_projector.settProjectionType(m_triggerData.m_projectionType); + } + + m_prevCondition = false; + m_triggerDelayCount = 0; + m_triggerCounter = 0; + m_trues = 0; + m_falses = 0; + } + + void operator=(const TriggerCondition& other) + { + setData(other.m_triggerData); + } + }; + + /** + * Complex trace stuff + */ + typedef DoubleBufferSimple TraceBuffer; + + struct TraceBackBuffer + { + TraceBuffer m_traceBuffer; + + TraceBackBuffer() { + m_endPoint = m_traceBuffer.getCurrent(); + } + + void resize(uint32_t size) { + m_traceBuffer.resize(size); + } + + void reset() { + m_traceBuffer.reset(); + } + + void write(const ComplexVector::const_iterator begin, int nbSamples) { + m_traceBuffer.write(begin, nbSamples); + } + + unsigned int absoluteFill() const { + return m_traceBuffer.absoluteFill(); + } + + void current(ComplexVector::iterator& it) { + m_traceBuffer.getCurrent(it); + } + + QByteArray serialize() const + { + SimpleSerializer s(1); + + QByteArray buffer = m_traceBuffer.serialize(); + unsigned int endDelta = m_endPoint - m_traceBuffer.begin(); + s.writeU32(1, endDelta); + s.writeBlob(2, buffer); + + return s.final(); + } + + bool deserialize(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + unsigned int tmpUInt; + QByteArray buf; + + d.readU32(1, &tmpUInt, 0); + d.readBlob(2, &buf); + m_traceBuffer.deserialize(buf); + m_endPoint = m_traceBuffer.begin() + tmpUInt; + + return true; + } + else + { + return false; + } + } + + void setEndPoint(const ComplexVector::const_iterator& endPoint) { + m_endPoint = endPoint; + } + + ComplexVector::const_iterator getEndPoint() { + return m_endPoint; + } + + void getEndPoint(ComplexVector::const_iterator& it) { + it = m_endPoint; + } + + private: + ComplexVector::const_iterator m_endPoint; + }; + + typedef std::vector TraceBackBufferStream; + + struct ConvertBuffers + { + ConvertBuffers(uint32_t nbStreams = 1) : + m_convertBuffers(nbStreams) + {} + + void setNbStreams(uint32_t nbStreams) + { + m_convertBuffers.resize(nbStreams); + resize(m_size); + } + + void resize(unsigned int size) + { + for (unsigned int s = 0; s < m_convertBuffers.size(); s++) { + m_convertBuffers[s].resize(size); + } + + m_size = size; + } + + unsigned int size() const { + return m_size; + } + + std::vector& getBuffers() { + return m_convertBuffers; + } + + private: + unsigned int m_size; + std::vector m_convertBuffers; + }; + struct TraceBackDiscreteMemory + { + /** + * Give memory size in number of traces + */ + TraceBackDiscreteMemory(uint32_t size, uint32_t nbStreams = 1) : + m_traceBackBuffersStreams(nbStreams), + m_memSize(size), + m_currentMemIndex(0), + m_maxMemIndex(0), + m_traceSize(0) + { + for (unsigned int s = 0; s < m_traceBackBuffersStreams.size(); s++) { + m_traceBackBuffersStreams[s].resize(m_memSize); + } + } + + void setNbStreams(uint32_t nbStreams) + { + m_traceBackBuffersStreams.resize(nbStreams); + + for (unsigned int s = 0; s < m_traceBackBuffersStreams.size(); s++) { + m_traceBackBuffersStreams[s].resize(m_memSize); + } + + resize(m_traceSize); + } + + /** + * Resize all trace buffers in memory + */ + void resize(uint32_t size) + { + m_traceSize = size; + + for (unsigned int s = 0; s < m_traceBackBuffersStreams.size(); s++) + { + for (std::vector::iterator it = m_traceBackBuffersStreams[s].begin(); it != m_traceBackBuffersStreams[s].end(); ++it) { + it->resize(2*m_traceSize); // was multiplied by 4 + } + } + } + + /** + * Move index forward by one position and return reference to the trace at this position + * Copy a trace length of samples into the new memory slot + * samplesToReport are the number of samples to report on the next trace + */ + void store(int samplesToReport) + { + uint32_t nextMemIndex = m_currentMemIndex < (m_memSize-1) ? m_currentMemIndex+1 : 0; + + for (unsigned int s = 0; s < m_traceBackBuffersStreams.size(); s++) + { + m_traceBackBuffersStreams[s][nextMemIndex].reset(); + m_traceBackBuffersStreams[s][nextMemIndex].write( + m_traceBackBuffersStreams[s][m_currentMemIndex].getEndPoint() - samplesToReport, + samplesToReport + ); + } + + m_currentMemIndex = nextMemIndex; + + if (m_currentMemIndex > m_maxMemIndex) { + m_maxMemIndex = m_currentMemIndex; + } + } + + /** + * Return current memory index + */ + uint32_t currentIndex() const { return m_currentMemIndex; } + + /** + * Return max memory index processed + */ + uint32_t maxIndex() const { return m_maxMemIndex; } + + /** + * Serializer + */ + QByteArray serialize() const + { + SimpleSerializer s(1); + + s.writeU32(1, m_traceBackBuffersStreams.size()); + s.writeU32(2, m_memSize); + s.writeU32(3, m_currentMemIndex); + s.writeU32(4, m_traceSize); + + for (unsigned int is = 0; is < m_traceBackBuffersStreams.size(); is++) + { + SimpleSerializer ss(1); + + for (unsigned int i = 0; i < m_memSize; i++) + { + QByteArray buffer = m_traceBackBuffersStreams[is][i].serialize(); + ss.writeBlob(i, buffer); + } + + s.writeBlob(5+is, ss.final()); + } + + return s.final(); + } + + /** + * Deserializer + */ + bool deserialize(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + unsigned int nbStreams; + d.readU32(1, &nbStreams, 0); + d.readU32(2, &m_memSize, 0); + d.readU32(3, &m_currentMemIndex, 0); + uint32_t traceSize; + d.readU32(4, &traceSize, 0); + + for (unsigned int is = 0; is < nbStreams; is++) + { + if (is >= m_traceBackBuffersStreams.size()) { + break; + } + + m_traceBackBuffersStreams[is].resize(m_memSize); + + if (traceSize != m_traceSize) { + resize(traceSize); + } + + QByteArray streamData; + d.readBlob(5+is, &streamData); + SimpleDeserializer ds(streamData); + + for (unsigned int i = 0; i < m_memSize; i++) + { + QByteArray buffer; + ds.readBlob(i, &buffer); + m_traceBackBuffersStreams[is][i].deserialize(buffer); + } + } + + return true; + } + else + { + return false; + } + } + + /** + * Get current point at current memory position (first stream) + */ + void getCurrent(ComplexVector::iterator& it) { + current().current(it); + } + + /** + * Get current points at current memory position + */ + void getCurrent(std::vector& vit) + { + vit.clear(); + + for (unsigned int is = 0; is < m_traceBackBuffersStreams.size(); is++) + { + ComplexVector::iterator it; + current(is).current(it); + vit.push_back(it); + } + } + + /** + * Set end point at current memory position (first stream) + */ + void setCurrentEndPoint(const ComplexVector::iterator& it) { + current().setEndPoint(it); + } + + /** + * Set end points at current memory position + */ + void setCurrentEndPoint(const std::vector& vit) + { + for (unsigned int is = 0; is < vit.size(); is++) + { + if (is >= m_traceBackBuffersStreams.size()) { + break; + } + + current(is).setEndPoint(vit[is]); + } + } + + /** + * Get end point at given memory position (first stream) + */ + void getEndPointAt(int index, ComplexVector::const_iterator& mend) { + at(index).getEndPoint(mend); + } + + /** + * Get end points at given memory position + */ + void getEndPointAt(int index, std::vector& vend) + { + vend.clear(); + + for (unsigned int is = 0; is < m_traceBackBuffersStreams.size(); is++) + { + ComplexVector::const_iterator mend; + at(index, is).getEndPoint(mend); + vend.push_back(mend); + } + } + + /** + * Write trace at current memory position (first stream) + */ + void writeCurrent(const ComplexVector::const_iterator& begin, int length) { + current().write(begin, length); + } + + /** + * Write traces at current memory position + */ + void writeCurrent(const std::vector& vbegin, int length) + { + for (unsigned int i = 0; i < vbegin.size(); i++) { + current(i).write(vbegin[i], length); + } + } + + /** + * Move buffer iterator by a certain amount (first stream) + */ + static void moveIt(const ComplexVector::const_iterator& x, ComplexVector::const_iterator& y, int amount) { + y = x + amount; + } + + /** + * Move buffers iterators by a certain amount + */ + static void moveIt(const std::vector& vx, std::vector& vy, int amount) + { + for (unsigned int i = 0; i < vx.size(); i++) + { + if (i >= vy.size()) { + break; + } + + vy[i] = vx[i] + amount; + } + } + + private: + std::vector m_traceBackBuffersStreams; + uint32_t m_memSize; + uint32_t m_currentMemIndex; + uint32_t m_maxMemIndex; + uint32_t m_traceSize; + + TraceBackBuffer& current(uint32_t streamIndex = 0) { //!< Return trace at current memory position + return m_traceBackBuffersStreams[streamIndex][m_currentMemIndex]; + } + + TraceBackBuffer& at(int index, uint32_t streamIndex = 0) { //!< Return trace at given memory position + return m_traceBackBuffersStreams[streamIndex][index]; + } + }; + + /** + * Displayable trace stuff + */ + struct TraceControl + { + Projector m_projector; //!< Projector transform from complex trace to real (displayable) trace + uint32_t m_traceCount[2]; //!< Count of samples processed (double buffered) + double m_maxPow; //!< Maximum power over the current trace for MagDB overlay display + double m_sumPow; //!< Cumulative power over the current trace for MagDB overlay display + int m_nbPow; //!< Number of power samples over the current trace for MagDB overlay display + + TraceControl() : m_projector(Projector::ProjectionReal) + { + reset(); + } + + ~TraceControl() + { + } + + void initProjector(Projector::ProjectionType projectionType) + { + m_projector.settProjectionType(projectionType); + } + + void releaseProjector() + { + } + + void reset() + { + m_traceCount[0] = 0; + m_traceCount[1] = 0; + m_maxPow = 0.0f; + m_sumPow = 0.0f; + m_nbPow = 0; + } + }; + + struct Traces + { + std::vector m_tracesControl; //!< Corresponding traces control data + std::vector m_tracesData; //!< Corresponding traces data + std::vector m_traces[2]; //!< Double buffer of traces processed by glScope + std::vector m_projectionTypes; + int m_traceSize; //!< Current size of a trace in buffer + int m_maxTraceSize; //!< Maximum Size of a trace in buffer + bool evenOddIndex; //!< Even (true) or odd (false) index + + Traces() : + m_traceSize(0), + m_maxTraceSize(0), + evenOddIndex(true), + m_x0(0), + m_x1(0) + { + } + + ~Traces() + { + for (std::vector::iterator it = m_tracesControl.begin(); it != m_tracesControl.end(); ++it) { + delete *it; + } + + if (m_x0) { + delete[] m_x0; + } + + if (m_x1) { + delete[] m_x1; + } + + m_maxTraceSize = 0; + } + + bool isVerticalDisplayChange(const GLScopeSettings::TraceData& traceData, uint32_t traceIndex) + { + return (m_tracesData[traceIndex].m_projectionType != traceData.m_projectionType) + || (m_tracesData[traceIndex].m_amp != traceData.m_amp) + || (m_tracesData[traceIndex].m_ofs != traceData.m_ofs + || (m_tracesData[traceIndex].m_traceColor != traceData.m_traceColor)); + } + + void addTrace(const GLScopeSettings::TraceData& traceData, int traceSize) + { + if (m_traces[0].size() < GLScopeSettings::m_maxNbTraces) + { + qDebug("ScopeVis::Traces::addTrace"); + m_traces[0].push_back(nullptr); + m_traces[1].push_back(nullptr); + m_tracesData.push_back(traceData); + m_projectionTypes.push_back(traceData.m_projectionType); + m_tracesControl.push_back(new TraceControl()); + TraceControl *traceControl = m_tracesControl.back(); + traceControl->initProjector(traceData.m_projectionType); + + resize(traceSize); + } + } + + void changeTrace(const GLScopeSettings::TraceData& traceData, uint32_t traceIndex) + { + if (traceIndex < m_tracesControl.size()) + { + TraceControl *traceControl = m_tracesControl[traceIndex]; + traceControl->releaseProjector(); + traceControl->initProjector(traceData.m_projectionType); + m_tracesData[traceIndex] = traceData; + m_projectionTypes[traceIndex] = traceData.m_projectionType; + } + } + + void removeTrace(uint32_t traceIndex) + { + if (traceIndex < m_tracesControl.size()) + { + qDebug("ScopeVis::Traces::removeTrace"); + m_traces[0].erase(m_traces[0].begin() + traceIndex); + m_traces[1].erase(m_traces[1].begin() + traceIndex); + m_projectionTypes.erase(m_projectionTypes.begin() + traceIndex); + TraceControl *traceControl = m_tracesControl[traceIndex]; + traceControl->releaseProjector(); + m_tracesControl.erase(m_tracesControl.begin() + traceIndex); + m_tracesData.erase(m_tracesData.begin() + traceIndex); + delete traceControl; + + resize(m_traceSize); // reallocate pointers + } + } + + void moveTrace(uint32_t traceIndex, bool upElseDown) + { + if ((!upElseDown) && (traceIndex == 0)) { + return; + } + + int nextControlIndex = (traceIndex + (upElseDown ? 1 : -1)) % (m_tracesControl.size()); + int nextDataIndex = (traceIndex + (upElseDown ? 1 : -1)) % (m_tracesData.size()); // should be the same + int nextProjectionTypeIndex = (traceIndex + (upElseDown ? 1 : -1)) % (m_projectionTypes.size()); // should be the same + + Projector::ProjectionType nextType = m_projectionTypes[traceIndex]; + m_projectionTypes[nextProjectionTypeIndex] = m_projectionTypes[traceIndex]; + m_projectionTypes[traceIndex] = nextType; + + TraceControl *traceControl = m_tracesControl[traceIndex]; + TraceControl *nextTraceControl = m_tracesControl[nextControlIndex]; + + traceControl->releaseProjector(); + nextTraceControl->releaseProjector(); + + m_tracesControl[nextControlIndex] = traceControl; + m_tracesControl[traceIndex] = nextTraceControl; + + GLScopeSettings::TraceData nextData = m_tracesData[nextDataIndex]; + m_tracesData[nextDataIndex] = m_tracesData[traceIndex]; + m_tracesData[traceIndex] = nextData; + + traceControl = m_tracesControl[traceIndex]; + nextTraceControl = m_tracesControl[nextControlIndex]; + + traceControl->initProjector(m_tracesData[traceIndex].m_projectionType); + nextTraceControl->initProjector(m_tracesData[nextDataIndex].m_projectionType); + } + + void resize(int traceSize) + { + m_traceSize = traceSize; + + if (m_traceSize > m_maxTraceSize) + { + delete[] m_x0; + delete[] m_x1; + m_x0 = new float[2*m_traceSize*GLScopeSettings::m_maxNbTraces]; + m_x1 = new float[2*m_traceSize*GLScopeSettings::m_maxNbTraces]; + + m_maxTraceSize = m_traceSize; + } + + std::fill_n(m_x0, 2*m_traceSize*m_traces[0].size(), 0.0f); + std::fill_n(m_x1, 2*m_traceSize*m_traces[0].size(), 0.0f); + + for (unsigned int i = 0; i < m_traces[0].size(); i++) + { + (m_traces[0])[i] = &m_x0[2*m_traceSize*i]; + (m_traces[1])[i] = &m_x1[2*m_traceSize*i]; + } + } + + uint32_t currentBufferIndex() const { return evenOddIndex? 0 : 1; } + uint32_t size() const { return m_tracesControl.size(); } + + void switchBuffer() + { + evenOddIndex = !evenOddIndex; + + for (std::vector::iterator it = m_tracesControl.begin(); it != m_tracesControl.end(); ++it) { + (*it)->m_traceCount[currentBufferIndex()] = 0; + } + } + + void resetControls() + { + for (auto traceControl : m_tracesControl) { + traceControl->reset(); + } + } + + private: + float *m_x0; + float *m_x1; + }; + + class TriggerComparator + { + public: + TriggerComparator() : m_level(0), m_reset(true) { + computeLevels(); + } + + bool triggered(const Complex& s, TriggerCondition& triggerCondition) + { + if (triggerCondition.m_triggerData.m_triggerLevel != m_level) + { + m_level = triggerCondition.m_triggerData.m_triggerLevel; + computeLevels(); + } + + bool condition, trigger; + + if (triggerCondition.m_projector.getProjectionType() == Projector::ProjectionMagDB) { + condition = triggerCondition.m_projector.run(s) > m_levelPowerDB; + } else if (triggerCondition.m_projector.getProjectionType() == Projector::ProjectionMagLin) { + condition = triggerCondition.m_projector.run(s) > m_levelPowerLin; + } else if (triggerCondition.m_projector.getProjectionType() == Projector::ProjectionMagSq) { + condition = triggerCondition.m_projector.run(s) > m_levelPowerLin; + } else { + condition = triggerCondition.m_projector.run(s) > m_level; + } + + if (condition) + { + if (triggerCondition.m_trues < triggerCondition.m_triggerData.m_triggerHoldoff) { + condition = false; + triggerCondition.m_trues++; + } + else + { + triggerCondition.m_falses = 0; + } + } + else + { + if (triggerCondition.m_falses < triggerCondition.m_triggerData.m_triggerHoldoff) { + condition = true; + triggerCondition.m_falses++; + } + else + { + triggerCondition.m_trues = 0; + } + } + + if (m_reset) + { + triggerCondition.m_prevCondition = condition; + m_reset = false; + return false; + } + + if (triggerCondition.m_triggerData.m_triggerBothEdges) { + trigger = triggerCondition.m_prevCondition ? !condition : condition; // This is a XOR between bools + } else if (triggerCondition.m_triggerData.m_triggerPositiveEdge) { + trigger = !triggerCondition.m_prevCondition && condition; + } else { + trigger = triggerCondition.m_prevCondition && !condition; + } + +// if (trigger) { +// qDebug("ScopeVis::triggered: %s/%s %f/%f", +// triggerCondition.m_prevCondition ? "T" : "F", +// condition ? "T" : "F", +// triggerCondition.m_projector->run(s), +// triggerCondition.m_triggerData.m_triggerLevel); +// } + + triggerCondition.m_prevCondition = condition; + return trigger; + } + + void reset() { + m_reset = true; + } + + private: + void computeLevels() + { + m_levelPowerLin = m_level + 1.0f; + m_levelPowerDB = (100.0f * (m_level - 1.0f)); + } + + Real m_level; + Real m_levelPowerDB; + Real m_levelPowerLin; + bool m_reset; + }; + + GLScopeInterface* m_glScope; + SpectrumVis *m_spectrumVis; + bool m_ssbSpectrum; + GLScopeSettings m_settings; + MessageQueue m_inputMessageQueue; + uint32_t m_preTriggerDelay; //!< Pre-trigger delay in number of samples + uint32_t m_livePreTriggerDelay; //!< Pre-trigger delay in number of samples in live mode + std::vector m_triggerConditions; //!< Chain of triggers + uint32_t m_currentTriggerIndex; //!< Index of current index in the chain + uint32_t m_focusedTriggerIndex; //!< Index of the trigger that has focus + TriggerState m_triggerState; //!< Current trigger state + Traces m_traces; //!< Displayable traces + int m_focusedTraceIndex; //!< Index of the trace that has focus + uint32_t m_nbStreams; + uint32_t m_traceChunkSize; //!< Trace length unit size in number of samples + uint32_t m_traceSize; //!< Size of traces in number of samples + uint32_t m_liveTraceSize; //!< Size of traces in number of samples in live mode + int m_nbSamples; //!< Number of samples yet to process in one complex trace + uint32_t m_timeBase; //!< Trace display time divisor + uint32_t m_timeOfsProMill; //!< Start trace shift in 1/1000 trace size + bool m_traceStart; //!< Trace is at start point + int m_triggerLocation; //!< Trigger location from end point + int m_sampleRate; //!< Actual sample rate being used + int m_liveSampleRate; //!< Sample rate in live mode + TraceBackDiscreteMemory m_traceDiscreteMemory; //!< Complex trace memory + ConvertBuffers m_convertBuffers; //!< Sample to Complex conversions + bool m_freeRun; //!< True if free running (trigger globally disabled) + int m_maxTraceDelay; //!< Maximum trace delay + TriggerComparator m_triggerComparator; //!< Compares sample level to trigger level + QRecursiveMutex m_mutex; + Real m_projectorCache[(int) Projector::nbProjectionTypes]; + bool m_triggerOneShot; //!< True when one shot mode is active + bool m_triggerWaitForReset; //!< In one shot mode suspended until reset by UI + uint32_t m_currentTraceMemoryIndex; //!< The current index of trace in memory (0: current) + + + void applySettings(const GLScopeSettings& settings, bool force = false); + void addTrace(const GLScopeSettings::TraceData& traceData); + void changeTrace(const GLScopeSettings::TraceData& traceData, uint32_t traceIndex); + void removeTrace(uint32_t traceIndex); + void moveTrace(uint32_t traceIndex, bool upElseDown); + void focusOnTrace(uint32_t traceIndex); + void addTrigger(const GLScopeSettings::TriggerData& triggerData); + void changeTrigger(const GLScopeSettings::TriggerData& triggerData, uint32_t triggerIndex); + void removeTrigger(uint32_t triggerIndex); + void moveTrigger(uint32_t triggerIndex, bool upElseDown); + void focusOnTrigger(uint32_t triggerIndex); + + /** + * Moves on to the next trigger if any or increments trigger count if in repeat mode + * - If not final it returns true + * - If final i.e. signal is actually triggered it returns false + */ + bool nextTrigger(); //!< Returns true if not final + + /** + * Process a sample trace which length is at most the trace length (m_traceSize) + */ + void processTrace(const std::vector& vbegin, int length, int& triggerPointToEnd); + + /** + * process a trace in memory at current trace index in memory + */ + void processMemoryTrace(); + + /** + * Process traces from complex trace memory buffer. + * - if finished it returns the number of unprocessed samples left in the buffer + * - if not finished it returns -1 + */ + int processTraces(const std::vector& vbegin, int length, bool traceBack = false); + + /** + * Get maximum trace delay + */ + void updateMaxTraceDelay(); + + /** + * Initialize trace buffers + */ + void initTraceBuffers(); + + /** + * Calculate trigger levels on display + * - every time a trigger condition focus changes TBD + * - every time the focused trigger condition changes its projection type or level + * - every time a trace data changes: projection type, amp, offset + * - every time a trace data is added or removed + */ + void computeDisplayTriggerLevels(); + + /** + * Update glScope display + * - Live trace: call glScipe update method + * - Trace in memory: call process memory trace + */ + void updateGLScopeDisplay(); + + /** + * Set the actual sample rate + */ + void setSampleRate(int sampleRate); + + /** + * Set the traces size + */ + void setTraceSize(uint32_t traceSize, bool emitSignal = false); + + /** + * Set the pre trigger delay + */ + void setPreTriggerDelay(uint32_t preTriggerDelay, bool emitSignal = false); + +private slots: + void handleInputMessages(); +}; + + + +#endif /* SDRBASE_DSP_SCOPEVISNG_H_ */ diff --git a/android/app/src/main/cpp/dsp/sigmf_forward.h b/android/app/src/main/cpp/dsp/sigmf_forward.h new file mode 100644 index 0000000..cfe2f56 --- /dev/null +++ b/android/app/src/main/cpp/dsp/sigmf_forward.h @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + * Copyright 2019 DeepSig Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSIGMF_SIGMF_FORWARD_H +#define LIBSIGMF_SIGMF_FORWARD_H + +namespace sigmf { + + template + class Global; + + template + class Captures; + + template + class Annotations; + + template + class SigMFVector : public std::vector { + public: + T &create_new() { + T new_element; + this->emplace_back(new_element); + return this->back(); + } + }; + + template + struct SigMF { + GlobalType global; + SigMFVector captures; + SigMFVector annotations; + }; + +} + +// Missing bits... + +namespace core { + class DescrT; +} +namespace sdrangel { + class DescrT; +} + +namespace sigmf { + template class Capture; + template class Annotation; +} + +#endif // LIBSIGMF_SIGMF_FORWARD_H diff --git a/android/app/src/main/cpp/dsp/sigmffilerecord.cpp b/android/app/src/main/cpp/dsp/sigmffilerecord.cpp new file mode 100644 index 0000000..301b6d0 --- /dev/null +++ b/android/app/src/main/cpp/dsp/sigmffilerecord.cpp @@ -0,0 +1,405 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Christoph Berg // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// File recorder in SigMF format single channel for SI plugins // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "libsigmf/sigmf_core_generated.h" +#include "libsigmf/sigmf_sdrangel_generated.h" +#include "libsigmf/sigmf.h" + +#include "dsp/dspcommands.h" +//#include "util/sha512.h" - SHA512 is skipped because it takes too long + +#include "sigmffilerecord.h" + +SigMFFileRecord::SigMFFileRecord() : + FileRecordInterface(), + m_fileName("test"), + m_sampleRate(0), + m_centerFrequency(0), + m_msShift(0), + m_recordOn(false), + m_recordStart(true), + m_sampleStart(0), + m_sampleCount(0), + m_initialMsCount(0), + m_initialBytesCount(0) +{ + qDebug("SigMFFileRecord::SigMFFileRecord: test"); + setObjectName("SigMFFileSink"); + m_metaRecord = new sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >(); + // sizeof(FixReal) is either 2 or 4 thus log2 sample size is either 4 (size 16) or 5 (size 32) + m_log2RecordSampleSize = (sizeof(FixReal) / 2) + 3; +} + +SigMFFileRecord::SigMFFileRecord(const QString& fileName, const QString& hardwareId) : + FileRecordInterface(), + m_hardwareId(hardwareId), + m_fileName(fileName), + m_sampleRate(0), + m_centerFrequency(0), + m_recordOn(false), + m_recordStart(true), + m_sampleStart(0), + m_sampleCount(0), + m_initialMsCount(0), + m_initialBytesCount(0) +{ + qDebug("SigMFFileRecord::SigMFFileRecord: %s", qPrintable(fileName)); + setObjectName("SigMFFileSink"); + m_metaRecord = new sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >(); + // sizeof(FixReal) is either 2 or 4 thus log2 sample size is either 4 (size 16) or 5 (size 32) + m_log2RecordSampleSize = (sizeof(FixReal) / 2) + 3; +} + +SigMFFileRecord::~SigMFFileRecord() +{ + qDebug("SigMFFileRecord::~SigMFFileRecord"); + + stopRecording(); + + if (m_metaFile.isOpen()) { + m_metaFile.close(); + } + + if (m_sampleFile.isOpen()) { + m_sampleFile.close(); + } + + delete m_metaRecord; +} + +void SigMFFileRecord::setFileName(const QString& fileName) +{ + if (!m_recordOn) + { + qDebug("SigMFFileRecord::setFileName: %s", qPrintable(fileName)); + + if (m_metaFile.isOpen()) { + m_metaFile.close(); + } + + if (m_sampleFile.isOpen()) { + m_sampleFile.close(); + } + + m_fileName = fileName; + QString metaFileName = m_fileName + ".sigmf-meta"; + m_metaFile.setFileName(metaFileName); + QString sampleFileName = m_fileName + ".sigmf-data"; + m_sampleFile.setFileName(sampleFileName); + + if (QFile::exists(metaFileName) && QFile::exists(sampleFileName)) + { + QFile metaStream(metaFileName); + metaStream.open(QIODevice::ReadOnly); + try + { + from_json(json::parse(metaStream.readAll().toStdString()), *m_metaRecord); + metaStream.close(); + std::string sdrAngelVersion = m_metaRecord->global.access().version; + + if (sdrAngelVersion.size() != 0) + { + qDebug("SigMFFileRecord::setFileName: appending mode"); + m_initialMsCount = 0; + + for (auto capture : m_metaRecord->captures) + { + uint64_t length = capture.get().length; + int32_t sampleRate = capture.get().sample_rate; + m_initialMsCount += (length * 1000) / sampleRate; + } + + m_sampleFile.setFileName(m_fileName + ".sigmf-data"); + if (!m_sampleFile.open(QIODevice::WriteOnly | QIODevice::Append)) + { + qWarning() << "SigMFFileRecord::setFileName: failed to open file: " << m_sampleFile.fileName(); + } + m_initialBytesCount = (uint64_t) m_sampleFile.size(); + m_sampleStart = m_initialBytesCount / ((1<captures.size(); +} + +bool SigMFFileRecord::startRecording() +{ + bool success = true; + + if (m_recordStart) + { + qDebug("SigMFFileRecord::startRecording: new record %s", qPrintable(m_fileName)); + clearMeta(); + m_sampleStart = 0; + if (!m_sampleFile.open(QIODevice::WriteOnly)) + { + qWarning() << "SigMFFileRecord::startRecording: failed to open file: " << m_sampleFile.fileName(); + success = false; + } + if (!m_metaFile.open(QIODevice::WriteOnly | QIODevice::Append)) + { + qWarning() << "SigMFFileRecord::startRecording: failed to open file: " << m_metaFile.fileName(); + success = false; + } + makeHeader(); + m_recordStart = false; + } + else + { + qDebug("SigMFFileRecord::startRecording: start new capture"); + } + + m_captureStartDT = QDateTime::currentDateTimeUtc().addMSecs(m_msShift); + m_recordOn = true; + m_sampleCount = 0; + return success; +} + +bool SigMFFileRecord::stopRecording() +{ + if (m_recordOn) + { + qDebug("SigMFFileRecord::stopRecording: file previous capture"); + makeCapture(); + m_recordOn = false; + if (m_sampleFile.error()) + { + qWarning() << "SigMFFileRecord::stopRecording: an error occurred while writing to " << m_sampleFile.fileName(); + return false; + } + if (m_metaFile.error()) + { + qWarning() << "SigMFFileRecord::stopRecording: an error occurred while writing to " << m_metaFile.fileName(); + return false; + } + } + return true; +} + +void SigMFFileRecord::makeHeader() +{ + m_metaRecord->global.access().author = "SDRangel"; + m_metaRecord->global.access().description = "SDRangel SigMF I/Q recording file"; + m_metaRecord->global.access().sample_rate = m_sampleRate; + m_metaRecord->global.access().hw = m_hardwareId.toStdString(); + m_metaRecord->global.access().recorder = QString(QCoreApplication::applicationName()).toStdString(); + m_metaRecord->global.access().version = "0.0.2"; + m_metaRecord->global.access().version = QString(QCoreApplication::applicationVersion()).toStdString(); + m_metaRecord->global.access().qt_version = QT_VERSION_STR; + m_metaRecord->global.access().rx_bits = SDR_RX_SAMP_SZ; + m_metaRecord->global.access().arch = QString(QSysInfo::currentCpuArchitecture()).toStdString(); + m_metaRecord->global.access().os = QString(QSysInfo::prettyProductName()).toStdString(); + QString endianSuffix = QSysInfo::ByteOrder == QSysInfo::LittleEndian ? "le" : "be"; + int size = 1<global.access().datatype = QString("ci%1_%2").arg(size).arg(endianSuffix).toStdString(); +} + +void SigMFFileRecord::makeCapture() +{ + if (m_sampleCount) + { + qDebug("SigMFFileRecord::makeCapture: m_sampleStart: %llu m_sampleCount: %llu", m_sampleStart, m_sampleCount); + // Flush samples to disk + m_sampleFile.flush(); + // calculate SHA512 and write it to header + // m_metaRecord->global.access().sha512 = sw::sha512::file(m_sampleFileName.toStdString()); // skip takes too long + // Add new capture + auto recording_capture = sigmf::Capture(); + recording_capture.get().frequency = m_centerFrequency; + recording_capture.get().sample_start = m_sampleStart; + recording_capture.get().length = m_sampleCount; + recording_capture.get().datetime = m_captureStartDT.toString("yyyy-MM-ddTHH:mm:ss.zzzZ").toStdString(); + recording_capture.get().sample_rate = m_sampleRate; + recording_capture.get().tsms = m_captureStartDT.toMSecsSinceEpoch(); + m_metaRecord->captures.emplace_back(recording_capture); + m_sampleStart += m_sampleCount; + // Flush meta to disk + m_metaFile.seek(0); + std::string jsonRecord = json(*m_metaRecord).dump(2); + m_metaFile.write(jsonRecord.c_str(), jsonRecord.size()); + m_metaFile.flush(); + m_metaFile.resize(m_metaFile.pos()); + } + else + { + qDebug("SigMFFileRecord::makeCapture: skipped because of no samples"); + } +} + +void SigMFFileRecord::clearMeta() +{ + m_metaRecord->captures.clear(); +} + +void SigMFFileRecord::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) +{ + (void) positiveOnly; + // if no recording is active, send the samples to /dev/null + if(!m_recordOn) + return; + + if (begin < end) // if there is something to put out + { + // m_sampleFile.write(reinterpret_cast(&*(begin)), (end - begin)*sizeof(Sample)); + feedConv(begin, end); + m_sampleCount += end - begin; + } +} + +void SigMFFileRecord::feedConv(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + uint32_t desiredIorQSampleSize = 1<(&*(begin)), (end - begin)*sizeof(Sample)); + } + else + { + uint32_t nsamples = (end - begin); + + // Only the 24 LSBits of the 32 bits samples are significant + if (desiredIorQSampleSize == 32) // can only be 16 bit samples => x8 (16 -> 24) + { + if (nsamples > m_samples32.size()) { + m_samples32.resize(nsamples); + } + std::transform( + begin, + end, + m_samples32.begin(), + [](const Sample& s) -> Sample32 { + return Sample32{s.real()<<8, s.imag()<<8}; + } + ); + m_sampleFile.write(reinterpret_cast(&*(m_samples32.begin())), nsamples*sizeof(Sample32)); + } + else if (desiredIorQSampleSize == 16) // can only be 32 bit samples size => /8 (24 -> 16) + { + if (nsamples > m_samples16.size()) { + m_samples16.resize(nsamples); + } + std::transform( + begin, + end, + m_samples16.begin(), + [](const Sample& s) -> Sample16 { + return Sample16{(qint16)(s.real()>>8), (qint16)(s.imag()>>8)}; + } + ); + m_sampleFile.write(reinterpret_cast(&*(m_samples16.begin())), nsamples*sizeof(Sample16)); + } + else // can only be 8 bit desired sample size + { + // divide by 8 for 16 -> 8 (sizeof(sample) == 4) or 16 for 24 -> 8 (sizeod(Sample) == 8) + // thus division of a I or Q sample is done with >>(2*sizeof(sample)) operation + if (nsamples > m_samples8.size()) { + m_samples8.resize(nsamples); + } + std::transform( + begin, + end, + m_samples8.begin(), + [](const Sample& s) -> Sample8 { + return Sample8{(qint8)(s.real()>>(2*sizeof(Sample))), (qint8)(s.imag()>>(2*sizeof(Sample)))}; + } + ); + m_sampleFile.write(reinterpret_cast(&*(m_samples8.begin())), nsamples*sizeof(Sample8)); + } + } +} + +void SigMFFileRecord::start() +{ +} + +void SigMFFileRecord::stop() +{ + stopRecording(); +} + +bool SigMFFileRecord::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + if (m_recordOn) { + makeCapture(); + m_captureStartDT = QDateTime::currentDateTimeUtc(); + m_sampleCount = 0; + } + + DSPSignalNotification& notif = (DSPSignalNotification&) message; + m_sampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + qDebug() << "SigMFFileRecord::handleMessage: DSPSignalNotification: " + << " m_inputSampleRate: " << m_sampleRate + << " m_centerFrequency: " << m_centerFrequency; + + return true; + } + else + { + return false; + } +} + diff --git a/android/app/src/main/cpp/dsp/sigmffilerecord.h b/android/app/src/main/cpp/dsp/sigmffilerecord.h new file mode 100644 index 0000000..2191445 --- /dev/null +++ b/android/app/src/main/cpp/dsp/sigmffilerecord.h @@ -0,0 +1,103 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2016, 2018-2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2018 beta-tester // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// File recorder in SigMF format single channel for SI plugins // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMF_FILERECORD_H +#define INCLUDE_SIGMF_FILERECORD_H + +#include + +#include +#include + +#include "dsp/sigmf_forward.h" +#include "dsp/filerecordinterface.h" +#include "export.h" + +class Message; + +class SDRBASE_API SigMFFileRecord : public FileRecordInterface { +public: + SigMFFileRecord(); + SigMFFileRecord(const QString& filename, const QString& hardwareId); + virtual ~SigMFFileRecord(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) override; + virtual void start() override; + virtual void stop() override; + virtual bool handleMessage(const Message& message) override; + + virtual void setFileName(const QString& filename) override; + virtual const QString& getCurrentFileName() override { return m_fileName; } + virtual bool startRecording() override; + virtual bool stopRecording() override; + virtual bool isRecording() const override { return m_recordOn; } + + void setHardwareId(const QString& hardwareId) { m_hardwareId = hardwareId; } + void setMsShift(qint64 msShift) override { m_msShift = msShift; } + unsigned int getNbCaptures() const; + uint64_t getInitialMsCount() const { return m_initialMsCount; } + uint64_t getInitialBytesCount() const { return m_initialBytesCount; } + void setLog2RecordSampleSize(uint32_t log2RecordSampleSize) { m_log2RecordSampleSize = log2RecordSampleSize; } + +private: + struct Sample8 { + qint8 m_real; + qint8 m_imag; + }; + + struct Sample16 { + qint16 m_real; + qint16 m_imag; + }; + + struct Sample32 { + qint32 m_real; + qint32 m_imag; + }; + + QString m_hardwareId; + QString m_fileName; + quint32 m_sampleRate; + quint64 m_centerFrequency; + qint64 m_msShift; + bool m_recordOn; + bool m_recordStart; + QDateTime m_captureStartDT; + QFile m_metaFile; + QFile m_sampleFile; + quint64 m_sampleStart; + quint64 m_sampleCount; + quint64 m_initialMsCount; + quint64 m_initialBytesCount; + uint32_t m_log2RecordSampleSize; + sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation > *m_metaRecord; + std::vector m_samples8; + std::vector m_samples16; + std::vector m_samples32; + void makeHeader(); + void makeCapture(); + void clearMeta(); + void feedConv(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); +}; + +#endif // INCLUDE_SIGMF_FILERECORD_H diff --git a/android/app/src/main/cpp/dsp/spectrumcalibrationpoint.cpp b/android/app/src/main/cpp/dsp/spectrumcalibrationpoint.cpp new file mode 100644 index 0000000..dde688d --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrumcalibrationpoint.cpp @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/simpleserializer.h" +#include "spectrumcalibrationpoint.h" + +QByteArray SpectrumCalibrationPoint::serialize() const +{ + SimpleSerializer s(1); + + s.writeS64(1, m_frequency); + s.writeFloat(2, m_powerRelativeReference); + s.writeFloat(3, m_powerCalibratedReference); + + return s.final(); +} + +bool SpectrumCalibrationPoint::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + d.readS64(1, &m_frequency, 0); + d.readFloat(2, &m_powerRelativeReference, 1.0f); + d.readFloat(3, &m_powerCalibratedReference, 1.0f); + + return true; + } + else + { + return false; + } + +} diff --git a/android/app/src/main/cpp/dsp/spectrumcalibrationpoint.h b/android/app/src/main/cpp/dsp/spectrumcalibrationpoint.h new file mode 100644 index 0000000..41e0f32 --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrumcalibrationpoint.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016-2019, 2021-2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SPECTRUMCALIBRATIONPOINT_H +#define INCLUDE_SPECTRUMCALIBRATIONPOINT_H + +#include + +#include "export.h" + +struct SDRBASE_API SpectrumCalibrationPoint +{ + qint64 m_frequency; //!< frequency in Hz + float m_powerRelativeReference; //!< relative power level on 0..1 scale + float m_powerCalibratedReference; //!< calibrated power level on 0..x scale. x limited to 10000 (40dB) in the GUI. + + SpectrumCalibrationPoint() : + m_frequency(0), + m_powerRelativeReference(1.0f), + m_powerCalibratedReference(1.0f) + {} + + SpectrumCalibrationPoint( + quint64 frequency, + float powerRelativeReference, + float powerAbsoluteReference + ) : + m_frequency(frequency), + m_powerRelativeReference(powerRelativeReference), + m_powerCalibratedReference(powerAbsoluteReference) + {} + + SpectrumCalibrationPoint(const SpectrumCalibrationPoint& other) = default; + SpectrumCalibrationPoint& operator=(const SpectrumCalibrationPoint&) = default; + + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + +#endif // INCLUDE_SPECTRUMCALIBRATIONPOINTs_H diff --git a/android/app/src/main/cpp/dsp/spectrummarkers.cpp b/android/app/src/main/cpp/dsp/spectrummarkers.cpp new file mode 100644 index 0000000..dbdd65b --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrummarkers.cpp @@ -0,0 +1,166 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/simpleserializer.h" +#include "spectrummarkers.h" + +QByteArray SpectrumHistogramMarker::serialize() const +{ + SimpleSerializer s(1); + + s.writeFloat(1, m_frequency); + s.writeFloat(2, m_power); + s.writeS32(3, (int) m_markerType); + int r, g, b; + m_markerColor.getRgb(&r, &g, &b); + s.writeS32(4, r); + s.writeS32(5, g); + s.writeS32(6, b); + s.writeBool(7, m_show); + + return s.final(); +} + +bool SpectrumHistogramMarker::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + int tmp; + + d.readFloat(1, &m_frequency, 0); + d.readFloat(2, &m_power, 0); + d.readS32(3, &tmp, 0); + m_markerType = (SpectrumMarkerType) tmp; + int r, g, b; + d.readS32(4, &r, 255); + m_markerColor.setRed(r); + d.readS32(5, &g, 255); + m_markerColor.setGreen(g); + d.readS32(6, &b, 255); + m_markerColor.setBlue(b); + + d.readBool(7, &m_show); + + return true; + } + else + { + return false; + } +} + +QByteArray SpectrumWaterfallMarker::serialize() const +{ + SimpleSerializer s(1); + + s.writeFloat(1, m_frequency); + s.writeFloat(2, m_time); + int r, g, b; + m_markerColor.getRgb(&r, &g, &b); + s.writeS32(4, r); + s.writeS32(5, g); + s.writeS32(6, b); + s.writeS32(7, (int) m_show); + + return s.final(); +} + +bool SpectrumWaterfallMarker::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + d.readFloat(1, &m_frequency, 0); + d.readFloat(2, &m_time, 0); + int r, g, b; + d.readS32(4, &r, 255); + m_markerColor.setRed(r); + d.readS32(5, &g, 255); + m_markerColor.setGreen(g); + d.readS32(6, &b, 255); + m_markerColor.setBlue(b); + d.readBool(7, &m_show); + + return true; + } + else + { + return false; + } +} + +QByteArray SpectrumAnnotationMarker::serialize() const +{ + SimpleSerializer s(1); + + s.writeS64(1, m_startFrequency); + s.writeU32(2, m_bandwidth); + int r, g, b; + m_markerColor.getRgb(&r, &g, &b); + s.writeS32(4, r); + s.writeS32(5, g); + s.writeS32(6, b); + s.writeBool(7, m_show); + s.writeString(8, m_text); + + return s.final(); +} + +bool SpectrumAnnotationMarker::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + int tmp; + + d.readS64(1, &m_startFrequency, 0); + d.readU32(2, &m_bandwidth, 0); + int r, g, b; + d.readS32(4, &r, 255); + m_markerColor.setRed(r); + d.readS32(5, &g, 255); + m_markerColor.setGreen(g); + d.readS32(6, &b, 255); + m_markerColor.setBlue(b); + d.readS32 (7, &tmp, 1); + m_show = (ShowState) tmp; + d.readString(8, &m_text); + + return true; + } + else + { + return false; + } + +} diff --git a/android/app/src/main/cpp/dsp/spectrummarkers.h b/android/app/src/main/cpp/dsp/spectrummarkers.h new file mode 100644 index 0000000..6c9e529 --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrummarkers.h @@ -0,0 +1,210 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SPECTRUMMARKERS_H +#define INCLUDE_SPECTRUMMARKERS_H + +#include +#include +#include + +#include "export.h" + +struct SDRBASE_API SpectrumHistogramMarker +{ + enum SpectrumMarkerType + { + SpectrumMarkerTypeManual, + SpectrumMarkerTypePower, + SpectrumMarkerTypePowerMax + }; + + QPointF m_point; + float m_frequency; + int m_fftBin; + float m_power; + bool m_holdReset; + float m_powerMax; + SpectrumMarkerType m_markerType; + QColor m_markerColor; + bool m_show; + QString m_frequencyStr; + QString m_powerStr; + QString m_deltaFrequencyStr; + QString m_deltaPowerStr; + static const int m_maxNbOfMarkers = 4; + + SpectrumHistogramMarker() : + m_point(0, 0), + m_frequency(0), + m_fftBin(0), + m_power(0), + m_holdReset(true), + m_powerMax(0), + m_markerType(SpectrumMarkerTypeManual), + m_markerColor("white"), + m_show(true), + m_frequencyStr(), + m_powerStr(), + m_deltaFrequencyStr(), + m_deltaPowerStr() + {} + + SpectrumHistogramMarker( + const QPointF& point, + float frequency, + int fftBin, + float power, + bool holdReset, + float powerMax, + SpectrumMarkerType markerType, + QColor markerColor, + bool show, + const QString& frequencyStr, + const QString& powerStr, + const QString& deltaFrequencyStr, + const QString& deltaPowerStr + ) : + m_point(point), + m_frequency(frequency), + m_fftBin(fftBin), + m_power(power), + m_holdReset(holdReset), + m_powerMax(powerMax), + m_markerType(markerType), + m_markerColor(markerColor), + m_show(show), + m_frequencyStr(frequencyStr), + m_powerStr(powerStr), + m_deltaFrequencyStr(deltaFrequencyStr), + m_deltaPowerStr(deltaPowerStr) + {} + + SpectrumHistogramMarker(const SpectrumHistogramMarker& other) = default; + SpectrumHistogramMarker& operator=(const SpectrumHistogramMarker&) = default; + + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +struct SDRBASE_API SpectrumWaterfallMarker +{ + QPointF m_point; + float m_frequency; + float m_time; + QColor m_markerColor; + bool m_show; + QString m_frequencyStr; + QString m_timeStr; + QString m_deltaFrequencyStr; + QString m_deltaTimeStr; + static const int m_maxNbOfMarkers = 4; + + SpectrumWaterfallMarker() : + m_point(0, 0), + m_frequency(0), + m_time(0), + m_markerColor("white"), + m_show(true), + m_frequencyStr(), + m_timeStr(), + m_deltaFrequencyStr(), + m_deltaTimeStr() + {} + + SpectrumWaterfallMarker( + const QPointF& point, + float frequency, + float time, + QColor markerColor, + bool show, + const QString& frequencyStr, + const QString& timeStr, + const QString& deltaFrequencyStr, + const QString& deltaTimeStr + ) : + m_point(point), + m_frequency(frequency), + m_time(time), + m_markerColor(markerColor), + m_show(show), + m_frequencyStr(frequencyStr), + m_timeStr(timeStr), + m_deltaFrequencyStr(deltaFrequencyStr), + m_deltaTimeStr(deltaTimeStr) + {} + + SpectrumWaterfallMarker(const SpectrumWaterfallMarker& other) = default; + SpectrumWaterfallMarker& operator=(const SpectrumWaterfallMarker&) = default; + + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +struct SDRBASE_API SpectrumAnnotationMarker +{ + enum ShowState + { + Hidden, + ShowTop, + ShowFull, + ShowText + }; + + qint64 m_startFrequency; + uint32_t m_bandwidth; + QColor m_markerColor; + ShowState m_show; + QString m_text; + float m_startPos; + float m_stopPos; + + SpectrumAnnotationMarker() : + m_startFrequency(0), + m_bandwidth(0), + m_markerColor("white"), + m_show(ShowTop), + m_text("Text"), + m_startPos(0.0f), + m_stopPos(1.0f) + {} + + SpectrumAnnotationMarker( + qint64 startFrequency, + uint32_t bandwidth, + QColor markerColor, + ShowState show, + const QString& text + ) : + m_startFrequency(startFrequency), + m_bandwidth(bandwidth), + m_markerColor(markerColor), + m_show(show), + m_text(text), + m_startPos(0.0f), + m_stopPos(1.0f) + {} + + SpectrumAnnotationMarker(const SpectrumAnnotationMarker& other) = default; + SpectrumAnnotationMarker& operator=(const SpectrumAnnotationMarker&) = default; + + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif // INCLUDE_SPECTRUMMARKERS_H diff --git a/android/app/src/main/cpp/dsp/spectrumsettings.cpp b/android/app/src/main/cpp/dsp/spectrumsettings.cpp new file mode 100644 index 0000000..cce7fe1 --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrumsettings.cpp @@ -0,0 +1,667 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Vort // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "SWGGLSpectrum.h" + +#include "util/simpleserializer.h" +#include "spectrumsettings.h" + +SpectrumSettings::SpectrumSettings() +{ + resetToDefaults(); +} + +SpectrumSettings::~SpectrumSettings() +{} + + +void SpectrumSettings::resetToDefaults() +{ + m_fftSize = 1024; + m_fftOverlap = 0; + m_fftWindow = FFTWindow::Hanning; + m_refLevel = 0; + m_powerRange = 100; + m_fpsPeriodMs = 50; + m_decay = 1; + m_decayDivisor = 1; + m_histogramStroke = 30; + m_displayGridIntensity = 5; + m_displayTraceIntensity = 50; + m_waterfallShare = 0.5; + m_displayCurrent = true; + m_displayWaterfall = true; + m_invertedWaterfall = true; + m_display3DSpectrogram = false; + m_displayMaxHold = false; + m_displayHistogram = false; + m_displayGrid = false; + m_truncateFreqScale = false; + m_averagingMode = AvgModeNone; + m_averagingIndex = 0; + m_averagingValue = 1; + m_linear = false; + m_ssb = false; + m_usb = true; + m_wsSpectrum = false; + m_wsSpectrumAddress = "127.0.0.1"; + m_wsSpectrumPort = 8887; + m_markersDisplay = MarkersDisplayNone; + m_useCalibration = false; + m_calibrationInterpMode = CalibInterpLinear; + m_3DSpectrogramStyle = Outline; + m_colorMap = "Angel"; + m_spectrumStyle = Line; + m_measurement = MeasurementNone; + m_measurementCenterFrequencyOffset = 0; + m_measurementBandwidth = 10000; + m_measurementChSpacing = 10000; + m_measurementAdjChBandwidth = 10000; + m_measurementHarmonics = 5; + m_measurementPeaks = 5; + m_measurementHighlight = true; + m_measurementsPosition = PositionBelow; + m_measurementPrecision = 1; + m_findHistogramPeaks = false; +#ifdef ANDROID + m_showAllControls = false; +#else + m_showAllControls = true; +#endif +} + +QByteArray SpectrumSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_fftSize); + s.writeS32(2, m_fftOverlap); + s.writeS32(3, (int) m_fftWindow); + s.writeReal(4, m_refLevel); + s.writeReal(5, m_powerRange); + s.writeBool(6, m_displayWaterfall); + s.writeBool(7, m_invertedWaterfall); + s.writeBool(8, m_displayMaxHold); + s.writeBool(9, m_displayHistogram); + s.writeS32(10, m_decay); + s.writeBool(11, m_displayGrid); + s.writeS32(13, m_displayGridIntensity); + s.writeS32(14, m_decayDivisor); + s.writeS32(15, m_histogramStroke); + s.writeBool(16, m_displayCurrent); + s.writeS32(17, m_displayTraceIntensity); + s.writeReal(18, m_waterfallShare); + s.writeS32(19, (int) m_averagingMode); + s.writeS32(20, (qint32) getAveragingValue(m_averagingIndex, m_averagingMode)); + s.writeBool(21, m_linear); + s.writeString(22, m_wsSpectrumAddress); + s.writeU32(23, m_wsSpectrumPort); + s.writeBool(24, m_ssb); + s.writeBool(25, m_usb); + s.writeS32(26, m_fpsPeriodMs); + s.writeBool(27, m_wsSpectrum); + s.writeS32(28, (int) m_markersDisplay); + s.writeBool(29, m_useCalibration); + s.writeS32(30, (int) m_calibrationInterpMode); + s.writeBool(31, m_display3DSpectrogram); + s.writeS32(32, (int) m_3DSpectrogramStyle); + s.writeString(33, m_colorMap); + s.writeS32(34, (int) m_spectrumStyle); + s.writeS32(35, (int) m_measurement); + s.writeS32(36, m_measurementBandwidth); + s.writeS32(37, m_measurementChSpacing); + s.writeS32(38, m_measurementAdjChBandwidth); + s.writeS32(39, m_measurementHarmonics); + // 41, 42 used below + s.writeBool(42, m_measurementHighlight); + s.writeS32(43, m_measurementPeaks); + s.writeS32(44, (int)m_measurementsPosition); + s.writeS32(45, m_measurementPrecision); + s.writeS32(46, m_measurementCenterFrequencyOffset); + s.writeBool(47, m_findHistogramPeaks); + s.writeBool(48, m_truncateFreqScale); + s.writeBool(49, m_showAllControls); + s.writeS32(100, m_histogramMarkers.size()); + + for (int i = 0; i < m_histogramMarkers.size(); i++) { + s.writeBlob(101+i, m_histogramMarkers[i].serialize()); + } + + s.writeS32(110, m_waterfallMarkers.size()); + + for (int i = 0; i < m_waterfallMarkers.size(); i++) { + s.writeBlob(111+i, m_waterfallMarkers[i].serialize()); + } + + s.writeList(40, m_annoationMarkers); + s.writeList(41, m_calibrationPoints); + + return s.final(); +} + +QDataStream& operator<<(QDataStream& out, const SpectrumAnnotationMarker& marker) +{ + out << marker.m_startFrequency; + out << marker.m_bandwidth; + out << marker.m_markerColor; + out << (int) marker.m_show; + out << marker.m_text; + return out; +} + +QDataStream& operator<<(QDataStream& out, const SpectrumCalibrationPoint& calibrationPoint) +{ + out << calibrationPoint.m_frequency; + out << calibrationPoint.m_powerRelativeReference; + out << calibrationPoint.m_powerCalibratedReference; + return out; +} + +bool SpectrumSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + int tmp; + uint32_t utmp; + QByteArray bytetmp; + + if (d.getVersion() == 1) + { + d.readS32(1, &m_fftSize, 1024); + d.readS32(2, &m_fftOverlap, 0); + d.readS32(3, &tmp, (int) FFTWindow::Hanning); + m_fftWindow = (FFTWindow::Function) tmp; + d.readReal(4, &m_refLevel, 0); + d.readReal(5, &m_powerRange, 100); + d.readBool(6, &m_displayWaterfall, true); + d.readBool(7, &m_invertedWaterfall, true); + d.readBool(8, &m_displayMaxHold, false); + d.readBool(9, &m_displayHistogram, false); + d.readS32(10, &m_decay, 1); + d.readBool(11, &m_displayGrid, false); + d.readS32(13, &m_displayGridIntensity, 5); + d.readS32(14, &m_decayDivisor, 1); + d.readS32(15, &m_histogramStroke, 30); + d.readBool(16, &m_displayCurrent, true); + d.readS32(17, &m_displayTraceIntensity, 50); + d.readReal(18, &m_waterfallShare, 0.66); + d.readS32(19, &tmp, 0); + m_averagingMode = tmp < 0 ? AvgModeNone : tmp > 3 ? AvgModeMax : (AveragingMode) tmp; + d.readS32(20, &tmp, 0); + m_averagingIndex = getAveragingIndex(tmp, m_averagingMode); + m_averagingValue = getAveragingValue(m_averagingIndex, m_averagingMode); + d.readBool(21, &m_linear, false); + d.readString(22, &m_wsSpectrumAddress, "127.0.0.1"); + d.readU32(23, &utmp, 8887); + m_wsSpectrumPort = utmp < 1024 ? 1024 : utmp > 65535 ? 65535 : utmp; + d.readBool(24, &m_ssb, false); + d.readBool(25, &m_usb, true); + d.readS32(26, &tmp, 50); + m_fpsPeriodMs = tmp < 5 ? 5 : tmp > 500 ? 500 : tmp; + d.readBool(27, &m_wsSpectrum, false); + d.readS32(28, &tmp, 0); + m_markersDisplay = (MarkersDisplay) tmp; + d.readBool(29, &m_useCalibration, false); + d.readS32(30, &tmp, 0); + m_calibrationInterpMode = (CalibrationInterpolationMode) tmp; + d.readBool(31, &m_display3DSpectrogram, false); + d.readS32(32, (int*)&m_3DSpectrogramStyle, (int)Outline); + d.readString(33, &m_colorMap, "Angel"); + d.readS32(34, (int*)&m_spectrumStyle, (int)Line); + d.readS32(35, (int*)&m_measurement, (int)MeasurementNone); + d.readS32(36, &m_measurementBandwidth, 10000); + d.readS32(37, &m_measurementChSpacing, 10000); + d.readS32(38, &m_measurementAdjChBandwidth, 10000); + d.readS32(39, &m_measurementHarmonics, 5); + d.readBool(42, &m_measurementHighlight, true); + d.readS32(43, &m_measurementPeaks, 5); + d.readS32(44, (int*)&m_measurementsPosition, (int)PositionBelow); + d.readS32(45, &m_measurementPrecision, 1); + d.readS32(46, &m_measurementCenterFrequencyOffset, 0); + d.readBool(47, &m_findHistogramPeaks, false); + d.readBool(48, &m_truncateFreqScale, false); +#ifdef ANDROID + d.readBool(49, &m_showAllControls, false); +#else + d.readBool(49, &m_showAllControls, true); +#endif + + int histogramMarkersSize; + d.readS32(100, &histogramMarkersSize, 0); + histogramMarkersSize = histogramMarkersSize < 0 ? 0 : + histogramMarkersSize > SpectrumHistogramMarker::m_maxNbOfMarkers ? + SpectrumHistogramMarker::m_maxNbOfMarkers : histogramMarkersSize; + m_histogramMarkers.clear(); + + for (int i = 0; i < histogramMarkersSize; i++) + { + d.readBlob(101+i, &bytetmp); + m_histogramMarkers.push_back(SpectrumHistogramMarker()); + m_histogramMarkers.back().deserialize(bytetmp); + } + + int waterfallMarkersSize; + + d.readS32(110, &waterfallMarkersSize, 0); + waterfallMarkersSize = waterfallMarkersSize < 0 ? 0 : + waterfallMarkersSize > SpectrumWaterfallMarker::m_maxNbOfMarkers ? + SpectrumWaterfallMarker::m_maxNbOfMarkers : waterfallMarkersSize; + m_waterfallMarkers.clear(); + + for (int i = 0; i < waterfallMarkersSize; i++) + { + d.readBlob(111+i, &bytetmp); + m_waterfallMarkers.push_back(SpectrumWaterfallMarker()); + m_waterfallMarkers.back().deserialize(bytetmp); + } + + d.readList(40, &m_annoationMarkers); + d.readList(41, &m_calibrationPoints); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +QDataStream& operator>>(QDataStream& in, SpectrumAnnotationMarker& marker) +{ + int tmp; + in >> marker.m_startFrequency; + in >> marker.m_bandwidth; + in >> marker.m_markerColor; + in >> tmp; + in >> marker.m_text; + marker.m_show = (SpectrumAnnotationMarker::ShowState) tmp; + return in; +} + +QDataStream& operator>>(QDataStream& in, SpectrumCalibrationPoint& calibrationPoint) +{ + in >> calibrationPoint.m_frequency; + in >> calibrationPoint.m_powerRelativeReference; + in >> calibrationPoint.m_powerCalibratedReference; + return in; +} + +void SpectrumSettings::formatTo(SWGSDRangel::SWGObject *swgObject) const +{ + SWGSDRangel::SWGGLSpectrum *swgSpectrum = static_cast(swgObject); + + swgSpectrum->setFftWindow((int) m_fftWindow); + swgSpectrum->setFftSize(m_fftSize); + swgSpectrum->setFftOverlap(m_fftOverlap); + swgSpectrum->setAveragingMode((int) m_averagingMode); + swgSpectrum->setAveragingValue(SpectrumSettings::getAveragingValue(m_averagingIndex, m_averagingMode)); + swgSpectrum->setRefLevel(m_refLevel); + swgSpectrum->setPowerRange(m_powerRange); + swgSpectrum->setFpsPeriodMs(m_fpsPeriodMs); + swgSpectrum->setLinear(m_linear ? 1 : 0); + swgSpectrum->setWsSpectrum(m_wsSpectrum ? 1 : 0); + swgSpectrum->setWsSpectrumPort(m_wsSpectrumPort); + + if (swgSpectrum->getWsSpectrumAddress()) { + *swgSpectrum->getWsSpectrumAddress() = m_wsSpectrumAddress; + } else { + swgSpectrum->setWsSpectrumAddress(new QString(m_wsSpectrumAddress)); + } + + swgSpectrum->setDisplayHistogram(m_displayHistogram ? 1 : 0); + swgSpectrum->setDecay(m_decay); + swgSpectrum->setDecayDivisor(m_decayDivisor); + swgSpectrum->setHistogramStroke(m_histogramStroke); + swgSpectrum->setDisplayMaxHold(m_displayMaxHold ? 1 : 0); + swgSpectrum->setDisplayCurrent(m_displayCurrent ? 1 : 0); + swgSpectrum->setDisplayTraceIntensity(m_displayTraceIntensity); + swgSpectrum->setInvertedWaterfall(m_invertedWaterfall ? 1 : 0); + swgSpectrum->setDisplayWaterfall(m_displayWaterfall ? 1 : 0); + swgSpectrum->setDisplayGrid(m_displayGrid ? 1 : 0); + swgSpectrum->setDisplayGridIntensity(m_displayGridIntensity); + swgSpectrum->setSsb(m_ssb ? 1 : 0); + swgSpectrum->setUsb(m_usb ? 1 : 0); + swgSpectrum->setWaterfallShare(m_waterfallShare); + swgSpectrum->setMarkersDisplay((int) m_markersDisplay); + swgSpectrum->setUseCalibration(m_useCalibration ? 1 : 0); + swgSpectrum->setCalibrationInterpMode((int) m_calibrationInterpMode); + + if (m_histogramMarkers.size() > 0) + { + swgSpectrum->setHistogramMarkers(new QList); + + for (const auto &marker : m_histogramMarkers) + { + swgSpectrum->getHistogramMarkers()->append(new SWGSDRangel::SWGSpectrumHistogramMarker); + swgSpectrum->getHistogramMarkers()->back()->setFrequency(marker.m_frequency); + swgSpectrum->getHistogramMarkers()->back()->setPower(marker.m_power); + swgSpectrum->getHistogramMarkers()->back()->setMarkerType((int) marker.m_markerType); + swgSpectrum->getHistogramMarkers()->back()->setMarkerColor(qColorToInt(marker.m_markerColor)); + swgSpectrum->getHistogramMarkers()->back()->setShow(marker.m_show ? 1 : 0); + } + } + + if (m_waterfallMarkers.size() > 0) + { + swgSpectrum->setWaterfallMarkers(new QList); + + for (const auto &marker : m_waterfallMarkers) + { + swgSpectrum->getWaterfallMarkers()->append(new SWGSDRangel::SWGSpectrumWaterfallMarker); + swgSpectrum->getWaterfallMarkers()->back()->setFrequency(marker.m_frequency); + swgSpectrum->getWaterfallMarkers()->back()->setTime(marker.m_time); + swgSpectrum->getWaterfallMarkers()->back()->setMarkerColor(qColorToInt(marker.m_markerColor)); + swgSpectrum->getWaterfallMarkers()->back()->setShow(marker.m_show ? 1 : 0); + } + } + + if (m_annoationMarkers.size() > 0) + { + swgSpectrum->setAnnotationMarkers(new QList); + + for (const auto &marker : m_annoationMarkers) + { + swgSpectrum->getAnnotationMarkers()->append(new SWGSDRangel::SWGSpectrumAnnotationMarker); + swgSpectrum->getAnnotationMarkers()->back()->setStartFrequency(marker.m_startFrequency); + swgSpectrum->getAnnotationMarkers()->back()->setBandwidth(marker.m_bandwidth); + swgSpectrum->getAnnotationMarkers()->back()->setMarkerColor(qColorToInt(marker.m_markerColor)); + swgSpectrum->getAnnotationMarkers()->back()->setShow((int) marker.m_show); + } + } + + if (m_calibrationPoints.size() > 0) + { + swgSpectrum->setCalibrationPoints(new QList); + + for (const auto &calibrationPoint : m_calibrationPoints) + { + swgSpectrum->getCalibrationPoints()->append(new SWGSDRangel::SWGSpectrumCalibrationPoint); + swgSpectrum->getCalibrationPoints()->back()->setFrequency(calibrationPoint.m_frequency); + swgSpectrum->getCalibrationPoints()->back()->setPowerRelativeReference(calibrationPoint.m_powerRelativeReference); + swgSpectrum->getCalibrationPoints()->back()->setPowerAbsoluteReference(calibrationPoint.m_powerCalibratedReference); + } + } +} + +void SpectrumSettings::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject) +{ + SWGSDRangel::SWGGLSpectrum *swgSpectrum = + static_cast(const_cast(swgObject)); + + if (keys.contains("spectrumConfig.fftWindow")) { + m_fftWindow = (FFTWindow::Function) swgSpectrum->getFftWindow(); + } + if (keys.contains("spectrumConfig.fftSize")) { + m_fftSize = swgSpectrum->getFftSize(); + } + if (keys.contains("spectrumConfig.fftOverlap")) { + m_fftOverlap = swgSpectrum->getFftOverlap(); + } + if (keys.contains("spectrumConfig.averagingMode")) { + m_averagingMode = (SpectrumSettings::AveragingMode) swgSpectrum->getAveragingMode(); + } + if (keys.contains("spectrumConfig.averagingValue")) + { + m_averagingValue = swgSpectrum->getAveragingValue(); + m_averagingIndex = SpectrumSettings::getAveragingIndex(m_averagingValue, m_averagingMode); + } + if (keys.contains("spectrumConfig.refLevel")) { + m_refLevel = swgSpectrum->getRefLevel(); + } + if (keys.contains("spectrumConfig.powerRange")) { + m_powerRange = swgSpectrum->getPowerRange(); + } + if (keys.contains("spectrumConfig.fpsPeriodMs")) { + m_fpsPeriodMs = swgSpectrum->getFpsPeriodMs(); + } + if (keys.contains("spectrumConfig.linear")) { + m_linear = swgSpectrum->getLinear() != 0; + } + if (keys.contains("spectrumConfig.wsSpectrum")) { + m_wsSpectrum = swgSpectrum->getWsSpectrum() != 0; + } + if (keys.contains("spectrumConfig.wsSpectrum")) { + m_wsSpectrum = swgSpectrum->getWsSpectrum() != 0; + } + if (keys.contains("spectrumConfig.wsSpectrumAddress")) { + m_wsSpectrumAddress = *swgSpectrum->getWsSpectrumAddress(); + } + if (keys.contains("spectrumConfig.wsSpectrumPort")) { + m_wsSpectrumPort = swgSpectrum->getWsSpectrumPort(); + } + if (keys.contains("spectrumConfig.displayHistogram")) { + m_displayHistogram = swgSpectrum->getDisplayHistogram() != 0; + } + if (keys.contains("spectrumConfig.decay")) { + m_decay = swgSpectrum->getDecay(); + } + if (keys.contains("spectrumConfig.decayDivisor")) { + m_decayDivisor = swgSpectrum->getDecayDivisor(); + } + if (keys.contains("spectrumConfig.histogramStroke")) { + m_histogramStroke = swgSpectrum->getHistogramStroke(); + } + if (keys.contains("spectrumConfig.displayMaxHold")) { + m_displayMaxHold = swgSpectrum->getDisplayMaxHold() != 0; + } + if (keys.contains("spectrumConfig.displayCurrent")) { + m_displayCurrent = swgSpectrum->getDisplayCurrent() != 0; + } + if (keys.contains("spectrumConfig.displayTraceIntensity")) { + m_displayTraceIntensity = swgSpectrum->getDisplayTraceIntensity(); + } + if (keys.contains("spectrumConfig.invertedWaterfall")) { + m_invertedWaterfall = swgSpectrum->getInvertedWaterfall() != 0; + } + if (keys.contains("spectrumConfig.displayWaterfall")) { + m_displayWaterfall = swgSpectrum->getDisplayWaterfall() != 0; + } + if (keys.contains("spectrumConfig.displayGrid")) { + m_displayGrid = swgSpectrum->getDisplayGrid() != 0; + } + if (keys.contains("spectrumConfig.displayGridIntensity")) { + m_displayGridIntensity = swgSpectrum->getDisplayGridIntensity(); + } + if (keys.contains("spectrumConfig.ssb")) { + m_ssb = swgSpectrum->getSsb() != 0; + } + if (keys.contains("spectrumConfig.usb")) { + m_usb = swgSpectrum->getUsb() != 0; + } + if (keys.contains("spectrumConfig.waterfallShare")) { + m_waterfallShare = swgSpectrum->getWaterfallShare(); + } + if (keys.contains("spectrumConfig.markersDisplay")) { + m_markersDisplay = (SpectrumSettings::MarkersDisplay) swgSpectrum->getMarkersDisplay(); + } + if (keys.contains("spectrumConfig.useCalibration")) { + m_useCalibration = swgSpectrum->getUseCalibration() != 0; + } + if (keys.contains("spectrumConfig.calibrationInterpMode")) { + m_calibrationInterpMode = (CalibrationInterpolationMode) swgSpectrum->getCalibrationInterpMode(); + } + + if (keys.contains("spectrumConfig.histogramMarkers")) + { + QList *swgHistogramMarkers = swgSpectrum->getHistogramMarkers(); + m_histogramMarkers.clear(); + int i = 0; + + for (const auto &swgHistogramMarker : *swgHistogramMarkers) + { + m_histogramMarkers.push_back(SpectrumHistogramMarker()); + m_histogramMarkers.back().m_frequency = swgHistogramMarker->getFrequency(); + m_histogramMarkers.back().m_power = swgHistogramMarker->getPower(); + m_histogramMarkers.back().m_markerType = (SpectrumHistogramMarker::SpectrumMarkerType) swgHistogramMarker->getMarkerType(); + m_histogramMarkers.back().m_markerColor = intToQColor(swgHistogramMarker->getMarkerColor()); + m_histogramMarkers.back().m_show = swgHistogramMarker->getShow() != 0; + + if (i++ == 10) { // no more than 10 markers + break; + } + } + } + + if (keys.contains("spectrumConfig.waterfallMarkers")) + { + QList *swgWaterfallMarkers = swgSpectrum->getWaterfallMarkers(); + m_waterfallMarkers.clear(); + int i = 0; + + for (const auto &swgWaterfallMarker : *swgWaterfallMarkers) + { + m_waterfallMarkers.push_back(SpectrumWaterfallMarker()); + m_waterfallMarkers.back().m_frequency = swgWaterfallMarker->getFrequency(); + m_waterfallMarkers.back().m_time = swgWaterfallMarker->getTime(); + m_waterfallMarkers.back().m_markerColor = intToQColor(swgWaterfallMarker->getMarkerColor()); + m_waterfallMarkers.back().m_show = swgWaterfallMarker->getShow() != 0; + + if (i++ == 10) { // no more than 10 markers + break; + } + } + } + + if (keys.contains("spectrumConfig.annotationMarkers")) + { + QList *swgAnnotationMarkers = swgSpectrum->getAnnotationMarkers(); + m_waterfallMarkers.clear(); + + for (const auto &swgAnnotationMarker : *swgAnnotationMarkers) + { + m_annoationMarkers.push_back(SpectrumAnnotationMarker()); + m_annoationMarkers.back().m_startFrequency = swgAnnotationMarker->getStartFrequency(); + m_annoationMarkers.back().m_bandwidth = swgAnnotationMarker->getBandwidth() < 0 ? 0 : swgAnnotationMarker->getBandwidth(); + m_annoationMarkers.back().m_markerColor = intToQColor(swgAnnotationMarker->getMarkerColor()); + m_annoationMarkers.back().m_show = (SpectrumAnnotationMarker::ShowState) swgAnnotationMarker->getShow(); + } + } + + if (keys.contains("spectrumConfig.calibrationPoints")) + { + QList *swgCalibrationPoints = swgSpectrum->getCalibrationPoints(); + m_calibrationPoints.clear(); + + for (const auto &swgCalibrationPoint : *swgCalibrationPoints) + { + m_calibrationPoints.push_back(SpectrumCalibrationPoint()); + m_calibrationPoints.back().m_frequency = swgCalibrationPoint->getFrequency(); + m_calibrationPoints.back().m_powerRelativeReference = swgCalibrationPoint->getPowerRelativeReference(); + m_calibrationPoints.back().m_powerCalibratedReference = swgCalibrationPoint->getPowerAbsoluteReference(); + } + } +} + +int SpectrumSettings::getAveragingMaxScale(AveragingMode averagingMode) +{ + if (averagingMode == AvgModeMoving) { + return 3; // max 10k + } else { + return 5; // max 1M + } +} + +int SpectrumSettings::getAveragingValue(int averagingIndex, AveragingMode averagingMode) +{ + if (averagingIndex <= 0) { + return 1; + } + + int v = averagingIndex - 1; + int m = pow(10.0, v/3 > getAveragingMaxScale(averagingMode) ? getAveragingMaxScale(averagingMode) : v/3); + int x = 1; + + if (v % 3 == 0) { + x = 2; + } else if (v % 3 == 1) { + x = 5; + } else if (v % 3 == 2) { + x = 10; + } + + return x * m; +} + +int SpectrumSettings::getAveragingIndex(int averagingValue, AveragingMode averagingMode) +{ + if (averagingValue <= 1) { + return 0; + } + + int v = averagingValue; + int j = 0; + + for (int i = 0; i <= getAveragingMaxScale(averagingMode); i++) + { + if (v < 20) + { + if (v < 2) { + j = 0; + } else if (v < 5) { + j = 1; + } else if (v < 10) { + j = 2; + } else { + j = 3; + } + + return 3*i + j; + } + + v /= 10; + } + + return 3*getAveragingMaxScale(averagingMode) + 3; +} + +uint64_t SpectrumSettings::getMaxAveragingValue(int fftSize, AveragingMode averagingMode) +{ + if (averagingMode == AvgModeMoving) + { + uint64_t limit = (1UL<<28) / (sizeof(double)*fftSize); // 256 MB max + return limit > (1<<14) ? (1<<14) : limit; // limit to 16 kS anyway + } + else + { + return (1<<20); // fixed 1 MS + } +} + +int SpectrumSettings::qColorToInt(const QColor& color) +{ + return 256*256*color.blue() + 256*color.green() + color.red(); +} + +QColor SpectrumSettings::intToQColor(int intColor) +{ + int r = intColor % 256; + int bg = intColor / 256; + int g = bg % 256; + int b = bg / 256; + return QColor(r, g, b); +} diff --git a/android/app/src/main/cpp/dsp/spectrumsettings.h b/android/app/src/main/cpp/dsp/spectrumsettings.h new file mode 100644 index 0000000..e6a76d6 --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrumsettings.h @@ -0,0 +1,172 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_SPECTRUMSETTNGS_H +#define SDRBASE_DSP_SPECTRUMSETTNGS_H + +#include +#include + +#include "export.h" +#include "dsp/dsptypes.h" +#include "dsp/fftwindow.h" +#include "dsp/spectrummarkers.h" +#include "dsp/spectrumcalibrationpoint.h" +#include "settings/serializable.h" + +class SDRBASE_API SpectrumSettings : public Serializable +{ +public: + enum AveragingMode + { + AvgModeNone, + AvgModeMoving, + AvgModeFixed, + AvgModeMax + }; + + // Bitmask for which selection of markers to display + enum MarkersDisplay + { + MarkersDisplayNone = 0, + MarkersDisplaySpectrum = 0x1, + MarkersDisplayAnnotations = 0x2, + MarkersDisplayAll = 0x3 + }; + + enum CalibrationInterpolationMode + { + CalibInterpLinear, //!< linear power + CalibInterpLog //!< log power (dB linear) + }; + + enum SpectrogramStyle + { + Points, + Lines, + Solid, + Outline, + Shaded + }; + + enum SpectrumStyle + { + Line, + Fill, + Gradient + }; + + enum Measurement + { + MeasurementNone, + MeasurementPeaks, + MeasurementChannelPower, + MeasurementAdjacentChannelPower, + MeasurementOccupiedBandwidth, + Measurement3dBBandwidth, + MeasurementSNR + }; + + enum MeasurementsPosition { + PositionAbove, + PositionBelow, + PositionLeft, + PositionRight + }; + + int m_fftSize; + int m_fftOverlap; + FFTWindow::Function m_fftWindow; + Real m_refLevel; + Real m_powerRange; + int m_fpsPeriodMs; //!< FPS capping period in ms FPS = 1000/m_fpsPeriodMs. If zero: no limit. + int m_decay; + int m_decayDivisor; + int m_histogramStroke; + int m_displayGridIntensity; + int m_displayTraceIntensity; + bool m_displayWaterfall; + bool m_invertedWaterfall; + Real m_waterfallShare; + bool m_display3DSpectrogram; + bool m_displayMaxHold; + bool m_displayCurrent; + bool m_displayHistogram; + bool m_displayGrid; + bool m_truncateFreqScale; + AveragingMode m_averagingMode; + int m_averagingIndex; + unsigned int m_averagingValue; + bool m_linear; //!< linear else logarithmic scale + bool m_ssb; //!< SSB display with spectrum center at start of array or display - else spectrum center is on center + bool m_usb; //!< USB display with increasing frequencies towads the right - else decreasing frequencies + bool m_wsSpectrum; //!< Start or stop websocket spectrum server + QString m_wsSpectrumAddress; //!< websocket spectrum server address + uint16_t m_wsSpectrumPort; //!< websocket spectrum server port + QList m_histogramMarkers; + QList m_waterfallMarkers; + QList m_annoationMarkers; + bool m_findHistogramPeaks; + MarkersDisplay m_markersDisplay; + QList m_calibrationPoints; + bool m_useCalibration; + CalibrationInterpolationMode m_calibrationInterpMode; //!< How is power interpolated between calibration points + SpectrogramStyle m_3DSpectrogramStyle; + QString m_colorMap; + SpectrumStyle m_spectrumStyle; + Measurement m_measurement; + int m_measurementCenterFrequencyOffset; + int m_measurementBandwidth; + int m_measurementChSpacing; + int m_measurementAdjChBandwidth; + int m_measurementHarmonics; + int m_measurementPeaks; + bool m_measurementHighlight; + MeasurementsPosition m_measurementsPosition; + int m_measurementPrecision; + bool m_showAllControls; + + static const int m_log2FFTSizeMin = 6; // 64 + static const int m_log2FFTSizeMax = 15; // 32k + + SpectrumSettings(); + virtual ~SpectrumSettings(); + void resetToDefaults(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const; + virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject); + + QList& getHistogramMarkers() { return m_histogramMarkers; } + QList& getWaterfallMarkers() { return m_waterfallMarkers; } + bool getHistogramFindPeaks() { return m_findHistogramPeaks; } + + static int getAveragingMaxScale(AveragingMode averagingMode); //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000 + static int getAveragingValue(int averagingIndex, AveragingMode averagingMode); + static int getAveragingIndex(int averagingValue, AveragingMode averagingMode); + static uint64_t getMaxAveragingValue(int fftSize, AveragingMode averagingMode); + +private: + static int qColorToInt(const QColor& color); + static QColor intToQColor(int intColor); +}; + +#endif // SDRBASE_DSP_SPECTRUMSETTNGS_H diff --git a/android/app/src/main/cpp/dsp/spectrumvis.cpp b/android/app/src/main/cpp/dsp/spectrumvis.cpp new file mode 100644 index 0000000..761d44a --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrumvis.cpp @@ -0,0 +1,1132 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// Copyright (C) 2023 Arne Jünemann // +// Copyright (C) 2023 Vladimir Pleskonjic // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGGLSpectrum.h" +#include "SWGSpectrumServer.h" +#include "SWGSuccessResponse.h" + +#include "glspectruminterface.h" +#include "dspcommands.h" +#include "dspengine.h" +#include "fftfactory.h" +#include "util/messagequeue.h" + +#include "spectrumvis.h" + +MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureSpectrumVis, Message) +MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureScalingFactor, Message) +MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureWSpectrumOpenClose, Message) +MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureWSpectrum, Message) +MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgFrequencyZooming, Message) + +const Real SpectrumVis::m_mult = (10.0f / log2(10.0f)); + +SpectrumVis::SpectrumVis(Real scalef) : + BasebandSampleSink(), + m_running(true), + m_fft(nullptr), + m_fftEngineSequence(0), + m_fftBuffer(4096), + m_powerSpectrum(4096), + m_psd(4096), + m_fftBufferFill(0), + m_needMoreSamples(false), + m_frequencyZoomFactor(1.0f), + m_frequencyZoomPos(0.5f), + m_scalef(scalef), + m_glSpectrum(nullptr), + m_specMax(0.0f), + m_centerFrequency(0), + m_sampleRate(48000), + m_ofs(0), + m_powFFTDiv(1.0), + m_guiMessageQueue(nullptr) +{ + setObjectName("SpectrumVis"); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + applySettings(m_settings, true); +} + +SpectrumVis::~SpectrumVis() +{ + FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); + fftFactory->releaseEngine(m_settings.m_fftSize, false, m_fftEngineSequence); +} + +void SpectrumVis::setScalef(Real scalef) +{ + MsgConfigureScalingFactor* cmd = new MsgConfigureScalingFactor(scalef); + m_inputMessageQueue.push(cmd); +} + +void SpectrumVis::configureWSSpectrum(const QString& address, uint16_t port) +{ + MsgConfigureWSpectrum* cmd = new MsgConfigureWSpectrum(address, port); + m_inputMessageQueue.push(cmd); +} + +void SpectrumVis::feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly) +{ + feed(triggerPoint, end, positiveOnly); // normal feed from trigger point + /* + if (triggerPoint == end) + { + // the following piece of code allows to terminate the FFT that ends past the end of scope captured data + // that is the spectrum will include the captured data + // just do nothing if you want the spectrum to be included inside the scope captured data + // that is to drop the FFT that dangles past the end of captured data + if (m_needMoreSamples) { + feed(begin, end, positiveOnly); + m_needMoreSamples = false; // force finish + } + } + else + { + feed(triggerPoint, end, positiveOnly); // normal feed from trigger point + }*/ +} + +void SpectrumVis::feed(const Complex *begin, unsigned int length) +{ + if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) { + return; + } + + if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process + return; + } + + Complex c; + Real v; + int fftMin = (m_frequencyZoomFactor == 1.0f) ? + 0 : (m_frequencyZoomPos - (0.5f / m_frequencyZoomFactor)) * m_settings.m_fftSize; + int fftMax = (m_frequencyZoomFactor == 1.0f) ? + m_settings.m_fftSize : (m_frequencyZoomPos + (0.5f / m_frequencyZoomFactor)) * m_settings.m_fftSize; + + if (m_settings.m_averagingMode == SpectrumSettings::AvgModeNone) + { + for (int i = 0; i < m_settings.m_fftSize; i++) + { + if (i < (int) length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + m_psd[i] = v/m_powFFTDiv; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i] = v; + } + + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + } + else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMoving) + { + for (int i = 0; i < m_settings.m_fftSize; i++) + { + if (i < (int) length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i); + m_psd[i] = v/m_powFFTDiv; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i] = v; + } + + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + + m_movingAverage.nextAverage(); + } + else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeFixed) + { + double avg; + + for (int i = 0; i < m_settings.m_fftSize; i++) + { + if (i < (int) length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_fixedAverage.storeAndGetAvg(avg, v, i)) + { + m_psd[i] = avg/m_powFFTDiv; + avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs; + m_powerSpectrum[i] = avg; + } + } + + // result available + if (m_fixedAverage.nextAverage()) + { + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + } + } + else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMax) + { + double max; + + for (int i = 0; i < m_settings.m_fftSize; i++) + { + if (i < (int) length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_max.storeAndGetMax(max, v, i)) + { + m_psd[i] = max/m_powFFTDiv; + max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs; + m_powerSpectrum[i] = max; + } + } + + // result available + if (m_max.nextMax()) + { + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + } + } + + m_mutex.unlock(); +} + +void SpectrumVis::feed(const ComplexVector::const_iterator& cbegin, const ComplexVector::const_iterator& end, bool positiveOnly) +{ + if (!m_running) { + return; + } + + // if no visualisation is set, send the samples to /dev/null + if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) { + return; + } + + if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process + return; + } + + ComplexVector::const_iterator begin(cbegin); + + while (begin < end) + { + std::size_t todo = end - begin; + std::size_t samplesNeeded = m_settings.m_fftSize - m_fftBufferFill; + + if (todo >= samplesNeeded) + { + // fill up the buffer + std::copy(begin, begin + samplesNeeded, m_fftBuffer.begin() + m_fftBufferFill); + begin += samplesNeeded; + + processFFT(positiveOnly); + + // advance buffer respecting the fft overlap factor + // undefined behavior if the memory regions overlap, valid code for 50% overlap + std::copy(m_fftBuffer.begin() + m_refillSize, m_fftBuffer.end(), m_fftBuffer.begin()); + + // start over + m_fftBufferFill = m_overlapSize; + m_needMoreSamples = false; + } + else + { + // not enough samples for FFT - just fill in new data and return + std::copy(begin, end, m_fftBuffer.begin() + m_fftBufferFill); + begin = end; + m_fftBufferFill += todo; + m_needMoreSamples = true; + } + } + + m_mutex.unlock(); +} + +void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly) +{ + if (!m_running) { + return; + } + + // if no visualisation is set, send the samples to /dev/null + if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) { + return; + } + + if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process + return; + } + + SampleVector::const_iterator begin(cbegin); + + while (begin < end) + { + std::size_t todo = end - begin; + std::size_t samplesNeeded = m_settings.m_fftSize - m_fftBufferFill; + + if (todo >= samplesNeeded) + { + // fill up the buffer + std::vector::iterator it = m_fftBuffer.begin() + m_fftBufferFill; + + for (std::size_t i = 0; i < samplesNeeded; ++i, ++begin) { + *it++ = Complex(begin->real() / m_scalef, begin->imag() / m_scalef); + } + + processFFT(positiveOnly); + + // advance buffer respecting the fft overlap factor + // undefined behavior if the memory regions overlap, valid code for 50% overlap + std::copy(m_fftBuffer.begin() + m_refillSize, m_fftBuffer.end(), m_fftBuffer.begin()); + + // start over + m_fftBufferFill = m_overlapSize; + m_needMoreSamples = false; + } + else + { + // not enough samples for FFT - just fill in new data and return + for (std::vector::iterator it = m_fftBuffer.begin() + m_fftBufferFill; begin < end; ++begin) { + *it++ = Complex(begin->real() / m_scalef, begin->imag() / m_scalef); + } + + m_fftBufferFill += todo; + m_needMoreSamples = true; + } + } + + m_mutex.unlock(); +} + +void SpectrumVis::processFFT(bool positiveOnly) +{ + int fftMin = (m_frequencyZoomFactor == 1.0f) ? + 0 : (m_frequencyZoomPos - (0.5f / m_frequencyZoomFactor)) * m_settings.m_fftSize; + int fftMax = (m_frequencyZoomFactor == 1.0f) ? + m_settings.m_fftSize : (m_frequencyZoomPos + (0.5f / m_frequencyZoomFactor)) * m_settings.m_fftSize; + + // apply fft window (and copy from m_fftBuffer to m_fftIn) + m_window.apply(&m_fftBuffer[0], m_fft->in()); + + // calculate FFT + m_fft->transform(); + + // extract power spectrum and reorder buckets + const Complex* fftOut = m_fft->out(); + Complex c; + Real v; + std::size_t halfSize = m_settings.m_fftSize / 2; + + if (m_settings.m_averagingMode == SpectrumSettings::AvgModeNone) + { + m_specMax = 0.0f; + + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + m_psd[i] = v/m_powFFTDiv; + m_specMax = v > m_specMax ? v : m_specMax; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i * 2] = v; + m_powerSpectrum[i * 2 + 1] = v; + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + m_psd[i] = v/m_powFFTDiv; + m_specMax = v > m_specMax ? v : m_specMax; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i] = v; + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + m_psd[i + halfSize] = v/m_powFFTDiv; + m_specMax = v > m_specMax ? v : m_specMax; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i + halfSize] = v; + } + } + + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + } + else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMoving) + { + m_specMax = 0.0f; + + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i); + m_psd[i] = v/m_powFFTDiv; + m_specMax = v > m_specMax ? v : m_specMax; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i * 2] = v; + m_powerSpectrum[i * 2 + 1] = v; + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i+halfSize); + m_psd[i] = v/m_powFFTDiv; + m_specMax = v > m_specMax ? v : m_specMax; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i] = v; + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i); + m_psd[i + halfSize] = v/m_powFFTDiv; + m_specMax = v > m_specMax ? v : m_specMax; + v = m_settings.m_linear ? v/m_powFFTDiv : m_mult * log2fapprox(v) + m_ofs; + m_powerSpectrum[i + halfSize] = v; + } + } + + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + + m_movingAverage.nextAverage(); + } + else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeFixed) + { + double avg; + Real specMax = 0.0f; + + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_fixedAverage.storeAndGetAvg(avg, v, i)) + { + m_psd[i] = avg/m_powFFTDiv; + specMax = avg > specMax ? avg : specMax; + avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs; + m_powerSpectrum[i * 2] = avg; + m_powerSpectrum[i * 2 + 1] = avg; + } + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize)) + { + m_psd[i] = avg/m_powFFTDiv; + specMax = avg > specMax ? avg : specMax; + avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs; + m_powerSpectrum[i] = avg; + } + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_fixedAverage.storeAndGetAvg(avg, v, i)) + { + m_psd[i + halfSize] = avg/m_powFFTDiv; + specMax = avg > specMax ? avg : specMax; + avg = m_settings.m_linear ? avg/m_powFFTDiv : m_mult * log2fapprox(avg) + m_ofs; + m_powerSpectrum[i + halfSize] = avg; + } + } + } + + // result available + if (m_fixedAverage.nextAverage()) + { + m_specMax = specMax; + + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + } + } + else if (m_settings.m_averagingMode == SpectrumSettings::AvgModeMax) + { + double max; + Real specMax = 0.0f; + + if ( positiveOnly ) + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_max.storeAndGetMax(max, v, i)) + { + m_psd[i] = max/m_powFFTDiv; + specMax = max > specMax ? max : specMax; + max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs; + m_powerSpectrum[i * 2] = max; + m_powerSpectrum[i * 2 + 1] = max; + } + } + } + else + { + for (std::size_t i = 0; i < halfSize; i++) + { + c = fftOut[i + halfSize]; + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_max.storeAndGetMax(max, v, i+halfSize)) + { + m_psd[i] = max/m_powFFTDiv; + specMax = max > specMax ? max : specMax; + max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs; + m_powerSpectrum[i] = max; + } + + c = fftOut[i]; + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_max.storeAndGetMax(max, v, i)) + { + m_psd[i + halfSize] = max/m_powFFTDiv; + specMax = max > specMax ? max : specMax; + max = m_settings.m_linear ? max/m_powFFTDiv : m_mult * log2fapprox(max) + m_ofs; + m_powerSpectrum[i + halfSize] = max; + } + } + } + + // result available + if (m_max.nextMax()) + { + m_specMax = specMax; + + // send new data to visualisation + if (m_glSpectrum) + { + m_glSpectrum->newSpectrum( + &m_powerSpectrum.data()[fftMin], + fftMax - fftMin, + m_settings.m_fftSize + ); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_settings.m_fftSize, + m_centerFrequency, + m_sampleRate, + m_settings.m_linear, + m_settings.m_ssb, + m_settings.m_usb + ); + } + } + } +} + +void SpectrumVis::getZoomedPSDCopy(std::vector& copy) const +{ + int fftMin = (m_frequencyZoomFactor == 1.0f) ? + 0 : (m_frequencyZoomPos - (0.5f / m_frequencyZoomFactor)) * m_settings.m_fftSize; + int fftMax = (m_frequencyZoomFactor == 1.0f) ? + m_settings.m_fftSize : (m_frequencyZoomPos + (0.5f / m_frequencyZoomFactor)) * m_settings.m_fftSize; + copy.assign(m_psd.begin() + fftMin, m_psd.begin() + fftMax); +} + +void SpectrumVis::start() +{ + setRunning(true); + + if (getMessageQueueToGUI()) // propagate to GUI if any + { + MsgStartStop *msg = MsgStartStop::create(true); + getMessageQueueToGUI()->push(msg); + } +} + +void SpectrumVis::stop() +{ + setRunning(false); + + if (getMessageQueueToGUI()) // propagate to GUI if any + { + MsgStartStop *msg = MsgStartStop::create(false); + getMessageQueueToGUI()->push(msg); + } +} + +void SpectrumVis::pushMessage(Message *msg) +{ + m_inputMessageQueue.push(msg); +} + +QString SpectrumVis::getSinkName() +{ + return objectName(); +} + +void SpectrumVis::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool SpectrumVis::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + // This is coming from device engine and will apply to main spectrum + DSPSignalNotification& notif = (DSPSignalNotification&) message; + qDebug() << "SpectrumVis::handleMessage: DSPSignalNotification:" + << " centerFrequency: " << notif.getCenterFrequency() + << " sampleRate: " << notif.getSampleRate(); + handleConfigureDSP(notif.getCenterFrequency(), notif.getSampleRate()); + return true; + } + else if (MsgConfigureSpectrumVis::match(message)) + { + MsgConfigureSpectrumVis& cfg = (MsgConfigureSpectrumVis&) message; + qDebug() << "SpectrumVis::handleMessage: MsgConfigureSpectrumVis"; + applySettings(cfg.getSettings(), cfg.getForce()); + return true; + } + else if (MsgConfigureScalingFactor::match(message)) + { + MsgConfigureScalingFactor& conf = (MsgConfigureScalingFactor&) message; + handleScalef(conf.getScalef()); + return true; + } + else if (MsgConfigureWSpectrumOpenClose::match(message)) + { + MsgConfigureWSpectrumOpenClose& conf = (MsgConfigureWSpectrumOpenClose&) message; + handleWSOpenClose(conf.getOpenClose()); + return true; + } + else if (MsgConfigureWSpectrum::match(message)) { + MsgConfigureWSpectrum& conf = (MsgConfigureWSpectrum&) message; + handleConfigureWSSpectrum(conf.getAddress(), conf.getPort()); + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + setRunning(cmd.getStartStop()); + return true; + } + else if (MsgFrequencyZooming::match(message)) + { + MsgFrequencyZooming& cmd = (MsgFrequencyZooming&) message; + m_frequencyZoomFactor = cmd.getFrequencyZoomFactor(); + m_frequencyZoomPos = cmd.getFrequencyZoomPos(); + return true; + } + else + { + return false; + } +} + +void SpectrumVis::applySettings(const SpectrumSettings& settings, bool force) +{ + QMutexLocker mutexLocker(&m_mutex); + + int fftSize = settings.m_fftSize > (1<getFFTFactory(); + + // release previous engine allocation if any + if (m_fft) { + fftFactory->releaseEngine(m_settings.m_fftSize, false, m_fftEngineSequence); + } + + m_fftEngineSequence = fftFactory->getEngine(fftSize, false, &m_fft); + m_ofs = 20.0f * log10f(1.0f / fftSize); + m_powFFTDiv = fftSize * fftSize; + + if (fftSize > m_settings.m_fftSize) + { + m_fftBuffer.resize(fftSize); + m_powerSpectrum.resize(fftSize); + m_psd.resize(fftSize); + } + } + + if ((fftSize != m_settings.m_fftSize) + || (settings.m_fftWindow != m_settings.m_fftWindow) || force) + { + m_window.create(settings.m_fftWindow, fftSize); + } + + if ((fftSize != m_settings.m_fftSize) + || (settings.m_fftOverlap != m_settings.m_fftOverlap) || force) + { + m_overlapSize = settings.m_fftOverlap < 0 ? 0 : + settings.m_fftOverlap < fftSize ? settings.m_fftOverlap : (fftSize - 1); + m_refillSize = fftSize - m_overlapSize; + m_fftBufferFill = m_overlapSize; + } + + if ((fftSize != m_settings.m_fftSize) + || (settings.m_averagingIndex != m_settings.m_averagingIndex) + || (settings.m_averagingMode != m_settings.m_averagingMode) || force) + { + unsigned int averagingValue = SpectrumSettings::getAveragingValue(settings.m_averagingIndex, settings.m_averagingMode); + averagingValue = averagingValue > SpectrumSettings::getMaxAveragingValue(fftSize, settings.m_averagingMode) ? + SpectrumSettings::getMaxAveragingValue(fftSize, settings.m_averagingMode) : averagingValue; // Capping to avoid out of memory condition + m_movingAverage.resize(fftSize, averagingValue); + m_fixedAverage.resize(fftSize, averagingValue); + m_max.resize(fftSize, averagingValue); + } + + if ((settings.m_wsSpectrumAddress != m_settings.m_wsSpectrumAddress) + || (settings.m_wsSpectrumPort != m_settings.m_wsSpectrumPort) || force) { + handleConfigureWSSpectrum(settings.m_wsSpectrumAddress, settings.m_wsSpectrumPort); + } + + m_settings = settings; + m_settings.m_fftSize = fftSize; + + if (m_guiMessageQueue) + { + MsgConfigureSpectrumVis *msg = MsgConfigureSpectrumVis::create(m_settings, false); + m_guiMessageQueue->push(msg); + } +} + +void SpectrumVis::handleConfigureDSP(uint64_t centerFrequency, int sampleRate) +{ + QMutexLocker mutexLocker(&m_mutex); + m_centerFrequency = centerFrequency; + m_sampleRate = sampleRate; +} + +void SpectrumVis::handleScalef(Real scalef) +{ + QMutexLocker mutexLocker(&m_mutex); + m_scalef = scalef; +} + +void SpectrumVis::handleWSOpenClose(bool openClose) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (openClose) { + m_wsSpectrum.openSocket(); + } else { + m_wsSpectrum.closeSocket(); + } +} + +void SpectrumVis::handleConfigureWSSpectrum(const QString& address, uint16_t port) +{ + m_wsSpectrum.setListeningAddress(address); + m_wsSpectrum.setPort(port); + + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.closeSocket(); + m_wsSpectrum.openSocket(); + } +} + +int SpectrumVis::webapiSpectrumSettingsGet(SWGSDRangel::SWGGLSpectrum& response, QString& errorMessage) const +{ + (void) errorMessage; + response.init(); + webapiFormatSpectrumSettings(response, m_settings); + return 200; +} + +int SpectrumVis::webapiSpectrumSettingsPutPatch( + bool force, + const QStringList& spectrumSettingsKeys, + SWGSDRangel::SWGGLSpectrum& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + SpectrumSettings settings = m_settings; + webapiUpdateSpectrumSettings(settings, spectrumSettingsKeys, response); + + MsgConfigureSpectrumVis *msg = MsgConfigureSpectrumVis::create(settings, force); + m_inputMessageQueue.push(msg); + + if (getMessageQueueToGUI()) // forward to GUI if any + { + MsgConfigureSpectrumVis *msgToGUI = MsgConfigureSpectrumVis::create(settings, force); + getMessageQueueToGUI()->push(msgToGUI); + } + + webapiFormatSpectrumSettings(response, settings); + return 200; +} + +int SpectrumVis::webapiSpectrumServerGet(SWGSDRangel::SWGSpectrumServer& response, QString& errorMessage) const +{ + (void) errorMessage; + bool serverRunning = m_wsSpectrum.socketOpened(); + QList peerHosts; + QList peerPorts; + m_wsSpectrum.getPeers(peerHosts, peerPorts); + response.init(); + response.setRun(serverRunning ? 1 : 0); + + QHostAddress serverAddress = m_wsSpectrum.getListeningAddress(); + + if (serverAddress != QHostAddress::Null) { + response.setListeningAddress(new QString(serverAddress.toString())); + } + + uint16_t serverPort = m_wsSpectrum.getListeningPort(); + + if (serverPort != 0) { + response.setListeningPort(serverPort); + } + + if (peerHosts.size() > 0) + { + response.setClients(new QList); + + for (int i = 0; i < peerHosts.size(); i++) + { + response.getClients()->push_back(new SWGSDRangel::SWGSpectrumServer_clients); + response.getClients()->back()->setAddress(new QString(peerHosts.at(i).toString())); + response.getClients()->back()->setPort(peerPorts.at(i)); + } + } + + return 200; +} + +int SpectrumVis::webapiSpectrumServerPost(SWGSDRangel::SWGSuccessResponse& response, QString& errorMessage) +{ + (void) errorMessage; + MsgConfigureWSpectrumOpenClose *msg = MsgConfigureWSpectrumOpenClose::create(true); + m_inputMessageQueue.push(msg); + + if (getMessageQueueToGUI()) // forward to GUI if any + { + MsgConfigureWSpectrumOpenClose *msgToGui = MsgConfigureWSpectrumOpenClose::create(true); + getMessageQueueToGUI()->push(msgToGui); + } + + response.setMessage(new QString("Websocket spectrum server started")); + return 200; +} + +int SpectrumVis::webapiSpectrumServerDelete(SWGSDRangel::SWGSuccessResponse& response, QString& errorMessage) +{ + (void) errorMessage; + MsgConfigureWSpectrumOpenClose *msg = MsgConfigureWSpectrumOpenClose::create(false); + m_inputMessageQueue.push(msg); + + if (getMessageQueueToGUI()) // forward to GUI if any + { + MsgConfigureWSpectrumOpenClose *msgToGui = MsgConfigureWSpectrumOpenClose::create(false); + getMessageQueueToGUI()->push(msgToGui); + } + + response.setMessage(new QString("Websocket spectrum server stopped")); + return 200; +} + +void SpectrumVis::webapiFormatSpectrumSettings(SWGSDRangel::SWGGLSpectrum& response, const SpectrumSettings& settings) +{ + settings.formatTo(&response); +} + +void SpectrumVis::webapiUpdateSpectrumSettings( + SpectrumSettings& settings, + const QStringList& spectrumSettingsKeys, + SWGSDRangel::SWGGLSpectrum& response) +{ + QStringList prefixedKeys; + + for (const auto &key : spectrumSettingsKeys) { + prefixedKeys.append(tr("spectrumConfig.%1").arg(key)); + } + + settings.updateFrom(prefixedKeys, &response); +} + +// To calculate power, the usual equation: +// 10*log10(V1/V2), where V2=fftSize^2 +// is calculated using log2 instead, with: +// ofs=20.0f * log10f(1.0f / fftSize) +// mult=(10.0f / log2(10.0f)) +// dB = m_mult * log2f(v) + m_ofs +// However, while the gcc version of log2f is twice as fast as log10f, +// MSVC version is 6x slower. +// Also, we don't need full accuracy of log2f for calculating the power for the spectrum, +// so we can use the following approximation to get a good speed-up for both compilers: +// https://www.vplesko.com/posts/replacing_log2f.html +// https://www.vplesko.com/assets/replacing_log2f/main.c.txt +float SpectrumVis::log2fapprox(float x) const +{ + // IEEE 754 representation constants. + const int32_t mantissaLen = 23; + const int32_t mantissaMask = (1 << mantissaLen) - 1; + const int32_t baseExponent = -127; + + // Reinterpret x as int in a standard compliant way. + int32_t xi; + memcpy(&xi, &x, sizeof(xi)); + + // Calculate exponent of x. + float e = (float)((xi >> mantissaLen) + baseExponent); + + // Calculate mantissa of x. It will be in range [1, 2). + float m; + int32_t mxi = (xi & mantissaMask) | ((-baseExponent) << mantissaLen); + memcpy(&m, &mxi, sizeof(m)); + + // Use Remez algorithm-generated approximation polynomial + // for log2(a) where a is in range [1, 2]. + float l = 0.15824871f; + l = l * m + -1.051875f; + l = l * m + 3.0478842f; + l = l * m + -2.1536207f; + + // Add exponent to the calculation. + // Final log is log2(m*2^e)=log2(m)+e. + l += e; + + return l; +} diff --git a/android/app/src/main/cpp/dsp/spectrumvis.h b/android/app/src/main/cpp/dsp/spectrumvis.h new file mode 100644 index 0000000..7f567fe --- /dev/null +++ b/android/app/src/main/cpp/dsp/spectrumvis.h @@ -0,0 +1,274 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SPECTRUMVIS_H +#define INCLUDE_SPECTRUMVIS_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "dsp/fftengine.h" +#include "dsp/fftwindow.h" +#include "dsp/spectrumsettings.h" +#include "export.h" +#include "util/message.h" +#include "util/messagequeue.h" +#include "util/movingaverage2d.h" +#include "util/fixedaverage2d.h" +#include "util/max2d.h" +#include "websockets/wsspectrum.h" + +class GLSpectrumInterface; + +namespace SWGSDRangel { + class SWGGLSpectrum; + class SWGSpectrumServer; + class SWGSuccessResponse; +}; + +class SDRBASE_API SpectrumVis : public QObject, public BasebandSampleSink { + Q_OBJECT +public: + class SDRBASE_API MsgConfigureSpectrumVis : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SpectrumSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSpectrumVis* create(const SpectrumSettings& settings, bool force) { + return new MsgConfigureSpectrumVis(settings, force); + } + + private: + SpectrumSettings m_settings; + bool m_force; + + MsgConfigureSpectrumVis(const SpectrumSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class SDRBASE_API MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class SDRBASE_API MsgConfigureWSpectrumOpenClose : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + Real getOpenClose() const { return m_openClose; } + + static MsgConfigureWSpectrumOpenClose* create(bool openClose) { + return new MsgConfigureWSpectrumOpenClose(openClose); + } + + private: + bool m_openClose; + + MsgConfigureWSpectrumOpenClose(bool openClose) : + Message(), + m_openClose(openClose) + {} + }; + + class SDRBASE_API MsgFrequencyZooming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + float getFrequencyZoomFactor() const { return m_frequencyZoomFactor; } + float getFrequencyZoomPos() const { return m_frequencyZoomPos; } + + static MsgFrequencyZooming* create(float frequencyZoomFactor, float frequencyZoomPos) { + return new MsgFrequencyZooming(frequencyZoomFactor, frequencyZoomPos); + } + + private: + float m_frequencyZoomFactor; + float m_frequencyZoomPos; + + MsgFrequencyZooming(float frequencyZoomFactor, float frequencyZoomPos) : + Message(), + m_frequencyZoomFactor(frequencyZoomFactor), + m_frequencyZoomPos(frequencyZoomPos) + { } + }; + + enum AvgMode + { + AvgModeNone, + AvgModeMovingAvg, + AvgModeFixedAvg, + AvgModeMax + }; + + SpectrumVis(Real scalef); + virtual ~SpectrumVis(); + + void setGLSpectrum(GLSpectrumInterface* glSpectrum) { m_glSpectrum = glSpectrum; } + void setWorkspaceIndex(int index) { m_workspaceIndex = index; } + int getWorkspaceIndex() const { return m_workspaceIndex; } + + void setScalef(Real scalef); + void configureWSSpectrum(const QString& address, uint16_t port); + const SpectrumSettings& getSettings() const { return m_settings; } + Real getSpecMax() const { return m_specMax / m_powFFTDiv; } + void getPowerSpectrumCopy(std::vector& copy) { copy.assign(m_powerSpectrum.begin(), m_powerSpectrum.end()); } + void getPSDCopy(std::vector& copy) const { copy.assign(m_psd.begin(), m_psd.begin() + m_settings.m_fftSize); } + void getZoomedPSDCopy(std::vector& copy) const; + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); + void feed(const ComplexVector::const_iterator& begin, const ComplexVector::const_iterator& end, bool positiveOnly); + virtual void feed(const Complex *begin, unsigned int length); //!< direct FFT feed + void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly); + virtual void start(); + virtual void stop(); + virtual void pushMessage(Message *msg); + virtual QString getSinkName(); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + + void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } + + int webapiSpectrumSettingsGet(SWGSDRangel::SWGGLSpectrum& response, QString& errorMessage) const; + int webapiSpectrumSettingsPutPatch( + bool force, + const QStringList& spectrumSettingsKeys, + SWGSDRangel::SWGGLSpectrum& response, // query + response + QString& errorMessage); + int webapiSpectrumServerGet(SWGSDRangel::SWGSpectrumServer& response, QString& errorMessage) const; + int webapiSpectrumServerPost(SWGSDRangel::SWGSuccessResponse& response, QString& errorMessage); + int webapiSpectrumServerDelete(SWGSDRangel::SWGSuccessResponse& response, QString& errorMessage); + +private: + class MsgConfigureScalingFactor : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + MsgConfigureScalingFactor(Real scalef) : + Message(), + m_scalef(scalef) + {} + + Real getScalef() const { return m_scalef; } + + private: + Real m_scalef; + }; + + class MsgConfigureWSpectrum : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + MsgConfigureWSpectrum(const QString& address, uint16_t port) : + Message(), + m_address(address), + m_port(port) + {} + + const QString& getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + private: + QString m_address; + uint16_t m_port; + }; + + bool m_running; + FFTEngine* m_fft; + FFTWindow m_window; + unsigned int m_fftEngineSequence; + int m_workspaceIndex; + + std::vector m_fftBuffer; + std::vector m_powerSpectrum; //!< displayable power spectrum + std::vector m_psd; //!< real PSD + + SpectrumSettings m_settings; + int m_overlapSize; + int m_refillSize; + int m_fftBufferFill; + bool m_needMoreSamples; + + float m_frequencyZoomFactor; + float m_frequencyZoomPos; + + Real m_scalef; + GLSpectrumInterface* m_glSpectrum; + WSSpectrum m_wsSpectrum; + MovingAverage2D m_movingAverage; + FixedAverage2D m_fixedAverage; + Max2D m_max; + Real m_specMax; + + uint64_t m_centerFrequency; + int m_sampleRate; + + Real m_ofs; + Real m_powFFTDiv; + static const Real m_mult; + + MessageQueue m_inputMessageQueue; + MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI + + QRecursiveMutex m_mutex; + + void processFFT(bool positiveOnly); + void setRunning(bool running) { m_running = running; } + void applySettings(const SpectrumSettings& settings, bool force = false); + bool handleMessage(const Message& message); + void handleConfigureDSP(uint64_t centerFrequency, int sampleRate); + void handleScalef(Real scalef); + void handleWSOpenClose(bool openClose); + void handleConfigureWSSpectrum(const QString& address, uint16_t port); + float log2fapprox(float x) const; + + static void webapiFormatSpectrumSettings(SWGSDRangel::SWGGLSpectrum& response, const SpectrumSettings& settings); + static void webapiUpdateSpectrumSettings( + SpectrumSettings& settings, + const QStringList& spectrumSettingsKeys, + SWGSDRangel::SWGGLSpectrum& response); + +private slots: + void handleInputMessages(); +}; + +#endif // INCLUDE_SPECTRUMVIS_H diff --git a/android/app/src/main/cpp/dsp/upchannelizer.cpp b/android/app/src/main/cpp/dsp/upchannelizer.cpp new file mode 100644 index 0000000..7753956 --- /dev/null +++ b/android/app/src/main/cpp/dsp/upchannelizer.cpp @@ -0,0 +1,351 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +#include "hbfilterchainconverter.h" +#include "upchannelizer.h" + +UpChannelizer::UpChannelizer(ChannelSampleSource* sampleSource) : + m_filterChainSetMode(false), + m_sampleSource(sampleSource), + m_basebandSampleRate(0), + m_requestedInputSampleRate(0), + m_requestedCenterFrequency(0), + m_channelSampleRate(0), + m_channelFrequencyOffset(0), + m_log2Interp(0), + m_filterChainHash(0) +{ +} + +UpChannelizer::~UpChannelizer() +{ + freeFilterChain(); +} + +void UpChannelizer::pullOne(Sample& sample) +{ + if (m_sampleSource == nullptr) + { + m_sampleBuffer.clear(); + return; + } + + unsigned int log2Interp = m_filterStages.size(); + + if (log2Interp == 0) // optimization when no downsampling is done anyway + { + m_sampleSource->pullOne(sample); + } + else + { + FilterStages::iterator stage = m_filterStages.begin(); + std::vector::iterator stageSample = m_stageSamples.begin(); + + for (; stage != m_filterStages.end(); ++stage, ++stageSample) + { + if(stage == m_filterStages.end() - 1) + { + if ((*stage)->work(&m_sampleIn, &(*stageSample))) + { + m_sampleSource->pullOne(m_sampleIn); // get new input sample + } + } + else + { + if (!(*stage)->work(&(*(stageSample+1)), &(*stageSample))) + { + break; + } + } + } + + sample = *m_stageSamples.begin(); + } +} + +void UpChannelizer::pull(SampleVector::iterator begin, unsigned int nbSamples) +{ + if (m_sampleSource == nullptr) + { + m_sampleBuffer.clear(); + return; + } + + unsigned int log2Interp = m_filterStages.size(); + + if (log2Interp == 0) // optimization when no downsampling is done anyway + { + m_sampleSource->pull(begin, nbSamples); + } + else + { + std::for_each( + begin, + begin + nbSamples, + [this](Sample& s) { + pullOne(s); + } + ); + } +} + +void UpChannelizer::prefetch(unsigned int nbSamples) +{ + unsigned int log2Interp = m_filterStages.size(); + m_sampleSource->prefetch(nbSamples/(1< stageIndexes; + m_channelFrequencyOffset = m_basebandSampleRate * HBFilterChainConverter::convertToIndexes(m_log2Interp, m_filterChainHash, stageIndexes); + m_requestedCenterFrequency = m_channelFrequencyOffset; + + freeFilterChain(); + + m_channelFrequencyOffset = m_basebandSampleRate * setFilterChain(stageIndexes); + m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size()); + m_requestedInputSampleRate = m_channelSampleRate; + + qDebug() << "UpChannelizer::applyInterpolation:" + << " m_log2Interp:" << m_log2Interp + << " m_filterChainHash:" << m_filterChainHash + << " out:" << m_basebandSampleRate + << " in:" << m_channelSampleRate + << " fc:" << m_channelFrequencyOffset; +} + +#ifdef USE_SSE4_1 +UpChannelizer::FilterStage::FilterStage(Mode mode) : + m_filter(new IntHalfbandFilterEO1), + m_workFunction(0) +{ + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterEO1::workInterpolateCenter; + break; + + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterEO1::workInterpolateLowerHalf; + break; + + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterEO1::workInterpolateUpperHalf; + break; + } +} +#else +UpChannelizer::FilterStage::FilterStage(Mode mode) : + m_filter(new IntHalfbandFilterDB), + m_workFunction(0) +{ + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterDB::workInterpolateCenter; + break; + + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterDB::workInterpolateLowerHalf; + break; + + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterDB::workInterpolateUpperHalf; + break; + } +} +#endif + +UpChannelizer::FilterStage::~FilterStage() +{ + delete m_filter; +} + +Real UpChannelizer::channelMinSpace(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) +{ + Real leftSpace = chanStart - sigStart; + Real rightSpace = sigEnd - chanEnd; + return std::min(leftSpace, rightSpace); +} + +Real UpChannelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) +{ + Real sigBw = sigEnd - sigStart; + Real chanBw = chanEnd - chanStart; + Real rot = sigBw / 4; + Sample s; + + std::array filterMinSpaces; // Array of left, center and right filter min spaces respectively + filterMinSpaces[0] = channelMinSpace(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd); + filterMinSpaces[1] = channelMinSpace(sigStart + rot, sigEnd - rot, chanStart, chanEnd); + filterMinSpaces[2] = channelMinSpace(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd); + auto maxIt = std::max_element(filterMinSpaces.begin(), filterMinSpaces.end()); + int maxIndex = maxIt - filterMinSpaces.begin(); + Real maxValue = *maxIt; + + qDebug("UpChannelizer::createFilterChain: Signal [%.1f, %.1f] (BW %.1f) Channel [%.1f, %.1f] (BW %.1f) Selected: %d (fit %.1f)", + sigStart, sigEnd, sigBw, chanStart, chanEnd, chanBw, maxIndex, maxValue); + + if ((sigStart < sigEnd) && (chanStart < chanEnd) && (maxValue >= chanBw/8.0)) + { + if (maxIndex == 0) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); + m_stageSamples.push_back(s); + return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd); + } + + if (maxIndex == 1) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); + m_stageSamples.push_back(s); + return createFilterChain(sigStart + rot, sigEnd - rot, chanStart, chanEnd); + } + + if (maxIndex == 2) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); + m_stageSamples.push_back(s); + return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd); + } + } + + Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart); + + qDebug() << "UpChannelizer::createFilterChain: complete:" + << " #stages: " << m_filterStages.size() + << " BW: " << sigBw + << " ofs: " << ofs; + + return ofs; +} + +double UpChannelizer::setFilterChain(const std::vector& stageIndexes) +{ + // filters are described from lower to upper level but the chain is constructed the other way round + std::vector::const_reverse_iterator rit = stageIndexes.rbegin(); + double ofs = 0.0, ofs_stage = 0.25; + Sample s; + + // Each index is a base 3 number with 0 = low, 1 = center, 2 = high + // Functions at upper level will convert a number to base 3 to describe the filter chain. Common converting + // algorithms will go from LSD to MSD. This explains the reverse order. + for (; rit != stageIndexes.rend(); ++rit) + { + if (*rit == 0) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); + m_stageSamples.push_back(s); + ofs -= ofs_stage; + qDebug("UpChannelizer::setFilterChain: lower half: ofs: %f", ofs); + } + else if (*rit == 1) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); + m_stageSamples.push_back(s); + qDebug("UpChannelizer::setFilterChain: center: ofs: %f", ofs); + } + else if (*rit == 2) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); + m_stageSamples.push_back(s); + ofs += ofs_stage; + qDebug("UpChannelizer::setFilterChain: upper half: ofs: %f", ofs); + } + + ofs_stage /= 2; + } + + qDebug() << "UpChannelizer::setFilterChain: complete:" + << " #stages: " << m_filterStages.size() + << " ofs: " << ofs; + + return ofs; +} + +void UpChannelizer::freeFilterChain() +{ + for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it) + delete *it; + m_filterStages.clear(); + m_stageSamples.clear(); +} + + + + diff --git a/android/app/src/main/cpp/dsp/upchannelizer.h b/android/app/src/main/cpp/dsp/upchannelizer.h new file mode 100644 index 0000000..d637a15 --- /dev/null +++ b/android/app/src/main/cpp/dsp/upchannelizer.h @@ -0,0 +1,102 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019, 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_UPCHANNELIZER_H_ +#define SDRBASE_DSP_UPCHANNELIZER_H_ + +#include +#include + +#include "export.h" + +#include "channelsamplesource.h" + +#ifdef USE_SSE4_1 +#include "dsp/inthalfbandfiltereo1.h" +#else +#include "dsp/inthalfbandfilterdb.h" +#endif + +#define UPCHANNELIZER_HB_FILTER_ORDER 96 + +class SDRBASE_API UpChannelizer : public ChannelSampleSource { +public: + UpChannelizer(ChannelSampleSource* sampleSource); + virtual ~UpChannelizer(); + + virtual void pull(SampleVector::iterator begin, unsigned int nbSamples); + virtual void pullOne(Sample& sample); + virtual void prefetch(unsigned int nbSamples); + + void setInterpolation(unsigned int log2Interp, unsigned int filterChainHash); //!< Define channelizer with interpolation factor and filter chain definition + void setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency); //!< Define channelizer with requested sample rate and center frequency (shift in the baseband) + void setBasebandSampleRate(int basebandSampleRate, bool interp = false); //!< interp: true => use direct interpolation false => use channel configuration + int getChannelSampleRate() const { return m_channelSampleRate; }; + int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; } + +protected: + struct FilterStage { + enum Mode { + ModeCenter, + ModeLowerHalf, + ModeUpperHalf + }; + +#ifdef USE_SSE4_1 + typedef bool (IntHalfbandFilterEO1::*WorkFunction)(Sample* sIn, Sample *sOut); + IntHalfbandFilterEO1* m_filter; +#else + typedef bool (IntHalfbandFilterDB::*WorkFunction)(Sample* sIn, Sample *sOut); + IntHalfbandFilterDB* m_filter; +#endif + WorkFunction m_workFunction; + + FilterStage(Mode mode); + ~FilterStage(); + + bool work(Sample* sampleIn, Sample *sampleOut) { + return (m_filter->*m_workFunction)(sampleIn, sampleOut); + } + }; + + typedef std::vector FilterStages; + FilterStages m_filterStages; + bool m_filterChainSetMode; + std::vector m_stageSamples; + ChannelSampleSource* m_sampleSource; //!< Modulator + int m_basebandSampleRate; + int m_requestedInputSampleRate; + int m_requestedCenterFrequency; + int m_channelSampleRate; + int m_channelFrequencyOffset; + unsigned int m_log2Interp; + unsigned int m_filterChainHash; + SampleVector m_sampleBuffer; + Sample m_sampleIn; + + void applyChannelization(); + void applyInterpolation(); + static Real channelMinSpace(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd); + Real createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd); + double setFilterChain(const std::vector& stageIndexes); //!< returns offset in ratio of sample rate + void freeFilterChain(); +}; + + + + +#endif // SDRBASE_DSP_UPCHANNELIZER_H_ diff --git a/android/app/src/main/cpp/dsp/vkfftengine.cpp b/android/app/src/main/cpp/dsp/vkfftengine.cpp new file mode 100644 index 0000000..d3f97cf --- /dev/null +++ b/android/app/src/main/cpp/dsp/vkfftengine.cpp @@ -0,0 +1,171 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/vkfftengine.h" + +QMutex vkFFTEngine::m_globalPlanMutex; + +vkFFTEngine::vkFFTEngine() : + m_currentPlan(nullptr), + m_reuse(true) +{ + vkGPU = new VkGPU(); + memset(vkGPU, 0, sizeof(VkGPU)); + vkGPU->device_id = 0; // Could be set in GUI to support multiple GPUs +} + +vkFFTEngine::~vkFFTEngine() +{ +} + +bool vkFFTEngine::isAvailable() +{ + return vkGPU != nullptr; +} + +void vkFFTEngine::configure(int n, bool inverse) +{ + if (m_reuse) + { + for (const auto plan : m_plans) + { + if ((plan->n == n) && (plan->m_inverse == inverse)) + { + m_currentPlan = plan; + return; + } + } + } + + m_currentPlan = gpuAllocatePlan(); + m_currentPlan->n = n; + + QElapsedTimer t; + t.start(); + m_globalPlanMutex.lock(); + + VkFFTResult resFFT; + + // Allocate and initialise plan + m_currentPlan->m_configuration = new VkFFTConfiguration(); + memset(m_currentPlan->m_configuration, 0, sizeof(VkFFTConfiguration)); + m_currentPlan->m_app = new VkFFTApplication(); + memset(m_currentPlan->m_app, 0, sizeof(VkFFTApplication)); + m_currentPlan->m_configuration->FFTdim = 1; + m_currentPlan->m_configuration->size[0] = n; + m_currentPlan->m_configuration->size[1] = 1; + m_currentPlan->m_configuration->size[2] = 1; + m_currentPlan->m_configuration->numberBatches = 1; + m_currentPlan->m_configuration->performR2C = false; + m_currentPlan->m_configuration->performDCT = false; + m_currentPlan->m_configuration->doublePrecision = false; + m_currentPlan->m_configuration->halfPrecision = false; + m_currentPlan->m_bufferSize = sizeof(float) * 2 * n; + m_currentPlan->m_inverse = inverse; + + m_currentPlan->m_configuration->device = &vkGPU->device; +#if(VKFFT_BACKEND==0) + m_currentPlan->m_configuration->queue = &vkGPU->queue; + m_currentPlan->m_configuration->fence = &vkGPU->fence; + m_currentPlan->m_configuration->commandPool = &vkGPU->commandPool; + m_currentPlan->m_configuration->physicalDevice = &vkGPU->physicalDevice; + m_currentPlan->m_configuration->isCompilerInitialized = true; +#endif + m_currentPlan->m_configuration->bufferSize = &m_currentPlan->m_bufferSize; + + resFFT = gpuAllocateBuffers(); + if (resFFT != VKFFT_SUCCESS) + { + qDebug() << "vkFFTEngine::configure: gpuAllocateBuffers failed:" << getVkFFTErrorString(resFFT); + m_globalPlanMutex.unlock(); + delete m_currentPlan; + m_currentPlan = nullptr; + return; + } + + m_currentPlan->m_configuration->bufferSize = &m_currentPlan->m_bufferSize; + + resFFT = initializeVkFFT(m_currentPlan->m_app, *m_currentPlan->m_configuration); + if (resFFT != VKFFT_SUCCESS) + { + qDebug() << "vkFFTEngine::configure: initializeVkFFT failed:" << getVkFFTErrorString(resFFT); + m_globalPlanMutex.unlock(); + delete m_currentPlan; + m_currentPlan = nullptr; + return; + } + + resFFT = gpuConfigure(); + if (resFFT != VKFFT_SUCCESS) + { + qDebug() << "vkFFTEngine::configure: gpuConfigure failed:" << getVkFFTErrorString(resFFT); + m_globalPlanMutex.unlock(); + delete m_currentPlan; + m_currentPlan = nullptr; + return; + } + + m_globalPlanMutex.unlock(); + + qDebug("FFT: creating vkFFT plan (n=%d,%s) took %lld ms", n, inverse ? "inverse" : "forward", t.elapsed()); + m_plans.push_back(m_currentPlan); +} + +Complex* vkFFTEngine::in() +{ + if (m_currentPlan != nullptr) { + return m_currentPlan->m_in; + } else { + return nullptr; + } +} + +Complex* vkFFTEngine::out() +{ + if (m_currentPlan != nullptr) { + return m_currentPlan->m_out; + } else { + return nullptr; + } +} + +void vkFFTEngine::freeAll() +{ + for (auto plan : m_plans) + { + gpuDeallocatePlan(plan); + delete plan; + } + m_plans.clear(); +} + +vkFFTEngine::Plan::Plan() : + m_configuration(nullptr), + m_app(nullptr) +{ +} + +vkFFTEngine::Plan::~Plan() +{ + if (m_app) { + deleteVkFFT(m_app); + } + delete m_configuration; +} diff --git a/android/app/src/main/cpp/dsp/vkfftengine.h b/android/app/src/main/cpp/dsp/vkfftengine.h new file mode 100644 index 0000000..eafb46c --- /dev/null +++ b/android/app/src/main/cpp/dsp/vkfftengine.h @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_VKFFTENGINE_H +#define INCLUDE_VKFFTENGINE_H + +#include + +#include + +#include "dsp/fftengine.h" +#include "dsp/vkfftutils.h" +#include "export.h" + +class SDRBASE_API vkFFTEngine : public FFTEngine { +public: + vkFFTEngine(); + virtual ~vkFFTEngine(); + + virtual void configure(int n, bool inverse); + + virtual Complex* in(); + virtual Complex* out(); + + virtual void setReuse(bool reuse) { m_reuse = reuse; } + bool isAvailable() override; + +protected: + static QMutex m_globalPlanMutex; + + struct Plan { + Plan(); + virtual ~Plan(); + int n; + uint64_t m_bufferSize; + bool m_inverse; + VkFFTConfiguration* m_configuration; + VkFFTApplication* m_app; + Complex* m_in; // CPU memory + Complex* m_out; + }; + QList m_plans; + Plan* m_currentPlan; + bool m_reuse; + + VkGPU *vkGPU; + + virtual VkFFTResult gpuInit() = 0; + virtual VkFFTResult gpuAllocateBuffers() = 0; + virtual VkFFTResult gpuConfigure() = 0; + virtual Plan *gpuAllocatePlan() = 0; + virtual void gpuDeallocatePlan(Plan *plan) = 0; + + void freeAll(); +}; + +#endif // INCLUDE_VKFFTENGINE_H diff --git a/android/app/src/main/cpp/dsp/vkfftutils.cpp b/android/app/src/main/cpp/dsp/vkfftutils.cpp new file mode 100644 index 0000000..706c0e7 --- /dev/null +++ b/android/app/src/main/cpp/dsp/vkfftutils.cpp @@ -0,0 +1,343 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +// Selected code from https://github.com/DTolm/VkFFT/blob/master/benchmark_scripts/vkFFT_scripts/src/utils_VkFFT.cpp +// Formatting kept the same as source, to allow easier future merges + +#include "vkfftutils.h" + +#if(VKFFT_BACKEND==0) +#include "vulkan/vulkan.h" +#include "glslang_c_interface.h" +#endif + + +#if(VKFFT_BACKEND==0) + +VkResult CreateDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + //pointer to the function, as it is not part of the core. Function creates debugging messenger + PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(vkGPU->instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != NULL) { + return func(vkGPU->instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} +void DestroyDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkAllocationCallbacks* pAllocator) { + //pointer to the function, as it is not part of the core. Function destroys debugging messenger + PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(vkGPU->instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != NULL) { + func(vkGPU->instance, vkGPU->debugMessenger, pAllocator); + } +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + printf("validation layer: %s\n", pCallbackData->pMessage); + return VK_FALSE; +} + +VkResult setupDebugMessenger(VkGPU* vkGPU) { + //function that sets up the debugging messenger + if (vkGPU->enableValidationLayers == 0) return VK_SUCCESS; + + VkDebugUtilsMessengerCreateInfoEXT createInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + + if (CreateDebugUtilsMessengerEXT(vkGPU, &createInfo, NULL, &vkGPU->debugMessenger) != VK_SUCCESS) { + return VK_ERROR_INITIALIZATION_FAILED; + } + return VK_SUCCESS; +} + +VkResult checkValidationLayerSupport() { + //check if validation layers are supported when an instance is created + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, NULL); + + VkLayerProperties* availableLayers = (VkLayerProperties*)malloc(sizeof(VkLayerProperties) * layerCount); + if (!availableLayers) return VK_INCOMPLETE; + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers); + if (availableLayers) { + for (uint64_t i = 0; i < layerCount; i++) { + if (strcmp("VK_LAYER_KHRONOS_validation", availableLayers[i].layerName) == 0) { + free(availableLayers); + return VK_SUCCESS; + } + } + free(availableLayers); + } + else { + return VK_INCOMPLETE; + } + return VK_ERROR_LAYER_NOT_PRESENT; +} + +std::vector getRequiredExtensions(VkGPU* vkGPU, uint64_t sample_id) { + std::vector extensions; + + if (vkGPU->enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + switch (sample_id) { +#if (VK_API_VERSION>10) + case 2: case 102: + extensions.push_back("VK_KHR_get_physical_device_properties2"); + break; +#endif + default: + break; + } + + + return extensions; +} + +VkResult createInstance(VkGPU* vkGPU, uint64_t sample_id) { + //create instance - a connection between the application and the Vulkan library + VkResult res = VK_SUCCESS; + //check if validation layers are supported + if (vkGPU->enableValidationLayers == 1) { + res = checkValidationLayerSupport(); + if (res != VK_SUCCESS) return res; + } + + VkApplicationInfo applicationInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + applicationInfo.pApplicationName = "VkFFT"; + applicationInfo.applicationVersion = (uint32_t)VkFFTGetVersion(); + applicationInfo.pEngineName = "VkFFT"; + applicationInfo.engineVersion = 1; +#if (VK_API_VERSION>=12) + applicationInfo.apiVersion = VK_API_VERSION_1_2; +#elif (VK_API_VERSION==11) + applicationInfo.apiVersion = VK_API_VERSION_1_1; +#else + applicationInfo.apiVersion = VK_API_VERSION_1_0; +#endif + + VkInstanceCreateInfo createInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + createInfo.flags = 0; + createInfo.pApplicationInfo = &applicationInfo; + + auto extensions = getRequiredExtensions(vkGPU, sample_id); + createInfo.enabledExtensionCount = (uint32_t)(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; + + if (vkGPU->enableValidationLayers) { + //query for the validation layer support in the instance + createInfo.enabledLayerCount = 1; + const char* validationLayers = "VK_LAYER_KHRONOS_validation"; + createInfo.ppEnabledLayerNames = &validationLayers; + debugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + debugCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + debugCreateInfo.pfnUserCallback = debugCallback; + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + res = vkCreateInstance(&createInfo, NULL, &vkGPU->instance); + if (res != VK_SUCCESS) { + return res; + + } + return res; +} + +VkResult findPhysicalDevice(VkGPU* vkGPU) { + //check if there are GPUs that support Vulkan and select one + VkResult res = VK_SUCCESS; + uint32_t deviceCount; + res = vkEnumeratePhysicalDevices(vkGPU->instance, &deviceCount, NULL); + if (res != VK_SUCCESS) return res; + if (deviceCount == 0) { + return VK_ERROR_DEVICE_LOST; + } + + VkPhysicalDevice* devices = (VkPhysicalDevice*)malloc(sizeof(VkPhysicalDevice) * deviceCount); + if (!devices) return VK_INCOMPLETE; + res = vkEnumeratePhysicalDevices(vkGPU->instance, &deviceCount, devices); + if (res != VK_SUCCESS) return res; + if (devices) { + vkGPU->physicalDevice = devices[vkGPU->device_id]; + free(devices); + return VK_SUCCESS; + } + else + return VK_INCOMPLETE; +} +VkResult getComputeQueueFamilyIndex(VkGPU* vkGPU) { + //find a queue family for a selected GPU, select the first available for use + uint32_t queueFamilyCount; + vkGetPhysicalDeviceQueueFamilyProperties(vkGPU->physicalDevice, &queueFamilyCount, NULL); + + VkQueueFamilyProperties* queueFamilies = (VkQueueFamilyProperties*)malloc(sizeof(VkQueueFamilyProperties) * queueFamilyCount); + if (!queueFamilies) return VK_INCOMPLETE; + if (queueFamilies) { + vkGetPhysicalDeviceQueueFamilyProperties(vkGPU->physicalDevice, &queueFamilyCount, queueFamilies); + uint64_t i = 0; + for (; i < queueFamilyCount; i++) { + VkQueueFamilyProperties props = queueFamilies[i]; + + if (props.queueCount > 0 && (props.queueFlags & VK_QUEUE_COMPUTE_BIT)) { + break; + } + } + free(queueFamilies); + if (i == queueFamilyCount) { + return VK_ERROR_INITIALIZATION_FAILED; + } + vkGPU->queueFamilyIndex = i; + return VK_SUCCESS; + } + else + return VK_INCOMPLETE; +} + +VkResult createDevice(VkGPU* vkGPU, uint64_t sample_id) { + //create logical device representation + VkResult res = VK_SUCCESS; + VkDeviceQueueCreateInfo queueCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + res = getComputeQueueFamilyIndex(vkGPU); + if (res != VK_SUCCESS) return res; + queueCreateInfo.queueFamilyIndex = (uint32_t)vkGPU->queueFamilyIndex; + queueCreateInfo.queueCount = 1; + float queuePriorities = 1.0; + queueCreateInfo.pQueuePriorities = &queuePriorities; + VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + VkPhysicalDeviceFeatures deviceFeatures = {}; + switch (sample_id) { + case 1: case 12: case 17: case 18: case 101: case 201: case 1001: { + deviceFeatures.shaderFloat64 = true; + deviceCreateInfo.enabledExtensionCount = (uint32_t)vkGPU->enabledDeviceExtensions.size(); + deviceCreateInfo.ppEnabledExtensionNames = vkGPU->enabledDeviceExtensions.data(); + deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; + deviceCreateInfo.queueCreateInfoCount = 1; + deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + res = vkCreateDevice(vkGPU->physicalDevice, &deviceCreateInfo, NULL, &vkGPU->device); + if (res != VK_SUCCESS) return res; + vkGetDeviceQueue(vkGPU->device, (uint32_t)vkGPU->queueFamilyIndex, 0, &vkGPU->queue); + break; + } +#if (VK_API_VERSION>10) + case 2: case 102: { + VkPhysicalDeviceFeatures2 deviceFeatures2 = {}; + VkPhysicalDevice16BitStorageFeatures shaderFloat16 = {}; + shaderFloat16.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES; + shaderFloat16.storageBuffer16BitAccess = true; + /*VkPhysicalDeviceShaderFloat16Int8Features shaderFloat16 = {}; + shaderFloat16.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES; + shaderFloat16.shaderFloat16 = true; + shaderFloat16.shaderInt8 = true;*/ + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderFloat16; + deviceFeatures2.features = deviceFeatures; + vkGetPhysicalDeviceFeatures2(vkGPU->physicalDevice, &deviceFeatures2); + deviceCreateInfo.pNext = &deviceFeatures2; + vkGPU->enabledDeviceExtensions.push_back("VK_KHR_16bit_storage"); + deviceCreateInfo.enabledExtensionCount = (uint32_t)vkGPU->enabledDeviceExtensions.size(); + deviceCreateInfo.ppEnabledExtensionNames = vkGPU->enabledDeviceExtensions.data(); + deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; + deviceCreateInfo.queueCreateInfoCount = 1; + deviceCreateInfo.pEnabledFeatures = NULL; + res = vkCreateDevice(vkGPU->physicalDevice, &deviceCreateInfo, NULL, &vkGPU->device); + if (res != VK_SUCCESS) return res; + vkGetDeviceQueue(vkGPU->device, (uint32_t)vkGPU->queueFamilyIndex, 0, &vkGPU->queue); + break; + } +#endif + default: { + deviceCreateInfo.enabledExtensionCount = (uint32_t)vkGPU->enabledDeviceExtensions.size(); + deviceCreateInfo.ppEnabledExtensionNames = vkGPU->enabledDeviceExtensions.data(); + deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; + deviceCreateInfo.queueCreateInfoCount = 1; + deviceCreateInfo.pEnabledFeatures = NULL; + deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + res = vkCreateDevice(vkGPU->physicalDevice, &deviceCreateInfo, NULL, &vkGPU->device); + if (res != VK_SUCCESS) return res; + vkGetDeviceQueue(vkGPU->device, (uint32_t)vkGPU->queueFamilyIndex, 0, &vkGPU->queue); + break; + } + } + return res; +} +VkResult createFence(VkGPU* vkGPU) { + //create fence for synchronization + VkResult res = VK_SUCCESS; + VkFenceCreateInfo fenceCreateInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + fenceCreateInfo.flags = 0; + res = vkCreateFence(vkGPU->device, &fenceCreateInfo, NULL, &vkGPU->fence); + return res; +} +VkResult createCommandPool(VkGPU* vkGPU) { + //create a place, command buffer memory is allocated from + VkResult res = VK_SUCCESS; + VkCommandPoolCreateInfo commandPoolCreateInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + commandPoolCreateInfo.queueFamilyIndex = (uint32_t)vkGPU->queueFamilyIndex; + res = vkCreateCommandPool(vkGPU->device, &commandPoolCreateInfo, NULL, &vkGPU->commandPool); + return res; +} + +VkFFTResult findMemoryType(VkGPU* vkGPU, uint64_t memoryTypeBits, uint64_t memorySize, VkMemoryPropertyFlags properties, uint32_t* memoryTypeIndex) { + VkPhysicalDeviceMemoryProperties memoryProperties = { 0 }; + + vkGetPhysicalDeviceMemoryProperties(vkGPU->physicalDevice, &memoryProperties); + + for (uint64_t i = 0; i < memoryProperties.memoryTypeCount; ++i) { + if ((memoryTypeBits & ((uint64_t)1 << i)) && ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) && (memoryProperties.memoryHeaps[memoryProperties.memoryTypes[i].heapIndex].size >= memorySize)) + { + memoryTypeIndex[0] = (uint32_t)i; + return VKFFT_SUCCESS; + } + } + return VKFFT_ERROR_FAILED_TO_FIND_MEMORY; +} + +VkFFTResult allocateBuffer(VkGPU* vkGPU, VkBuffer* buffer, VkDeviceMemory* deviceMemory, VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags propertyFlags, uint64_t size) { + //allocate the buffer used by the GPU with specified properties + VkFFTResult resFFT = VKFFT_SUCCESS; + VkResult res = VK_SUCCESS; + uint32_t queueFamilyIndices; + VkBufferCreateInfo bufferCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferCreateInfo.queueFamilyIndexCount = 1; + bufferCreateInfo.pQueueFamilyIndices = &queueFamilyIndices; + bufferCreateInfo.size = size; + bufferCreateInfo.usage = usageFlags; + res = vkCreateBuffer(vkGPU->device, &bufferCreateInfo, NULL, buffer); + if (res != VK_SUCCESS) return VKFFT_ERROR_FAILED_TO_CREATE_BUFFER; + VkMemoryRequirements memoryRequirements = { 0 }; + vkGetBufferMemoryRequirements(vkGPU->device, buffer[0], &memoryRequirements); + VkMemoryAllocateInfo memoryAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + memoryAllocateInfo.allocationSize = memoryRequirements.size; + resFFT = findMemoryType(vkGPU, memoryRequirements.memoryTypeBits, memoryRequirements.size, propertyFlags, &memoryAllocateInfo.memoryTypeIndex); + if (resFFT != VKFFT_SUCCESS) return resFFT; + res = vkAllocateMemory(vkGPU->device, &memoryAllocateInfo, NULL, deviceMemory); + if (res != VK_SUCCESS) return VKFFT_ERROR_FAILED_TO_ALLOCATE_MEMORY; + res = vkBindBufferMemory(vkGPU->device, buffer[0], deviceMemory[0], 0); + if (res != VK_SUCCESS) return VKFFT_ERROR_FAILED_TO_BIND_BUFFER_MEMORY; + return resFFT; +} +#endif + diff --git a/android/app/src/main/cpp/dsp/vkfftutils.h b/android/app/src/main/cpp/dsp/vkfftutils.h new file mode 100644 index 0000000..a0eac31 --- /dev/null +++ b/android/app/src/main/cpp/dsp/vkfftutils.h @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +// Selected code from https://github.com/DTolm/VkFFT/blob/master/benchmark_scripts/vkFFT_scripts/include/utils_VkFFT.h + +#ifndef VKFFT_UTILS_H +#define VKFFT_UTILS_H + +#include + +#include + +typedef struct { +#if(VKFFT_BACKEND==0) + VkInstance instance; + VkPhysicalDevice physicalDevice; + VkPhysicalDeviceProperties physicalDeviceProperties; + VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties; + VkDevice device; + VkDebugUtilsMessengerEXT debugMessenger; + uint64_t queueFamilyIndex; + VkQueue queue; + VkCommandPool commandPool; + VkFence fence; + std::vector enabledDeviceExtensions; + uint64_t enableValidationLayers; +#elif(VKFFT_BACKEND==1) + CUdevice device; + CUcontext context; +#endif + uint64_t device_id; +} VkGPU; + +#if(VKFFT_BACKEND==0) +VkResult CreateDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger); +void DestroyDebugUtilsMessengerEXT(VkGPU* vkGPU, const VkAllocationCallbacks* pAllocator); +VkResult setupDebugMessenger(VkGPU* vkGPU); +VkResult checkValidationLayerSupport(); +std::vector getRequiredExtensions(VkGPU* vkGPU, uint64_t sample_id); +VkResult createInstance(VkGPU* vkGPU, uint64_t sample_id); +VkResult findPhysicalDevice(VkGPU* vkGPU); +VkResult getComputeQueueFamilyIndex(VkGPU* vkGPU); +VkResult createDevice(VkGPU* vkGPU, uint64_t sample_id); +VkResult createFence(VkGPU* vkGPU); +VkResult createCommandPool(VkGPU* vkGPU); +VkFFTResult findMemoryType(VkGPU* vkGPU, uint64_t memoryTypeBits, uint64_t memorySize, VkMemoryPropertyFlags properties, uint32_t* memoryTypeIndex); +VkFFTResult allocateBuffer(VkGPU* vkGPU, VkBuffer* buffer, VkDeviceMemory* deviceMemory, VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags propertyFlags, uint64_t size); +#endif + +#endif // VKFFT_UTILS_H diff --git a/android/app/src/main/cpp/dsp/vulkanvkfftengine.cpp b/android/app/src/main/cpp/dsp/vulkanvkfftengine.cpp new file mode 100644 index 0000000..97020c3 --- /dev/null +++ b/android/app/src/main/cpp/dsp/vulkanvkfftengine.cpp @@ -0,0 +1,345 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "glslang_c_interface.h" + +#include "dsp/vulkanvkfftengine.h" +#include "util/profiler.h" + +class GLSInitialiser { +public: + GLSInitialiser() { + glslang_initialize_process(); + }; + + ~GLSInitialiser() { + glslang_finalize_process(); + } +}; + +static GLSInitialiser glsInitialiser; + +VulkanvkFFTEngine::VulkanvkFFTEngine() +{ + VkFFTResult resFFT; + resFFT = gpuInit(); + if (resFFT != VKFFT_SUCCESS) + { + qDebug() << "VulkanvkFFTEngine::VulkanvkFFTEngine: Failed to initialise GPU:" << getVkFFTErrorString(resFFT); + delete vkGPU; + vkGPU = nullptr; + } +} + +VulkanvkFFTEngine::~VulkanvkFFTEngine() +{ + if (vkGPU) + { + freeAll(); + vkDestroyFence(vkGPU->device, vkGPU->fence, nullptr); + vkDestroyCommandPool(vkGPU->device, vkGPU->commandPool, nullptr); + vkDestroyDevice(vkGPU->device, nullptr); + DestroyDebugUtilsMessengerEXT(vkGPU, nullptr); + vkDestroyInstance(vkGPU->instance, nullptr); + } +} + +const QString VulkanvkFFTEngine::m_name = "vkFFT (Vulkan)"; + +QString VulkanvkFFTEngine::getName() const +{ + return m_name; +} + +VkFFTResult VulkanvkFFTEngine::gpuInit() +{ + VkResult res = VK_SUCCESS; + + // To enable validation on Windows: + // set VK_LAYER_PATH=%VULKAN_SDK%\Bin + // set VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_api_dump;VK_LAYER_KHRONOS_validation + // https://vulkan.lunarg.com/doc/view/1.3.204.1/windows/layer_configuration.html + // Create vk_layer_settings.txt in working dir + // Or run vkconfig to do so + + // Create instance - a connection between the application and the Vulkan library + res = createInstance(vkGPU, 0); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_CREATE_INSTANCE; + } + // Set up the debugging messenger + res = setupDebugMessenger(vkGPU); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_SETUP_DEBUG_MESSENGER; + } + // Check if there are GPUs that support Vulkan and select one + res = findPhysicalDevice(vkGPU); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_FIND_PHYSICAL_DEVICE; + } + // Create logical device representation + res = createDevice(vkGPU, 0); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_CREATE_DEVICE; + } + // Create fence for synchronization + res = createFence(vkGPU); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_CREATE_FENCE; + } + // Create a place, command buffer memory is allocated from + res = createCommandPool(vkGPU); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_CREATE_COMMAND_POOL; + } + vkGetPhysicalDeviceProperties(vkGPU->physicalDevice, &vkGPU->physicalDeviceProperties); + vkGetPhysicalDeviceMemoryProperties(vkGPU->physicalDevice, &vkGPU->physicalDeviceMemoryProperties); + + return VKFFT_SUCCESS; +} + +VkFFTResult VulkanvkFFTEngine::gpuAllocateBuffers() +{ + VkFFTResult resFFT; + VulkanPlan *plan = reinterpret_cast(m_currentPlan); + + // Allocate GPU memory + resFFT = allocateBuffer(vkGPU, + &plan->m_buffer, + &plan->m_bufferDeviceMemory, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_HEAP_DEVICE_LOCAL_BIT, + plan->m_bufferSize); + if (resFFT != VKFFT_SUCCESS) { + return resFFT; + } + + // Allocate CPU/GPU memory (Requires m_currentPlan->m_buffer to have been created) + resFFT = vulkanAllocateIn(plan); + if (resFFT != VKFFT_SUCCESS) { + return resFFT; + } + resFFT = vulkanAllocateOut(plan); + if (resFFT != VKFFT_SUCCESS) { + return resFFT; + } + + plan->m_configuration->buffer = &plan->m_buffer; + + return VKFFT_SUCCESS; +} + +VkFFTResult VulkanvkFFTEngine::gpuConfigure() +{ + VkFFTResult resFFT; + VulkanPlan *plan = reinterpret_cast(m_currentPlan); + + // Allocate command buffer with command to perform FFT + resFFT = vulkanAllocateFFTCommand(plan); + if (resFFT != VKFFT_SUCCESS) { + return resFFT; + } + + return VKFFT_SUCCESS; +} + +// Allocate CPU to GPU memory buffer +VkFFTResult VulkanvkFFTEngine::vulkanAllocateIn(VulkanPlan *plan) +{ + VkFFTResult resFFT; + VkResult res = VK_SUCCESS; + VkBuffer* buffer = (VkBuffer*)&plan->m_buffer; + + resFFT = allocateBuffer(vkGPU, &plan->m_inBuffer, &plan->m_inMemory, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_currentPlan->m_bufferSize); + if (resFFT != VKFFT_SUCCESS) { + return resFFT; + } + + void* data; + res = vkMapMemory(vkGPU->device, plan->m_inMemory, 0, plan->m_bufferSize, 0, &data); + if (res != VK_SUCCESS) { + return VKFFT_ERROR_FAILED_TO_MAP_MEMORY; + } + plan->m_in = (Complex*) data; + + return VKFFT_SUCCESS; +} + +// Allocate GPU to CPU memory buffer +VkFFTResult VulkanvkFFTEngine::vulkanAllocateOut(VulkanPlan *plan) +{ + VkFFTResult resFFT; + VkResult res; + VkBuffer* buffer = (VkBuffer*)&plan->m_buffer; + + resFFT = allocateBuffer(vkGPU, &plan->m_outBuffer, &plan->m_outMemory, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_currentPlan->m_bufferSize); + if (resFFT != VKFFT_SUCCESS) { + return resFFT; + } + + void* data; + res = vkMapMemory(vkGPU->device, plan->m_outMemory, 0, plan->m_bufferSize, 0, &data); + if (res != VK_SUCCESS) { + return VKFFT_ERROR_FAILED_TO_MAP_MEMORY; + } + plan->m_out = (Complex*) data; + + return VKFFT_SUCCESS; +} + +void VulkanvkFFTEngine::vulkanDeallocateIn(VulkanPlan *plan) +{ + vkUnmapMemory(vkGPU->device, plan->m_inMemory); + vkDestroyBuffer(vkGPU->device, plan->m_inBuffer, nullptr); + vkFreeMemory(vkGPU->device, plan->m_inMemory, nullptr); + plan->m_in = nullptr; +} + +void VulkanvkFFTEngine::vulkanDeallocateOut(VulkanPlan *plan) +{ + vkUnmapMemory(vkGPU->device, plan->m_outMemory); + vkDestroyBuffer(vkGPU->device, plan->m_outBuffer, nullptr); + vkFreeMemory(vkGPU->device, plan->m_outMemory, nullptr); + plan->m_out = nullptr; +} + +VkFFTResult VulkanvkFFTEngine::vulkanAllocateFFTCommand(VulkanPlan *plan) +{ + VkFFTResult resFFT; + VkResult res = VK_SUCCESS; + VkCommandBufferAllocateInfo commandBufferAllocateInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + commandBufferAllocateInfo.commandPool = vkGPU->commandPool; + commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandBufferAllocateInfo.commandBufferCount = 1; + res = vkAllocateCommandBuffers(vkGPU->device, &commandBufferAllocateInfo, &plan->m_commandBuffer); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_ALLOCATE_COMMAND_BUFFERS; + } + VkCommandBufferBeginInfo commandBufferBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + commandBufferBeginInfo.flags = 0; + res = vkBeginCommandBuffer(plan->m_commandBuffer, &commandBufferBeginInfo); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_BEGIN_COMMAND_BUFFER; + } + + VkBuffer* buffer = (VkBuffer*)&plan->m_buffer; + + // Copy from CPU to GPU + VkBufferCopy copyRegionIn = { 0 }; + copyRegionIn.srcOffset = 0; + copyRegionIn.dstOffset = 0; + copyRegionIn.size = plan->m_bufferSize; + vkCmdCopyBuffer(plan->m_commandBuffer, plan->m_inBuffer, buffer[0], 1, ©RegionIn); + + // Wait for copy to complete + VkMemoryBarrier memoryBarrierIn = { + VK_STRUCTURE_TYPE_MEMORY_BARRIER, + 0, + VK_ACCESS_SHADER_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT, + }; + vkCmdPipelineBarrier( + plan->m_commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, + 1, + &memoryBarrierIn, + 0, 0, 0, 0); + + // Perform FFT + VkFFTLaunchParams launchParams = {}; + launchParams.commandBuffer = &plan->m_commandBuffer; + resFFT = VkFFTAppend(plan->m_app, plan->m_inverse, &launchParams); + if (resFFT != VKFFT_SUCCESS) { + return resFFT; + } + + // Wait for FFT to complete + VkMemoryBarrier memoryBarrierOut = { + VK_STRUCTURE_TYPE_MEMORY_BARRIER, + 0, + VK_ACCESS_SHADER_WRITE_BIT, + VK_ACCESS_HOST_READ_BIT, + }; + vkCmdPipelineBarrier( + plan->m_commandBuffer, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + 0, + 1, + &memoryBarrierIn, + 0, 0, 0, 0); + + // Copy from GPU to CPU + VkBufferCopy copyRegionOut = { 0 }; + copyRegionOut.srcOffset = 0; + copyRegionOut.dstOffset = 0; + copyRegionOut.size = plan->m_bufferSize; + vkCmdCopyBuffer(plan->m_commandBuffer, buffer[0], plan->m_outBuffer, 1, ©RegionOut); + + res = vkEndCommandBuffer(plan->m_commandBuffer); + if (res != 0) { + return VKFFT_ERROR_FAILED_TO_END_COMMAND_BUFFER; + } + return VKFFT_SUCCESS; +} + +void VulkanvkFFTEngine::transform() +{ + PROFILER_START() + + VkResult res = VK_SUCCESS; + VulkanPlan *plan = reinterpret_cast(m_currentPlan); + + VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &plan->m_commandBuffer; + res = vkQueueSubmit(vkGPU->queue, 1, &submitInfo, vkGPU->fence); + if (res != 0) { + qDebug() << "VulkanvkFFTEngine::transform: Failed to submit to queue"; + } + res = vkWaitForFences(vkGPU->device, 1, &vkGPU->fence, VK_TRUE, 100000000000); + if (res != 0) { + qDebug() << "VulkanvkFFTEngine::transform: Failed to wait for fences"; + } + res = vkResetFences(vkGPU->device, 1, &vkGPU->fence); + if (res != 0) { + qDebug() << "VulkanvkFFTEngine::transform: Failed to reset fences"; + } + + PROFILER_STOP(QString("%1 FFT %2").arg(getName()).arg(m_currentPlan->n)) +} + +vkFFTEngine::Plan *VulkanvkFFTEngine::gpuAllocatePlan() +{ + return new VulkanPlan(); +} + +void VulkanvkFFTEngine::gpuDeallocatePlan(Plan *p) +{ + VulkanPlan *plan = reinterpret_cast(p); + + vulkanDeallocateOut(plan); + vulkanDeallocateIn(plan); + + vkFreeCommandBuffers(vkGPU->device, vkGPU->commandPool, 1, &plan->m_commandBuffer); + vkDestroyBuffer(vkGPU->device, plan->m_buffer, nullptr); + vkFreeMemory(vkGPU->device, plan->m_bufferDeviceMemory, nullptr); +} diff --git a/android/app/src/main/cpp/dsp/vulkanvkfftengine.h b/android/app/src/main/cpp/dsp/vulkanvkfftengine.h new file mode 100644 index 0000000..37bada2 --- /dev/null +++ b/android/app/src/main/cpp/dsp/vulkanvkfftengine.h @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_VULKANVKFFTENGINE_H +#define INCLUDE_VULKANVKFFTENGINE_H + +#include "vkfftengine.h" + +#include "vulkan/vulkan.h" + +class SDRBASE_API VulkanvkFFTEngine : public vkFFTEngine { +public: + VulkanvkFFTEngine(); + virtual ~VulkanvkFFTEngine(); + + void transform() override; + QString getName() const override; + static const QString m_name; + +protected: + + struct VulkanPlan : Plan { + VkBuffer m_inBuffer; // CPU input memory + VkDeviceMemory m_inMemory; + VkBuffer m_outBuffer; // CPU output memory + VkDeviceMemory m_outMemory; + VkBuffer m_buffer; // GPU memory + VkDeviceMemory m_bufferDeviceMemory; + VkCommandBuffer m_commandBuffer; + }; + + VkFFTResult gpuInit() override; + VkFFTResult gpuAllocateBuffers() override; + VkFFTResult gpuConfigure() override; + + Plan *gpuAllocatePlan() override; + void gpuDeallocatePlan(Plan *) override; + + VkFFTResult vulkanAllocateOut(VulkanPlan *plan); + VkFFTResult vulkanAllocateIn(VulkanPlan *plan); + void vulkanDeallocateOut(VulkanPlan *plan); + void vulkanDeallocateIn(VulkanPlan *plan); + VkFFTResult vulkanAllocateFFTCommand(VulkanPlan *plan); + +}; + +#endif // INCLUDE_VULKANVKFFTENGINE_H diff --git a/android/app/src/main/cpp/dsp/wavfilerecord.cpp b/android/app/src/main/cpp/dsp/wavfilerecord.cpp new file mode 100644 index 0000000..a052b4c --- /dev/null +++ b/android/app/src/main/cpp/dsp/wavfilerecord.cpp @@ -0,0 +1,656 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// Copyright (C) 2022-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 CRD716 // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include + +#include "dsp/dspcommands.h" +#include "util/message.h" + +#include "wavfilerecord.h" + +WavFileRecord::WavFileRecord(quint32 sampleRate, quint64 centerFrequency) : + FileRecordInterface(), + m_fileBase("test"), + m_fileBaseIsFileName(false), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency), + m_recordOn(false), + m_recordStart(false), + m_byteCount(0), + m_msShift(0), + m_nbChannels(2) +{ + setObjectName("WavFileRecord"); +} + +WavFileRecord::WavFileRecord(const QString& fileBase) : + FileRecordInterface(), + m_fileBase(fileBase), + m_fileBaseIsFileName(false), + m_sampleRate(0), + m_centerFrequency(0), + m_recordOn(false), + m_recordStart(false), + m_byteCount(0), + m_nbChannels(2) +{ + setObjectName("WavFileRecord"); +} + +WavFileRecord::~WavFileRecord() +{ + stopRecording(); +} + +void WavFileRecord::setFileName(const QString& fileBase) +{ + if (!m_recordOn) + { + m_fileBase = fileBase; + } +} + +void WavFileRecord::genUniqueFileName(uint deviceUID, int istream) +{ + if (istream < 0) { + setFileName(QString("rec%1_%2.wav").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"))); + } else { + setFileName(QString("rec%1_%2_%3.wav").arg(deviceUID).arg(istream).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"))); + } +} + +void WavFileRecord::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) +{ + (void) positiveOnly; + + if (!m_recordOn) { + return; + } + + if (begin < end) // if there is something to put out + { + if (m_recordStart) + { + writeHeader(); + m_recordStart = false; + } + + if (SDR_RX_SAMP_SZ == 16) + { + m_sampleFile.write(reinterpret_cast(&*(begin)), (end - begin)*sizeof(Sample)); + m_byteCount += end - begin; + } + else + { + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + // Convert from 24-bit to 16-bit + int16_t samples[2]; + samples[0] = std::min(32767, std::max(it->real() >> 8, -32768)); + samples[1] = std::min(32767, std::max(it->imag() >> 8, -32768)); + m_sampleFile.write(reinterpret_cast(&samples), 4); + m_byteCount += 4; + } + } + } +} + +void WavFileRecord::write(qint16 lSample, qint16 rSample) +{ + if (m_recordStart) + { + writeHeader(); + m_recordStart = false; + } + + m_sampleFile.write(reinterpret_cast(&lSample), 2); + m_sampleFile.write(reinterpret_cast(&rSample), 2); + m_byteCount += 4; +} + +void WavFileRecord::writeMono(qint16 sample) +{ + if (m_recordStart) + { + writeHeader(); + m_recordStart = false; + } + + m_sampleFile.write(reinterpret_cast(&sample), 2); + m_byteCount += 2; +} + +void WavFileRecord::writeMono(qint16 *samples, int nbSamples) +{ + if (m_recordStart) + { + writeHeader(); + m_recordStart = false; + } + + m_sampleFile.write(reinterpret_cast(samples), 2*nbSamples); + m_byteCount += 2*nbSamples; +} + +void WavFileRecord::start() +{ +} + +void WavFileRecord::stop() +{ + stopRecording(); +} + +bool WavFileRecord::startRecording() +{ + if (m_recordOn) { + stopRecording(); + } + +#ifdef ANDROID + if (!m_sampleFile.isOpen()) +#else + if (!m_sampleFile.is_open()) +#endif + { + qDebug() << "WavFileRecord::startRecording"; +#ifdef ANDROID + // FIXME: No idea how to write to a file where the filename doesn't come from the file picker + m_currentFileName = m_fileBase + ".wav"; + m_sampleFile.setFileName(m_currentFileName); + if (!m_sampleFile.open(QIODevice::ReadWrite)) + { + qWarning() << "WavFileRecord::startRecording: failed to open file: " << m_currentFileName << " error " << m_sampleFile.error(); + return false; + } +#else + if (m_fileBaseIsFileName) { + m_currentFileName = m_fileBase + ".wav"; + } else { + m_currentFileName = m_fileBase + "_" + QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz") + ".wav"; // Don't use QString::arg on Android, as filename can contain %2 + } + m_sampleFile.open(m_currentFileName.toStdString().c_str(), std::ios::binary); + if (!m_sampleFile.is_open()) + { + qWarning() << "WavFileRecord::startRecording: failed to open file: " << m_currentFileName; + return false; + } +#endif + m_recordOn = true; + m_recordStart = true; + m_byteCount = 0; + } + return true; +} + +void addString(QByteArray& buffer, const QString& type, const QString text) +{ + buffer.append(type.toUtf8(), 4); + + QByteArray s = text.toUtf8(); + s.append('\0'); // Strings should be null terminated + if (s.size() & 1) { + s.append('\0'); + } + + quint16 textSize = s.size(); + buffer.append(textSize & 0xff); + buffer.append((textSize >> 8) & 0xff); + buffer.append((textSize >> 16) & 0xff); + buffer.append((textSize >> 24) & 0xff); + + buffer.append(s, textSize); +} + +void addTag(QByteArray& buffer, const QString& id, const QString text) +{ + buffer.append(id.toUtf8(), 4); + QByteArray s = text.toUtf8(); + int size = s.size() + 1; + buffer.append((size >> 24) & 0xff); // MSB first + buffer.append((size >> 16) & 0xff); + buffer.append((size >> 8) & 0xff); + buffer.append(size & 0xff); + + buffer.append((char)0); // flags + buffer.append((char)0); // flags + + buffer.append((char)0); // text encoding (0 ISO-8859-1, 1 Unicode) + buffer.append(s); +} + +void WavFileRecord::setMetaData(const QString& trackTitle, const QString& album, const QString& artist) +{ + m_trackTitle = trackTitle; + m_album = album; + m_artist = artist; +} + +bool WavFileRecord::hasMetaData() const +{ + return !m_trackTitle.isEmpty() || !m_album.isEmpty() || !m_artist.isEmpty(); +} + +void WavFileRecord::writeInfoList() +{ + QByteArray meta; + + meta.append("INFO", 4); + + if (!m_trackTitle.isEmpty()) { + addString(meta, "INAM", m_trackTitle); + } + if (!m_album.isEmpty()) { + addString(meta, "IPRD", m_album); + } + if (!m_artist.isEmpty()) { + addString(meta, "IART", m_artist); + } + + struct Chunk list; + list.m_id[0] = 'L'; + list.m_id[1] = 'I'; + list.m_id[2] = 'S'; + list.m_id[3] = 'T'; + list.m_size = meta.size(); + + m_sampleFile.write((char*)&list, sizeof(Chunk)); + m_sampleFile.write((char*)meta.data(), meta.size()); +} + +// ID3 v2.3 +// https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.3.0.html +void WavFileRecord::writeID3() +{ + QByteArray meta; + + QByteArray textInfo; + if (!m_artist.isEmpty()) { + addTag(textInfo, "TPE1", m_artist); + } + if (!m_trackTitle.isEmpty()) { + addTag(textInfo, "TIT2", m_trackTitle); + } + if (!m_album.isEmpty()) { + addTag(textInfo, "TALB", m_album); + } + + meta.append("ID3", 3); + meta.append((char)3); // .3 version + meta.append((char)0); // revision + meta.append((char)0); // flags + + int textInfoSize = textInfo.size(); + meta.append((textInfoSize >> 24) & 0xff); // MSB first + meta.append((textInfoSize >> 16) & 0xff); + meta.append((textInfoSize >> 8) & 0xff); + meta.append(textInfoSize & 0xff); + + meta.append(textInfo); + + if (meta.size() & 1) { + meta.append((char)0); + } + + struct Chunk id3; + id3.m_id[0] = 'i'; + id3.m_id[1] = 'd'; + id3.m_id[2] = '3'; + id3.m_id[3] = ' '; + id3.m_size = meta.size(); + + m_sampleFile.write((char*)&id3, sizeof(Chunk)); + m_sampleFile.write((char*)meta.data(), meta.size()); +} + +bool WavFileRecord::stopRecording() +{ +#ifdef ANDROID + if (m_sampleFile.isOpen()) +#else + if (m_sampleFile.is_open()) +#endif + { + qDebug() << "WavFileRecord::stopRecording"; + +#ifdef ANDROID + long dataSize = (long)m_sampleFile.size(); +#else + long dataSize = m_sampleFile.tellp(); +#endif + + // Write meta data + if (hasMetaData()) + { + writeInfoList(); + writeID3(); + } + + // Fix up chunk sizes +#ifdef ANDROID + long fileSize = (long)m_sampleFile.size(); + m_sampleFile.seek(offsetof(Header, m_riffHeader.m_size)); +#else + long fileSize = m_sampleFile.tellp(); + m_sampleFile.seekp(offsetof(Header, m_riffHeader.m_size)); +#endif + qint32 size = (fileSize - 8); + m_sampleFile.write((char *)&size, 4); +#ifdef ANDROID + m_sampleFile.seek(offsetof(Header, m_dataHeader.m_size)); +#else + m_sampleFile.seekp(offsetof(Header, m_dataHeader.m_size)); +#endif + size = dataSize - sizeof(Header); + m_sampleFile.write((char *)&size, 4); + m_sampleFile.close(); + m_recordOn = false; + m_recordStart = false; +#ifdef ANDROID +#else + if (m_sampleFile.bad()) + { + qWarning() << "WavFileRecord::stopRecording: an error occurred while writing to " << m_currentFileName; + return false; + } +#endif + } + return true; +} + +bool WavFileRecord::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) message; + + int sampleRate = notif.getSampleRate(); + if ((sampleRate != (int)m_sampleRate) && m_recordOn) { + qDebug() << "WavFileRecord::handleMessage: sample rate has changed. Creating a new .wav file"; + stopRecording(); + m_recordOn = true; + } + + m_sampleRate = sampleRate; + m_centerFrequency = notif.getCenterFrequency(); + qDebug() << "WavFileRecord::handleMessage: DSPSignalNotification: m_inputSampleRate: " << m_sampleRate + << " m_centerFrequency: " << m_centerFrequency; + + if (m_recordOn) { + startRecording(); + } + + return true; + } + else + { + return false; + } +} + +void WavFileRecord::writeHeader() +{ + Header header; + header.m_riffHeader.m_id[0] = 'R'; + header.m_riffHeader.m_id[1] = 'I'; + header.m_riffHeader.m_id[2] = 'F'; + header.m_riffHeader.m_id[3] = 'F'; + header.m_riffHeader.m_size = 0; // Needs to be fixed on close + header.m_type[0] = 'W'; + header.m_type[1] = 'A'; + header.m_type[2] = 'V'; + header.m_type[3] = 'E'; + header.m_fmtHeader.m_id[0] = 'f'; + header.m_fmtHeader.m_id[1] = 'm'; + header.m_fmtHeader.m_id[2] = 't'; + header.m_fmtHeader.m_id[3] = ' '; + header.m_fmtHeader.m_size = 16; + header.m_audioFormat = 1; // Linear PCM + header.m_numChannels = m_nbChannels; // 2 for I/Q + header.m_sampleRate = m_sampleRate; + // We always use 16-bits regardless of SDR_RX_SAMP_SZ + header.m_byteRate = m_sampleRate * m_nbChannels * 16 / 8; + header.m_blockAlign = m_nbChannels * 16 / 8; + header.m_bitsPerSample = 16; + + header.m_auxiHeader.m_id[0] = 'a'; + header.m_auxiHeader.m_id[1] = 'u'; + header.m_auxiHeader.m_id[2] = 'x'; + header.m_auxiHeader.m_id[3] = 'i'; + header.m_auxiHeader.m_size = sizeof(Auxi); + QDateTime now = QDateTime::currentDateTime(); + header.m_auxi.m_startTime.m_year = now.date().year(); + header.m_auxi.m_startTime.m_month = now.date().month(); + header.m_auxi.m_startTime.m_dayOfWeek = now.date().dayOfWeek(); + header.m_auxi.m_startTime.m_day = now.date().day(); + header.m_auxi.m_startTime.m_hour = now.time().hour(); + header.m_auxi.m_startTime.m_minute = now.time().minute(); + header.m_auxi.m_startTime.m_second = now.time().second(); + header.m_auxi.m_startTime.m_milliseconds = now.time().msec(); + header.m_auxi.m_stopTime.m_year = 0; // Needs to be fixed on close + header.m_auxi.m_stopTime.m_month = 0; + header.m_auxi.m_stopTime.m_dayOfWeek = 0; + header.m_auxi.m_stopTime.m_day = 0; + header.m_auxi.m_stopTime.m_hour = 0; + header.m_auxi.m_stopTime.m_minute = 0; + header.m_auxi.m_stopTime.m_second = 0; + header.m_auxi.m_stopTime.m_milliseconds = 0; + header.m_auxi.m_centerFreq = m_centerFrequency; + header.m_auxi.m_adFrequency = m_sampleRate; + header.m_auxi.m_ifFrequency = 0; + header.m_auxi.m_bandwidth = 0; + header.m_auxi.m_iqOffset = 0; + header.m_auxi.m_unused2 = 0; + header.m_auxi.m_unused3 = 0; + header.m_auxi.m_unused4 = 0; + header.m_auxi.m_unused5 = 0; + memset(&header.m_auxi.m_nextFilename[0], 0, 96); + + header.m_dataHeader.m_size = sizeof(Auxi); + header.m_dataHeader.m_id[0] = 'd'; + header.m_dataHeader.m_id[1] = 'a'; + header.m_dataHeader.m_id[2] = 't'; + header.m_dataHeader.m_id[3] = 'a'; + header.m_dataHeader.m_size = 0; // Needs to be fixed on close + + writeHeader(m_sampleFile, header); +} + +bool WavFileRecord::readHeader(std::ifstream& sampleFile, Header& header, bool check) +{ + memset(&header, 0, sizeof(Header)); + + sampleFile.read((char *) &header, 8+4+8+16); + if (!sampleFile) + { + qDebug() << "WavFileRecord::readHeader: End of file without reading header"; + return false; + } + + if (check && !checkHeader(header)) { + return false; + } + + Chunk chunkHeader; + bool gotData = false; + while (!gotData) + { + sampleFile.read((char *) &chunkHeader, 8); + if (!sampleFile) + { + qDebug() << "WavFileRecord::readHeader: End of file without reading data header"; + return false; + } + + if (!strncmp(chunkHeader.m_id, "auxi", 4)) + { + memcpy(&header.m_auxiHeader, &chunkHeader, sizeof(Chunk)); + sampleFile.read((char *) &header.m_auxi, sizeof(Auxi)); + if (!sampleFile) + return false; + } + else if (!strncmp(chunkHeader.m_id, "data", 4)) + { + memcpy(&header.m_dataHeader, &chunkHeader, sizeof(Chunk)); + gotData = true; + } + } + + return true; +} + +bool WavFileRecord::readHeader(QFile& sampleFile, Header& header) +{ + memset(&header, 0, sizeof(Header)); + + sampleFile.read((char *) &header, 8+4+8+16); + + if (!checkHeader(header)) { + return false; + } + + Chunk chunkHeader; + bool gotData = false; + while (!gotData) + { + if (sampleFile.read((char *) &chunkHeader, 8) != 8) + { + qDebug() << "WavFileRecord::readHeader: End of file without reading data header"; + return false; + } + + if (!strncmp(chunkHeader.m_id, "auxi", 4)) + { + memcpy(&header.m_auxiHeader, &chunkHeader, sizeof(Chunk)); + if (sampleFile.read((char *) &header.m_auxi, sizeof(Auxi)) != sizeof(Auxi)) { + return false; + } + } + else if (!strncmp(chunkHeader.m_id, "data", 4)) + { + memcpy(&header.m_dataHeader, &chunkHeader, sizeof(Chunk)); + gotData = true; + } + } + + return true; +} + +bool WavFileRecord::checkHeader(Header& header) +{ + if (strncmp(header.m_riffHeader.m_id, "RIFF", 4)) + { + qDebug() << "WavFileRecord::readHeader: No RIFF header"; + return false; + } + if (strncmp(header.m_type, "WAVE", 4)) + { + qDebug() << "WavFileRecord::readHeader: No WAVE header"; + return false; + } + if (strncmp(header.m_fmtHeader.m_id, "fmt ", 4)) + { + qDebug() << "WavFileRecord::readHeader: No fmt header"; + return false; + } + if (header.m_audioFormat != 1) + { + qDebug() << "WavFileRecord::readHeader: Audio format is not PCM"; + return false; + } + if (header.m_numChannels != 2) + { + qDebug() << "WavFileRecord::readHeader: Number of channels is not 2"; + return false; + } + // FileInputWorker can't handle other bits sizes + if (header.m_bitsPerSample != 16) + { + qDebug() << "WavFileRecord::readHeader: Number of bits per sample is not 16"; + return false; + } + return true; +} + +void WavFileRecord::writeHeader(std::ofstream& sampleFile, Header& header) +{ + sampleFile.write((const char *) &header, sizeof(Header)); +} + +void WavFileRecord::writeHeader(QFile& sampleFile, Header& header) +{ + sampleFile.write((const char *) &header, sizeof(Header)); +} + +bool WavFileRecord::getCenterFrequency(QString fileName, quint64& centerFrequency) +{ + // Attempt to extract center frequency from filename + QRegularExpression freqkRE("(([0-9]+)kHz)"); + QRegularExpression freqRE("(([0-9]+)Hz)"); + QRegularExpressionMatch freqkREMatch = freqkRE.match(fileName); + QRegularExpressionMatch freqREMatch = freqRE.match(fileName); + + if (freqkREMatch.hasMatch()) + { + centerFrequency = freqkREMatch.capturedTexts()[2].toLongLong() * 1000LL; + return true; + } + else if (freqREMatch.hasMatch()) + { + centerFrequency = freqREMatch.capturedTexts()[2].toLongLong(); + return true; + } + return false; +} + +bool WavFileRecord::getStartTime(QString fileName, QDateTime& startTime) +{ + // Attempt to extract start time from filename + QRegularExpression dateTimeRE("([12][0-9][0-9][0-9]).?([01][0-9]).?([0-3][0-9]).?([0-2][0-9]).?([0-5][0-9]).?([0-5][0-9])"); + QRegularExpressionMatch match = dateTimeRE.match(fileName); + if (match.hasMatch()) + { + startTime = QDateTime(QDate( + match.capturedTexts()[1].toInt(), + match.capturedTexts()[2].toInt(), + match.capturedTexts()[3].toInt()), + QTime( + match.capturedTexts()[4].toInt(), + match.capturedTexts()[5].toInt(), + match.capturedTexts()[6].toInt())); + return true; + } + return false; +} + +QDateTime WavFileRecord::Header::getStartTime() const +{ + return QDateTime(QDate(m_auxi.m_startTime.m_year, + m_auxi.m_startTime.m_month, + m_auxi.m_startTime.m_day), + QTime(m_auxi.m_startTime.m_hour, + m_auxi.m_startTime.m_minute, + m_auxi.m_startTime.m_second, + m_auxi.m_startTime.m_milliseconds)); +} diff --git a/android/app/src/main/cpp/dsp/wavfilerecord.h b/android/app/src/main/cpp/dsp/wavfilerecord.h new file mode 100644 index 0000000..62c68d9 --- /dev/null +++ b/android/app/src/main/cpp/dsp/wavfilerecord.h @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// Copyright (C) 2022-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 CRD716 // +// // +// File recorder in .wav format // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_WAV_FILERECORD_H +#define INCLUDE_WAV_FILERECORD_H + +#include +#include +#include +#include + +#include +#include + +#include "dsp/filerecordinterface.h" +#include "export.h" + +class Message; + +class SDRBASE_API WavFileRecord : public FileRecordInterface { +public: + +#pragma pack(push, 1) + struct Chunk + { + char m_id[4]; // "RIFF", "fmt ", "auxi", "data" + quint32 m_size; + }; + struct SystemTime { + quint16 m_year; + quint16 m_month; + quint16 m_dayOfWeek; + quint16 m_day; + quint16 m_hour; + quint16 m_minute; + quint16 m_second; + quint16 m_milliseconds; + }; + struct Auxi { + SystemTime m_startTime; + SystemTime m_stopTime; + quint32 m_centerFreq; + quint32 m_adFrequency; + quint32 m_ifFrequency; + quint32 m_bandwidth; + quint32 m_iqOffset; + quint32 m_unused2; + quint32 m_unused3; + quint32 m_unused4; + quint32 m_unused5; + char m_nextFilename[96]; + }; + struct SDRBASE_API Header + { + Chunk m_riffHeader; + char m_type[4]; // "WAVE" + Chunk m_fmtHeader; + quint16 m_audioFormat; + quint16 m_numChannels; + quint32 m_sampleRate; + quint32 m_byteRate; + quint16 m_blockAlign; + quint16 m_bitsPerSample; + Chunk m_auxiHeader; + Auxi m_auxi; + Chunk m_dataHeader; + + QDateTime getStartTime() const; + }; +#pragma pack(pop) + + WavFileRecord(quint32 sampleRate=0, quint64 centerFrequency=0); + WavFileRecord(const QString& fileBase); + virtual ~WavFileRecord(); + + quint64 getByteCount() const { return m_byteCount; } + void setMsShift(qint64 shift) override { m_msShift = shift; } + virtual int getBytesPerSample() override { return 4; }; + const QString& getCurrentFileName() override { return m_currentFileName; } + void setMono(bool mono) { m_nbChannels = mono ? 1 : 2; } + void setSampleRate(quint32 sampleRate) { m_sampleRate = sampleRate; } + + void genUniqueFileName(uint deviceUID, int istream = -1); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) override; + void write(qint16 lSample, qint16 rSample); //!< write a single sample + void writeMono(qint16 sample); //!< write a single mono sample + void writeMono(qint16 *samples, int nbSamples); //!< write a buffer of mono samples + virtual void start() override; + virtual void stop() override; + virtual bool handleMessage(const Message& message) override; + + virtual void setFileName(const QString& fileBase) override; + void setFileBaseIsFileName(bool fileBaseIsFileName) { m_fileBaseIsFileName = fileBaseIsFileName; } + virtual bool startRecording() override; + virtual bool stopRecording() override; + virtual bool isRecording() const override { return m_recordOn; } + + void setMetaData(const QString& trackTitle="", const QString& album="", const QString& artist=""); + bool hasMetaData() const; + + static bool readHeader(std::ifstream& samplefile, Header& header, bool check=true); + static bool readHeader(QFile& samplefile, Header& header); + static void writeHeader(std::ofstream& samplefile, Header& header); + static void writeHeader(QFile& samplefile, Header& header); + + // These functions guess from the filename, not contents + static bool getCenterFrequency(QString fileName, quint64& centerFrequency); + static bool getStartTime(QString fileName, QDateTime& startTime); + +private: + QString m_fileBase; + bool m_fileBaseIsFileName; + quint32 m_sampleRate; + quint64 m_centerFrequency; + bool m_recordOn; + bool m_recordStart; +#ifdef ANDROID + QFile m_sampleFile; +#else + std::ofstream m_sampleFile; +#endif + QString m_currentFileName; + quint64 m_byteCount; + qint64 m_msShift; + int m_nbChannels; + + QString m_trackTitle; + QString m_album; + QString m_artist; + + void writeHeader(); + void writeInfoList(); + void writeID3(); + + static bool checkHeader(Header& header); +}; + +#endif // INCLUDE_WAV_FILERECORD_H diff --git a/android/app/src/main/cpp/dsp/wfir.cpp b/android/app/src/main/cpp/dsp/wfir.cpp new file mode 100644 index 0000000..23a342c --- /dev/null +++ b/android/app/src/main/cpp/dsp/wfir.cpp @@ -0,0 +1,400 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + July 15, 2015 + Iowa Hills Software LLC + http://www.iowahills.com + */ + +#include +#include +#include + +#include "wfir.h" + +#define M_2PI (2*M_PI) + +// This first calculates the impulse response for a rectangular window. +// It then applies the windowing function of choice to the impulse response. +void WFIR::BasicFIR(double *FirCoeff, int NumTaps, TPassTypeName PassType, + double OmegaC, double BW, TWindowType WindowType, double WinBeta) +{ + int j; + double Arg, OmegaLow, OmegaHigh; + + switch (PassType) + { + case LPF: + for (j = 0; j < NumTaps; j++) + { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + FirCoeff[j] = OmegaC * Sinc(OmegaC * Arg * M_PI); + } + break; + + case HPF: + if (NumTaps % 2 == 1) // Odd tap counts + { + for (j = 0; j < NumTaps; j++) + { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + FirCoeff[j] = Sinc(Arg * M_PI) + - OmegaC * Sinc(OmegaC * Arg * M_PI); + } + } + + else // Even tap counts + { + for (j = 0; j < NumTaps; j++) + { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + if (Arg == 0.0) + FirCoeff[j] = 0.0; + else + FirCoeff[j] = cos(OmegaC * Arg * M_PI) / M_PI / Arg + + cos(Arg * M_PI); + } + } + break; + + case BPF: + OmegaLow = OmegaC - BW / 2.0; + OmegaHigh = OmegaC + BW / 2.0; + for (j = 0; j < NumTaps; j++) + { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + if (Arg == 0.0) + FirCoeff[j] = 0.0; + else + FirCoeff[j] = (cos(OmegaLow * Arg * M_PI) + - cos(OmegaHigh * Arg * M_PI)) / M_PI / Arg; + } + break; + + case NOTCH: // If NumTaps is even for Notch filters, the response at Pi is attenuated. + OmegaLow = OmegaC - BW / 2.0; + OmegaHigh = OmegaC + BW / 2.0; + for (j = 0; j < NumTaps; j++) + { + Arg = (double) j - (double) (NumTaps - 1) / 2.0; + FirCoeff[j] = Sinc(Arg * M_PI) + - OmegaHigh * Sinc(OmegaHigh * Arg * M_PI) + - OmegaLow * Sinc(OmegaLow * Arg * M_PI); + } + break; + } + + // WindowData can be used to window data before an FFT. When used for FIR filters we set + // Alpha = 0.0 to prevent a flat top on the window and + // set UnityGain = false to prevent the window gain from getting set to unity. + WindowData(FirCoeff, NumTaps, WindowType, 0.0, WinBeta, false); + +} + +//--------------------------------------------------------------------------- + +// This gets used with the Kaiser window. +double WFIR::Bessel(double x) +{ + double Sum = 0.0, XtoIpower; + int i, j, Factorial; + for (i = 1; i < 10; i++) + { + XtoIpower = pow(x / 2.0, (double) i); + Factorial = 1; + for (j = 1; j <= i; j++) + Factorial *= j; + Sum += pow(XtoIpower / (double) Factorial, 2.0); + } + return (1.0 + Sum); +} + +//----------------------------------------------------------------------------- + +// This gets used with the Sinc window and various places in the BasicFIR function. +double WFIR::Sinc(double x) +{ + if (x > -1.0E-5 && x < 1.0E-5) + return (1.0); + return (sin(x) / x); +} + +//--------------------------------------------------------------------------- + +// These are the various windows definitions. These windows can be used for either +// FIR filter design or with an FFT for spectral analysis. +// Sourced verbatim from: ~MyDocs\Code\Common\FFTFunctions.cpp +// For definitions, see this article: http://en.wikipedia.org/wiki/Window_function + +// This function has 6 inputs +// Data is the array, of length N, containing the data to to be windowed. +// This data is either a FIR filter sinc pulse, or the data to be analyzed by an fft. + +// WindowType is an enum defined in the header file, which is at the bottom of this file. +// e.g. wtKAISER, wtSINC, wtHANNING, wtHAMMING, wtBLACKMAN, ... + +// Alpha sets the width of the flat top. +// Windows such as the Tukey and Trapezoid are defined to have a variably wide flat top. +// As can be seen by its definition, the Tukey is just a Hanning window with a flat top. +// Alpha can be used to give any of these windows a partial flat top, except the Flattop and Kaiser. +// Alpha = 0 gives the original window. (i.e. no flat top) +// To generate a Tukey window, use a Hanning with 0 < Alpha < 1 +// To generate a Bartlett window (triangular), use a Trapezoid window with Alpha = 0. +// Alpha = 1 generates a rectangular window in all cases. (except the Flattop and Kaiser) + +// Beta is used with the Kaiser, Sinc, and Sine windows only. +// These three windows are primarily used for FIR filter design, not spectral analysis. +// In FIR filter design, Beta controls the filter's transition bandwidth and the sidelobe levels. +// The code ignores Beta except in the Kaiser, Sinc, and Sine window cases. + +// UnityGain controls whether the gain of these windows is set to unity. +// Only the Flattop window has unity gain by design. The Hanning window, for example, has a gain +// of 1/2. UnityGain = true will set the gain of all these windows to 1. +// Then, when the window is applied to a signal, the signal's energy content is preserved. +// Don't use this with FIR filter design however. Since most of the enegy in an FIR sinc pulse +// is in the middle of the window, the window needs a peak amplitude of one, not unity gain. +// Setting UnityGain = true will simply cause the resulting FIR filter to have excess gain. + +// If using these windows for FIR filters, start with the Kaiser, Sinc, or Sine windows and +// adjust Beta for the desired transition BW and sidelobe levels (set Alpha = 0). +// While the FlatTop is an excellent window for spectral analysis, don't use it for FIR filter design. +// It has a peak amplitude of ~ 4.7 which causes the resulting FIR filter to have about this much gain. +// It works poorly for FIR filters even if you adjust its peak amplitude. +// The Trapezoid also works poorly for FIR filter design. + +// If using these windows with an fft for spectral analysis, start with the Hanning, Gauss, or Flattop. +// When choosing a window for spectral analysis, you must trade off between resolution and amplitude accuracy. +// The Hanning has the best resolution while the Flatop has the best amplitude accuracy. +// The Gauss is midway between these two for both accuracy and resolution. +// These three were the only windows available in the HP 89410A Vector Signal Analyzer. Which is to say, +// unless you have specific windowing requirements, use one of these 3 for general purpose signal analysis. +// Set UnityGain = true when using any of these windows for spectral analysis to preserve the signal's enegy level. + +void WFIR::WindowData(double *Data, int N, TWindowType WindowType, double Alpha, + double Beta, bool UnityGain) +{ + if (WindowType == wtNONE) + return; + + int j, M, TopWidth; + double dM, *WinCoeff; + + if (WindowType == wtKAISER || WindowType == wtFLATTOP) + Alpha = 0.0; + + if (Alpha < 0.0) + Alpha = 0.0; + if (Alpha > 1.0) + Alpha = 1.0; + + if (Beta < 0.0) + Beta = 0.0; + if (Beta > 10.0) + Beta = 10.0; + + WinCoeff = new (std::nothrow) double[N + 2]; + if (WinCoeff == 0) + { + std::cerr + << "Failed to allocate memory in FFTFunctions::WindowFFTData() " + << std::endl; + return; + } + + TopWidth = (int) (Alpha * (double) N); + if (TopWidth % 2 != 0) + TopWidth++; + if (TopWidth > N) + TopWidth = N; + M = N - TopWidth; + dM = M + 1; + + // Calculate the window for N/2 points, then fold the window over (at the bottom). + // TopWidth points will be set to 1. + if (WindowType == wtKAISER) + { + double Arg; + for (j = 0; j < M; j++) + { + Arg = Beta * sqrt(1.0 - pow(((double) (2 * j + 2) - dM) / dM, 2.0)); + WinCoeff[j] = Bessel(Arg) / Bessel(Beta); + } + } + + else if (WindowType == wtSINC) // Lanczos + { + for (j = 0; j < M; j++) + WinCoeff[j] = Sinc((double) (2 * j + 1 - M) / dM * M_PI); + for (j = 0; j < M; j++) + WinCoeff[j] = pow(WinCoeff[j], Beta); + } + + else if (WindowType == wtSINE) // Hanning if Beta = 2 + { + for (j = 0; j < M / 2; j++) + WinCoeff[j] = sin((double) (j + 1) * M_PI / dM); + for (j = 0; j < M / 2; j++) + WinCoeff[j] = pow(WinCoeff[j], Beta); + } + + else if (WindowType == wtHANNING) + { + for (j = 0; j < M / 2; j++) + WinCoeff[j] = 0.5 - 0.5 * cos((double) (j + 1) * M_2PI / dM); + } + + else if (WindowType == wtHAMMING) + { + for (j = 0; j < M / 2; j++) + WinCoeff[j] = 0.54 - 0.46 * cos((double) (j + 1) * M_2PI / dM); + } + + else if (WindowType == wtBLACKMAN) + { + for (j = 0; j < M / 2; j++) + { + WinCoeff[j] = 0.42 - 0.50 * cos((double) (j + 1) * M_2PI / dM) + + 0.08 * cos((double) (j + 1) * M_2PI * 2.0 / dM); + } + } + + // See: http://www.bth.se/fou/forskinfo.nsf/0/130c0940c5e7ffcdc1256f7f0065ac60/$file/ICOTA_2004_ttr_icl_mdh.pdf + else if (WindowType == wtFLATTOP) + { + for (j = 0; j <= M / 2; j++) + { + WinCoeff[j] = 1.0 + - 1.93293488969227 * cos((double) (j + 1) * M_2PI / dM) + + 1.28349769674027 + * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.38130801681619 + * cos((double) (j + 1) * M_2PI * 3.0 / dM) + + 0.02929730258511 + * cos((double) (j + 1) * M_2PI * 4.0 / dM); + } + } + + else if (WindowType == wtBLACKMAN_HARRIS) + { + for (j = 0; j < M / 2; j++) + { + WinCoeff[j] = 0.35875 - 0.48829 * cos((double) (j + 1) * M_2PI / dM) + + 0.14128 * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.01168 * cos((double) (j + 1) * M_2PI * 3.0 / dM); + } + } + + else if (WindowType == wtBLACKMAN_NUTTALL) + { + for (j = 0; j < M / 2; j++) + { + WinCoeff[j] = 0.3535819 + - 0.4891775 * cos((double) (j + 1) * M_2PI / dM) + + 0.1365995 * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.0106411 * cos((double) (j + 1) * M_2PI * 3.0 / dM); + } + } + + else if (WindowType == wtNUTTALL) + { + for (j = 0; j < M / 2; j++) + { + WinCoeff[j] = 0.355768 + - 0.487396 * cos((double) (j + 1) * M_2PI / dM) + + 0.144232 * cos((double) (j + 1) * M_2PI * 2.0 / dM) + - 0.012604 * cos((double) (j + 1) * M_2PI * 3.0 / dM); + } + } + + else if (WindowType == wtKAISER_BESSEL) + { + for (j = 0; j <= M / 2; j++) + { + WinCoeff[j] = 0.402 - 0.498 * cos(M_2PI * (double) (j + 1) / dM) + + 0.098 * cos(2.0 * M_2PI * (double) (j + 1) / dM) + + 0.001 * cos(3.0 * M_2PI * (double) (j + 1) / dM); + } + } + + else if (WindowType == wtTRAPEZOID) // Rectangle for Alpha = 1 Triangle for Alpha = 0 + { + int K = M / 2; + if (M % 2) + K++; + for (j = 0; j < K; j++) + WinCoeff[j] = (double) (j + 1) / (double) K; + } + + // This definition is from http://en.wikipedia.org/wiki/Window_function (Gauss Generalized normal window) + // We set their p = 2, and use Alpha in the numerator, instead of Sigma in the denominator, as most others do. + // Alpha = 2.718 puts the Gauss window response midway between the Hanning and the Flattop (basically what we want). + // It also gives the same BW as the Gauss window used in the HP 89410A Vector Signal Analyzer. + // Alpha = 1.8 puts it quite close to the Hanning. + else if (WindowType == wtGAUSS) + { + for (j = 0; j < M / 2; j++) + { + WinCoeff[j] = ((double) (j + 1) - dM / 2.0) / (dM / 2.0) * 2.7183; + WinCoeff[j] *= WinCoeff[j]; + WinCoeff[j] = exp(-WinCoeff[j]); + } + } + + else // Error. + { + std::cerr << "Incorrect window type in WindowFFTData" << std::endl; + delete[] WinCoeff; + return; + } + + // Fold the coefficients over. + for (j = 0; j < M / 2; j++) + WinCoeff[N - j - 1] = WinCoeff[j]; + + // This is the flat top if Alpha > 0. Cannot be applied to a Kaiser or Flat Top. + if (WindowType != wtKAISER && WindowType != wtFLATTOP) + { + for (j = M / 2; j < N - M / 2; j++) + { + if (j >= N + 2) { + break; + } + + WinCoeff[j] = 1.0; + } + } + + // This will set the gain of the window to 1. Only the Flattop window has unity gain by design. + if (UnityGain) + { + double Sum = 0.0; + for (j = 0; j < N; j++) + Sum += WinCoeff[j]; + Sum /= (double) N; + if (Sum != 0.0) + for (j = 0; j < N; j++) + WinCoeff[j] /= Sum; + } + + // Apply the window to the data. + for (j = 0; j < N; j++) + Data[j] *= WinCoeff[j]; + + delete[] WinCoeff; + +} diff --git a/android/app/src/main/cpp/dsp/wfir.h b/android/app/src/main/cpp/dsp/wfir.h new file mode 100644 index 0000000..2467a03 --- /dev/null +++ b/android/app/src/main/cpp/dsp/wfir.h @@ -0,0 +1,111 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017-2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + July 15, 2015 + Iowa Hills Software LLC + http://www.iowahills.com + + If you find a problem with this code, please leave us a note on: + http://www.iowahills.com/feedbackcomments.html + + Source: ~Projects\Common\BasicFIRFilterCode.cpp + + This generic FIR filter code is described in most textbooks. + e.g. Discrete Time Signal Processing, Oppenheim and Shafer + + A nice paper on this topic is: + http://dea.brunel.ac.uk/cmsp/Home_Saeed_Vaseghi/Chapter05-DigitalFilters.pdf + + This code first generates either a low pass, high pass, band pass, or notch + impulse response for a rectangular window. It then applies a window to this + impulse response. + + There are several windows available, including the Kaiser, Sinc, Hanning, + Blackman, and Hamming. Of these, the Kaiser and Sinc are probably the most useful + for FIR filters because their sidelobe levels can be controlled with the Beta parameter. + + This is a typical function call: + BasicFIR(FirCoeff, NumTaps, PassType, OmegaC, BW, wtKAISER, Beta); + BasicFIR(FirCoeff, 33, LPF, 0.2, 0.0, wtKAISER, 3.2); + 33 tap, low pass, corner frequency at 0.2, BW=0 (ignored in the low pass code), + Kaiser window, Kaiser Beta = 3.2 + + These variables should be defined similar to this: + double FirCoeff[MAXNUMTAPS]; + int NumTaps; NumTaps can be even or odd, but must be less than the FirCoeff array size. + TPassTypeName PassType; PassType is an enum defined in the header file. LPF, HPF, BPF, or NOTCH + double OmegaC 0.0 < OmegaC < 1.0 The filters corner freq, or center freq if BPF or NOTCH + double BW 0.0 < BW < 1.0 The filters band width if BPF or NOTCH + TWindowType WindowType; WindowType is an enum defined in the header to be one of these. + wtNONE, wtKAISER, wtSINC, wtHANNING, .... and others. + double Beta; 0 <= Beta <= 10.0 Beta is used with the Kaiser, Sinc, and Sine windows only. + It controls the transition BW and sidelobe level of the filters. + + + If you want to use it, Kaiser originally defined Beta as follows. + He derived its value based on the desired sidelobe level, dBAtten. + double dBAtten, Beta, Beta1=0.0, Beta2=0.0; + if(dBAtten < 21.0)dBAtten = 21.0; + if(dBAtten > 50.0)Beta1 = 0.1102 * (dBAtten - 8.7); + if(dBAtten >= 21.0 && dBAtten <= 50.0) Beta2 = 0.5842 * pow(dBAtten - 21.0, 0.4) + 0.07886 * (dBAtten - 21.0); + Beta = Beta1 + Beta2; + + */ + +#ifndef _WFIR_H_ +#define _WFIR_H_ + +#include "export.h" + +class SDRBASE_API WFIR +{ +public: + enum TPassTypeName + { + LPF, HPF, BPF, NOTCH + }; + + enum TWindowType + { + wtNONE, + wtKAISER, + wtSINC, + wtHANNING, + wtHAMMING, + wtBLACKMAN, + wtFLATTOP, + wtBLACKMAN_HARRIS, + wtBLACKMAN_NUTTALL, + wtNUTTALL, + wtKAISER_BESSEL, + wtTRAPEZOID, + wtGAUSS, + wtSINE, + wtTEST + }; + + static void BasicFIR(double *FirCoeff, int NumTaps, TPassTypeName PassType, + double OmegaC, double BW, TWindowType WindowType, double WinBeta); + +private: + static void WindowData(double *Data, int N, TWindowType WindowType, + double Alpha, double Beta, bool UnityGain); + static double Bessel(double x); + static double Sinc(double x); +}; + +#endif diff --git a/android/app/src/main/cpp/fcdlib/CMakeLists.txt b/android/app/src/main/cpp/fcdlib/CMakeLists.txt new file mode 100644 index 0000000..0ce5f16 --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/CMakeLists.txt @@ -0,0 +1,31 @@ +project(fcdlib) + +set(fcdlib_SOURCES + fcdtraits.cpp + fcdproplusconst.cpp + fcdproconst.cpp +) + +set(fcdlib_HEADERS + fcdtraits.h + fcdproplusconst.h + fcdproconst.h + export.h +) + +include_directories( +) + +add_library(fcdlib SHARED + ${fcdlib_SOURCES} +) + +target_link_libraries(fcdlib) + +set_target_properties(fcdlib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS true) +if (MSVC) + set_target_properties(fcdlib PROPERTIES INTERPROCEDURAL_OPTIMIZATION false) + set_target_properties(fcdlib PROPERTIES DEFINE_SYMBOL "FCDLIB_EXPORTS") +endif() + +install(TARGETS fcdlib DESTINATION ${INSTALL_LIB_DIR}) diff --git a/android/app/src/main/cpp/fcdlib/export.h b/android/app/src/main/cpp/fcdlib/export.h new file mode 100644 index 0000000..c257d5e --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/export.h @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef FCDLIB_EXPORT_H_ +#define FCDLIB_EXPORT_H_ + +// cmake's WINDOWS_EXPORT_ALL_SYMBOLS only supports functions, so we need dllexport/import for global data +#ifdef _MSC_VER +#ifdef FCDLIB_EXPORTS +#define FCDLIB_API __declspec(dllexport) +#else +#define FCDLIB_API __declspec(dllimport) +#endif +#else +#define FCDLIB_API +#endif + +#endif /* FCDLIB_EXPORT_H_ */ diff --git a/android/app/src/main/cpp/fcdlib/fcdproconst.cpp b/android/app/src/main/cpp/fcdlib/fcdproconst.cpp new file mode 100644 index 0000000..bcea1d4 --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/fcdproconst.cpp @@ -0,0 +1,304 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + * fcdproconst.cpp + * + * Created on: Sep 7, 2015 + * Author: f4exb + */ + +#include "fcdproconst.h" + +const fcdpro_lna_gain FCDProConstants::lna_gains[] = { + {FCDPRO_TLG_N5_0DB, "-5dB"}, + {FCDPRO_TLG_N2_5DB, "-2.5dB"}, + {FCDPRO_TLG_P0_0DB, "0dB"}, + {FCDPRO_TLG_P2_5DB, "2.5dB"}, + {FCDPRO_TLG_P5_0DB, "5dB"}, + {FCDPRO_TLG_P7_5DB, "7.5dB"}, + {FCDPRO_TLG_P10_0DB, "10dB"}, + {FCDPRO_TLG_P12_5DB, "12.5dB"}, + {FCDPRO_TLG_P15_0DB, "15dB"}, + {FCDPRO_TLG_P17_5DB, "17.5dB"}, + {FCDPRO_TLG_P20_0DB, "20dB"}, + {FCDPRO_TLG_P25_0DB, "25dB"}, + {FCDPRO_TLG_P30_0DB, "30dB"} +}; + +int FCDProConstants::fcdpro_lna_gain_nb_values() +{ + return sizeof(lna_gains) / sizeof(fcdpro_lna_gain); +} + +const fcdpro_lna_enhance FCDProConstants::lna_enhances[] = { + {FCDPRO_TLE_OFF, "Off"}, + {FCDPRO_TLE_0, "0"}, + {FCDPRO_TLE_1, "1"}, + {FCDPRO_TLE_2, "2"}, + {FCDPRO_TLE_3, "3"} +}; + +int FCDProConstants::fcdpro_lna_enhance_nb_values() +{ + return sizeof(lna_enhances) / sizeof(fcdpro_lna_enhance); +} + +const fcdpro_band FCDProConstants::bands[] = { + {FCDPRO_TB_VHF2, "VHF2"}, + {FCDPRO_TB_VHF3, "VHF3"}, + {FCDPRO_TB_UHF, "UHF"}, + {FCDPRO_TB_LBAND, "L"} +}; + +int FCDProConstants::fcdpro_band_nb_values() +{ + return sizeof(bands) / sizeof(fcdpro_band); +} + +const fcdpro_rf_filter FCDProConstants::rf_filters[] = { + // Band 0, VHF II + {FCDPRO_TRF_LPF268MHZ, "LP268M"}, + {FCDPRO_TRF_LPF299MHZ, "LP299M"}, + // Band 1, VHF III + {FCDPRO_TRF_LPF509MHZ, "LP509M"}, + {FCDPRO_TRF_LPF656MHZ, "LP656M"}, + // Band 2, UHF + {FCDPRO_TRF_BPF360MHZ, "BP360M"}, + {FCDPRO_TRF_BPF380MHZ, "BP390M"}, + {FCDPRO_TRF_BPF405MHZ, "BP405M"}, + {FCDPRO_TRF_BPF425MHZ, "BP425M"}, + {FCDPRO_TRF_BPF450MHZ, "BP450M"}, + {FCDPRO_TRF_BPF475MHZ, "BP475M"}, + {FCDPRO_TRF_BPF505MHZ, "BP505M"}, + {FCDPRO_TRF_BPF540MHZ, "BP540M"}, + {FCDPRO_TRF_BPF575MHZ, "BP575M"}, + {FCDPRO_TRF_BPF615MHZ, "BP615M"}, + {FCDPRO_TRF_BPF670MHZ, "BP670M"}, + {FCDPRO_TRF_BPF720MHZ, "BP720M"}, + {FCDPRO_TRF_BPF760MHZ, "BP760M"}, + {FCDPRO_TRF_BPF840MHZ, "BP840M"}, + {FCDPRO_TRF_BPF890MHZ, "BP890M"}, + {FCDPRO_TRF_BPF970MHZ, "BP970M"}, + // Band 2, L band + {FCDPRO_TRF_BPF1300MHZ, "BP1.3G"}, + {FCDPRO_TRF_BPF1320MHZ, "BP1.32G"}, + {FCDPRO_TRF_BPF1360MHZ, "BP1.36G"}, + {FCDPRO_TRF_BPF1410MHZ, "BP1.41G"}, + {FCDPRO_TRF_BPF1445MHZ, "BP1445M"}, + {FCDPRO_TRF_BPF1460MHZ, "BP1.46G"}, + {FCDPRO_TRF_BPF1490MHZ, "BP1.49G"}, + {FCDPRO_TRF_BPF1530MHZ, "BP1.53G"}, + {FCDPRO_TRF_BPF1560MHZ, "BP1.56G"}, + {FCDPRO_TRF_BPF1590MHZ, "BP1.59G"}, + {FCDPRO_TRF_BPF1640MHZ, "BP1.64G"}, + {FCDPRO_TRF_BPF1660MHZ, "BP1.66G"}, + {FCDPRO_TRF_BPF1680MHZ, "BP1.68G"}, + {FCDPRO_TRF_BPF1700MHZ, "BP1.7G"}, + {FCDPRO_TRF_BPF1720MHZ, "BP1.72G"}, + {FCDPRO_TRF_BPF1750MHZ, "BP1.75G"} +}; + +int FCDProConstants::fcdpro_rf_filter_nb_values() +{ + return sizeof(rf_filters) / sizeof(fcdpro_rf_filter); +} + +const fcdpro_mixer_gain FCDProConstants::mixer_gains[] = { + {FCDPRO_TMG_P4_0DB, "0dB"}, + {FCDPRO_TMG_P12_0DB, "12dB"} +}; + +int FCDProConstants::fcdpro_mixer_gain_nb_values() +{ + return sizeof(mixer_gains) / sizeof(fcdpro_mixer_gain); +} + +const fcdpro_bias_current FCDProConstants::bias_currents[] = { + {FCDPRO_TBC_LBAND, "L"}, + {FCDPRO_TBC_1, "1"}, + {FCDPRO_TBC_2, "2"}, + {FCDPRO_TBC_VUBAND, "VU"} +}; + +int FCDProConstants::fcdpro_bias_current_nb_values() +{ + return sizeof(bias_currents) / sizeof(fcdpro_bias_current); +} + +const fcdpro_mixer_filter FCDProConstants::mixer_filters[] = { + {FCDPRO_TMF_27_0MHZ, "27M"}, + {FCDPRO_TMF_4_6MHZ, "4.6M"}, + {FCDPRO_TMF_4_2MHZ, "4.2M"}, + {FCDPRO_TMF_3_8MHZ, "3.8M"}, + {FCDPRO_TMF_3_4MHZ, "3.4M"}, + {FCDPRO_TMF_3_0MHZ, "3.0M"}, + {FCDPRO_TMF_2_7MHZ, "2.7M"}, + {FCDPRO_TMF_2_3MHZ, "2.3M"}, + {FCDPRO_TMF_1_9MHZ, "1.9M"} +}; + +int FCDProConstants::fcdpro_mixer_filter_nb_values() +{ + return sizeof(mixer_filters) / sizeof(fcdpro_mixer_filter); +} + +const fcdpro_if_gain1 FCDProConstants::if_gains1[] = { + {FCDPRO_TIG1_N3_0DB, "3dB"}, + {FCDPRO_TIG1_P6_0DB, "6dB"} +}; + +int FCDProConstants::fcdpro_if_gain1_nb_values() +{ + return sizeof(if_gains1) / sizeof(fcdpro_if_gain1); +} + +const fcdpro_if_gain_mode FCDProConstants::if_gain_modes[] = { + {FCDPRO_TIGM_LINEARITY, "Lin"}, + {FCDPRO_TIGM_SENSITIVITY, "Sens"} +}; + +int FCDProConstants::fcdpro_if_gain_mode_nb_values() +{ + return sizeof(if_gain_modes) / sizeof(fcdpro_if_gain_mode); +} + +const fcdpro_if_rc_filter FCDProConstants::if_rc_filters[] = { + {FCDPRO_TIRF_21_4MHZ, "21.4M"}, + {FCDPRO_TIRF_21_0MHZ, "21.0M"}, + {FCDPRO_TIRF_17_6MHZ, "17.6M"}, + {FCDPRO_TIRF_14_7MHZ, "14.7M"}, + {FCDPRO_TIRF_12_4MHZ, "12.4M"}, + {FCDPRO_TIRF_10_6MHZ, "18.6M"}, + {FCDPRO_TIRF_9_0MHZ, "9.0M"}, + {FCDPRO_TIRF_7_7MHZ, "7.7M"}, + {FCDPRO_TIRF_6_4MHZ, "6.4M"}, + {FCDPRO_TIRF_5_3MHZ, "5.3M"}, + {FCDPRO_TIRF_4_4MHZ, "4.4M"}, + {FCDPRO_TIRF_3_4MHZ, "3.4M"}, + {FCDPRO_TIRF_2_6MHZ, "2.6M"}, + {FCDPRO_TIRF_1_8MHZ, "1.8M"}, + {FCDPRO_TIRF_1_2MHZ, "1.2M"}, + {FCDPRO_TIRF_1_0MHZ, "1.0M"} +}; + +int FCDProConstants::fcdpro_if_rc_filter_nb_values() +{ + return sizeof(if_rc_filters) / sizeof(fcdpro_if_rc_filter); +} + +const fcdpro_if_gain2 FCDProConstants::if_gains2[] = { + {FCDPRO_TIG2_P0_0DB, "0dB"}, + {FCDPRO_TIG2_P3_0DB, "3dB"}, + {FCDPRO_TIG2_P6_0DB, "6dB"}, + {FCDPRO_TIG2_P9_0DB, "9dB"} +}; + +int FCDProConstants::fcdpro_if_gain2_nb_values() +{ + return sizeof(if_gains2) / sizeof(fcdpro_if_gain2); +} + +const fcdpro_if_gain3 FCDProConstants::if_gains3[] = { + {FCDPRO_TIG3_P0_0DB, "0dB"}, + {FCDPRO_TIG3_P3_0DB, "3dB"}, + {FCDPRO_TIG3_P6_0DB, "6dB"}, + {FCDPRO_TIG3_P9_0DB, "9dB"} +}; + +int FCDProConstants::fcdpro_if_gain3_nb_values() +{ + return sizeof(if_gains3) / sizeof(fcdpro_if_gain3); +} + +const fcdpro_if_gain4 FCDProConstants::if_gains4[] = { + {FCDPRO_TIG4_P0_0DB, "0dB"}, + {FCDPRO_TIG4_P1_0DB, "1dB"}, + {FCDPRO_TIG4_P2_0DB, "2dB"}, +}; + +int FCDProConstants::fcdpro_if_gain4_nb_values() +{ + return sizeof(if_gains4) / sizeof(fcdpro_if_gain4); +} + +const fcdpro_if_filter FCDProConstants::if_filters[] = { + {FCDPRO_TIF_5_50MHZ, "5.5M"}, + {FCDPRO_TIF_5_30MHZ, "5.3M"}, + {FCDPRO_TIF_5_00MHZ, "5.0M"}, + {FCDPRO_TIF_4_80MHZ, "4.8M"}, + {FCDPRO_TIF_4_60MHZ, "4.6M"}, + {FCDPRO_TIF_4_40MHZ, "4.4M"}, + {FCDPRO_TIF_4_30MHZ, "4.3M"}, + {FCDPRO_TIF_4_10MHZ, "4.1M"}, + {FCDPRO_TIF_3_90MHZ, "3.9M"}, + {FCDPRO_TIF_3_80MHZ, "3.8M"}, + {FCDPRO_TIF_3_70MHZ, "3.7M"}, + {FCDPRO_TIF_3_60MHZ, "3.6M"}, + {FCDPRO_TIF_3_40MHZ, "3.4M"}, + {FCDPRO_TIF_3_30MHZ, "3.3M"}, + {FCDPRO_TIF_3_20MHZ, "3.2M"}, + {FCDPRO_TIF_3_10MHZ, "3.1M"}, + {FCDPRO_TIF_3_00MHZ, "3.0M"}, + {FCDPRO_TIF_2_95MHZ, "2.95M"}, + {FCDPRO_TIF_2_90MHZ, "2.9M"}, + {FCDPRO_TIF_2_80MHZ, "2.8M"}, + {FCDPRO_TIF_2_75MHZ, "2.75M"}, + {FCDPRO_TIF_2_70MHZ, "2.7M"}, + {FCDPRO_TIF_2_60MHZ, "2.6M"}, + {FCDPRO_TIF_2_55MHZ, "2.55M"}, + {FCDPRO_TIF_2_50MHZ, "2.5M"}, + {FCDPRO_TIF_2_45MHZ, "2.45M"}, + {FCDPRO_TIF_2_40MHZ, "2.4M"}, + {FCDPRO_TIF_2_30MHZ, "2.3M"}, + {FCDPRO_TIF_2_28MHZ, "2.28M"}, + {FCDPRO_TIF_2_24MHZ, "2.24M"}, + {FCDPRO_TIF_2_20MHZ, "2.2M"}, + {FCDPRO_TIF_2_15MHZ, "2.15M"} +}; + +int FCDProConstants::fcdpro_if_filter_nb_values() +{ + return sizeof(if_filters) / sizeof(fcdpro_if_filter); +} + +const fcdpro_if_gain5 FCDProConstants::if_gains5[] = { + {FCDPRO_TIG5_P3_0DB, "3dB"}, + {FCDPRO_TIG5_P6_0DB, "6dB"}, + {FCDPRO_TIG5_P9_0DB, "9dB"}, + {FCDPRO_TIG5_P12_0DB, "12dB"}, + {FCDPRO_TIG5_P15_0DB, "15dB"} +}; + +int FCDProConstants::fcdpro_if_gain5_nb_values() +{ + return sizeof(if_gains5) / sizeof(fcdpro_if_gain5); +} + +const fcdpro_if_gain6 FCDProConstants::if_gains6[] = { + {FCDPRO_TIG6_P3_0DB, "3dB"}, + {FCDPRO_TIG6_P6_0DB, "6dB"}, + {FCDPRO_TIG6_P9_0DB, "9dB"}, + {FCDPRO_TIG6_P12_0DB, "12dB"}, + {FCDPRO_TIG6_P15_0DB, "15dB"} +}; + +int FCDProConstants::fcdpro_if_gain6_nb_values() +{ + return sizeof(if_gains6) / sizeof(fcdpro_if_gain6); +} + + diff --git a/android/app/src/main/cpp/fcdlib/fcdproconst.h b/android/app/src/main/cpp/fcdlib/fcdproconst.h new file mode 100644 index 0000000..ca25b45 --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/fcdproconst.h @@ -0,0 +1,380 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + * fcdproconst.h + * + * Created on: Sep 7, 2015 + * Author: f4exb + */ + +#ifndef FCDLIB_FCDPROCONST_H_ +#define FCDLIB_FCDPROCONST_H_ + +#include + +#include "export.h" + +typedef enum +{ + FCDPRO_TLG_N5_0DB=0, + FCDPRO_TLG_N2_5DB=1, + FCDPRO_TLG_P0_0DB=4, + FCDPRO_TLG_P2_5DB=5, + FCDPRO_TLG_P5_0DB=6, + FCDPRO_TLG_P7_5DB=7, + FCDPRO_TLG_P10_0DB=8, + FCDPRO_TLG_P12_5DB=9, + FCDPRO_TLG_P15_0DB=10, + FCDPRO_TLG_P17_5DB=11, + FCDPRO_TLG_P20_0DB=12, + FCDPRO_TLG_P25_0DB=13, + FCDPRO_TLG_P30_0DB=14 +} fcdpro_lna_gain_value; + +typedef struct +{ + fcdpro_lna_gain_value value; + std::string label; +} fcdpro_lna_gain; + +typedef enum +{ + FCDPRO_TLE_OFF=0, + FCDPRO_TLE_0=1, + FCDPRO_TLE_1=3, + FCDPRO_TLE_2=5, + FCDPRO_TLE_3=7 +} fcdpro_lna_enhance_value; + +typedef struct +{ + fcdpro_lna_enhance_value value; + std::string label; +} fcdpro_lna_enhance; + +typedef enum +{ + FCDPRO_TB_VHF2, + FCDPRO_TB_VHF3, + FCDPRO_TB_UHF, + FCDPRO_TB_LBAND +} fcdpro_band_value; + +typedef struct +{ + fcdpro_band_value value; + std::string label; +} fcdpro_band; + +typedef enum +{ + // Band 0, VHF II + FCDPRO_TRF_LPF268MHZ=0, + FCDPRO_TRF_LPF299MHZ=8, + // Band 1, VHF III + FCDPRO_TRF_LPF509MHZ=0, + FCDPRO_TRF_LPF656MHZ=8, + // Band 2, UHF + FCDPRO_TRF_BPF360MHZ=0, + FCDPRO_TRF_BPF380MHZ=1, + FCDPRO_TRF_BPF405MHZ=2, + FCDPRO_TRF_BPF425MHZ=3, + FCDPRO_TRF_BPF450MHZ=4, + FCDPRO_TRF_BPF475MHZ=5, + FCDPRO_TRF_BPF505MHZ=6, + FCDPRO_TRF_BPF540MHZ=7, + FCDPRO_TRF_BPF575MHZ=8, + FCDPRO_TRF_BPF615MHZ=9, + FCDPRO_TRF_BPF670MHZ=10, + FCDPRO_TRF_BPF720MHZ=11, + FCDPRO_TRF_BPF760MHZ=12, + FCDPRO_TRF_BPF840MHZ=13, + FCDPRO_TRF_BPF890MHZ=14, + FCDPRO_TRF_BPF970MHZ=15, + // Band 2, L band + FCDPRO_TRF_BPF1300MHZ=0, + FCDPRO_TRF_BPF1320MHZ=1, + FCDPRO_TRF_BPF1360MHZ=2, + FCDPRO_TRF_BPF1410MHZ=3, + FCDPRO_TRF_BPF1445MHZ=4, + FCDPRO_TRF_BPF1460MHZ=5, + FCDPRO_TRF_BPF1490MHZ=6, + FCDPRO_TRF_BPF1530MHZ=7, + FCDPRO_TRF_BPF1560MHZ=8, + FCDPRO_TRF_BPF1590MHZ=9, + FCDPRO_TRF_BPF1640MHZ=10, + FCDPRO_TRF_BPF1660MHZ=11, + FCDPRO_TRF_BPF1680MHZ=12, + FCDPRO_TRF_BPF1700MHZ=13, + FCDPRO_TRF_BPF1720MHZ=14, + FCDPRO_TRF_BPF1750MHZ=15 +} fcdpro_rf_filter_value; + +typedef struct +{ + fcdpro_rf_filter_value value; + std::string label; +} fcdpro_rf_filter; + +typedef enum +{ + FCDPRO_TMG_P4_0DB=0, + FCDPRO_TMG_P12_0DB=1 +} fcdpro_mixer_gain_value; + +typedef struct +{ + fcdpro_mixer_gain_value value; + std::string label; +} fcdpro_mixer_gain; + +typedef enum +{ + FCDPRO_TBC_LBAND=0, + FCDPRO_TBC_1=1, + FCDPRO_TBC_2=2, + FCDPRO_TBC_VUBAND=3 +} fcdpro_bias_current_value; + +typedef struct +{ + fcdpro_bias_current_value value; + std::string label; +} fcdpro_bias_current; + +typedef enum +{ + FCDPRO_TMF_27_0MHZ=0, + FCDPRO_TMF_4_6MHZ=8, + FCDPRO_TMF_4_2MHZ=9, + FCDPRO_TMF_3_8MHZ=10, + FCDPRO_TMF_3_4MHZ=11, + FCDPRO_TMF_3_0MHZ=12, + FCDPRO_TMF_2_7MHZ=13, + FCDPRO_TMF_2_3MHZ=14, + FCDPRO_TMF_1_9MHZ=15 +} fcdpro_mixer_filter_value; + +typedef struct +{ + fcdpro_mixer_filter_value value; + std::string label; +} fcdpro_mixer_filter; + +typedef enum +{ + FCDPRO_TIG1_N3_0DB=0, + FCDPRO_TIG1_P6_0DB=1 +} fcdpro_if_gain1_value; + +typedef struct +{ + fcdpro_if_gain1_value value; + std::string label; +} fcdpro_if_gain1; + +typedef enum +{ + FCDPRO_TIGM_LINEARITY=0, + FCDPRO_TIGM_SENSITIVITY=1 +} fcdpro_if_gain_mode_value; + +typedef struct +{ + fcdpro_if_gain_mode_value value; + std::string label; +} fcdpro_if_gain_mode; + +typedef enum +{ + FCDPRO_TIRF_21_4MHZ=0, + FCDPRO_TIRF_21_0MHZ=1, + FCDPRO_TIRF_17_6MHZ=2, + FCDPRO_TIRF_14_7MHZ=3, + FCDPRO_TIRF_12_4MHZ=4, + FCDPRO_TIRF_10_6MHZ=5, + FCDPRO_TIRF_9_0MHZ=6, + FCDPRO_TIRF_7_7MHZ=7, + FCDPRO_TIRF_6_4MHZ=8, + FCDPRO_TIRF_5_3MHZ=9, + FCDPRO_TIRF_4_4MHZ=10, + FCDPRO_TIRF_3_4MHZ=11, + FCDPRO_TIRF_2_6MHZ=12, + FCDPRO_TIRF_1_8MHZ=13, + FCDPRO_TIRF_1_2MHZ=14, + FCDPRO_TIRF_1_0MHZ=15 +} fcdpro_if_rc_filter_value; + +typedef struct +{ + fcdpro_if_rc_filter_value value; + std::string label; +} fcdpro_if_rc_filter; + +typedef enum +{ + FCDPRO_TIG2_P0_0DB=0, + FCDPRO_TIG2_P3_0DB=1, + FCDPRO_TIG2_P6_0DB=2, + FCDPRO_TIG2_P9_0DB=3 +} fcdpro_if_gain2_value; + +typedef struct +{ + fcdpro_if_gain2_value value; + std::string label; +} fcdpro_if_gain2; + +typedef enum +{ + FCDPRO_TIG3_P0_0DB=0, + FCDPRO_TIG3_P3_0DB=1, + FCDPRO_TIG3_P6_0DB=2, + FCDPRO_TIG3_P9_0DB=3 +} fcdpro_if_gain3_value; + +typedef struct +{ + fcdpro_if_gain3_value value; + std::string label; +} fcdpro_if_gain3; + +typedef enum +{ + FCDPRO_TIG4_P0_0DB=0, + FCDPRO_TIG4_P1_0DB=1, + FCDPRO_TIG4_P2_0DB=2 +} fcdpro_if_gain4_value; + +typedef struct +{ + fcdpro_if_gain4_value value; + std::string label; +} fcdpro_if_gain4; + +typedef enum +{ + FCDPRO_TIF_5_50MHZ=0, + FCDPRO_TIF_5_30MHZ=1, + FCDPRO_TIF_5_00MHZ=2, + FCDPRO_TIF_4_80MHZ=3, + FCDPRO_TIF_4_60MHZ=4, + FCDPRO_TIF_4_40MHZ=5, + FCDPRO_TIF_4_30MHZ=6, + FCDPRO_TIF_4_10MHZ=7, + FCDPRO_TIF_3_90MHZ=8, + FCDPRO_TIF_3_80MHZ=9, + FCDPRO_TIF_3_70MHZ=10, + FCDPRO_TIF_3_60MHZ=11, + FCDPRO_TIF_3_40MHZ=12, + FCDPRO_TIF_3_30MHZ=13, + FCDPRO_TIF_3_20MHZ=14, + FCDPRO_TIF_3_10MHZ=15, + FCDPRO_TIF_3_00MHZ=16, + FCDPRO_TIF_2_95MHZ=17, + FCDPRO_TIF_2_90MHZ=18, + FCDPRO_TIF_2_80MHZ=19, + FCDPRO_TIF_2_75MHZ=20, + FCDPRO_TIF_2_70MHZ=21, + FCDPRO_TIF_2_60MHZ=22, + FCDPRO_TIF_2_55MHZ=23, + FCDPRO_TIF_2_50MHZ=24, + FCDPRO_TIF_2_45MHZ=25, + FCDPRO_TIF_2_40MHZ=26, + FCDPRO_TIF_2_30MHZ=27, + FCDPRO_TIF_2_28MHZ=28, + FCDPRO_TIF_2_24MHZ=29, + FCDPRO_TIF_2_20MHZ=30, + FCDPRO_TIF_2_15MHZ=31 +} fcdpro_if_filter_value; + +typedef struct +{ + fcdpro_if_filter_value value; + std::string label; +} fcdpro_if_filter; + +typedef enum +{ + FCDPRO_TIG5_P3_0DB=0, + FCDPRO_TIG5_P6_0DB=1, + FCDPRO_TIG5_P9_0DB=2, + FCDPRO_TIG5_P12_0DB=3, + FCDPRO_TIG5_P15_0DB=4 +} fcdpro_if_gain5_value; + +typedef struct +{ + fcdpro_if_gain5_value value; + std::string label; +} fcdpro_if_gain5; + +typedef enum +{ + FCDPRO_TIG6_P3_0DB=0, + FCDPRO_TIG6_P6_0DB=1, + FCDPRO_TIG6_P9_0DB=2, + FCDPRO_TIG6_P12_0DB=3, + FCDPRO_TIG6_P15_0DB=4 +} fcdpro_if_gain6_value; + +typedef struct +{ + fcdpro_if_gain6_value value; + std::string label; +} fcdpro_if_gain6; + +class FCDLIB_API FCDProConstants +{ +public: + static const fcdpro_lna_gain lna_gains[]; + static const fcdpro_lna_enhance lna_enhances[]; + static const fcdpro_band bands[]; + static const fcdpro_rf_filter rf_filters[]; + static const fcdpro_mixer_gain mixer_gains[]; + static const fcdpro_bias_current bias_currents[]; + static const fcdpro_mixer_filter mixer_filters[]; + static const fcdpro_if_gain_mode if_gain_modes[]; + static const fcdpro_if_rc_filter if_rc_filters[]; + static const fcdpro_if_filter if_filters[]; + static const fcdpro_if_gain1 if_gains1[]; + static const fcdpro_if_gain2 if_gains2[]; + static const fcdpro_if_gain3 if_gains3[]; + static const fcdpro_if_gain4 if_gains4[]; + static const fcdpro_if_gain5 if_gains5[]; + static const fcdpro_if_gain6 if_gains6[]; + + static int fcdpro_lna_gain_nb_values(); + static int fcdpro_lna_enhance_nb_values(); + static int fcdpro_band_nb_values(); + static int fcdpro_rf_filter_nb_values(); + static int fcdpro_mixer_gain_nb_values(); + static int fcdpro_bias_current_nb_values(); + static int fcdpro_mixer_filter_nb_values(); + static int fcdpro_if_gain_mode_nb_values(); + static int fcdpro_if_rc_filter_nb_values(); + static int fcdpro_if_filter_nb_values(); + static int fcdpro_if_gain1_nb_values(); + static int fcdpro_if_gain2_nb_values(); + static int fcdpro_if_gain3_nb_values(); + static int fcdpro_if_gain4_nb_values(); + static int fcdpro_if_gain5_nb_values(); + static int fcdpro_if_gain6_nb_values(); +}; + +#endif /* FCDLIB_FCDPROCONST_H_ */ diff --git a/android/app/src/main/cpp/fcdlib/fcdproplusconst.cpp b/android/app/src/main/cpp/fcdlib/fcdproplusconst.cpp new file mode 100644 index 0000000..56492e4 --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/fcdproplusconst.cpp @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + * fcdproplusconst.cpp + * + * Created on: Sep 5, 2015 + * Author: f4exb + */ + +#include "fcdproplusconst.h" + +const fcdproplus_rf_filter FCDProPlusConstants::rf_filters[] = { + {FCDPROPLUS_TRF_0_4, "0-4M"}, + {FCDPROPLUS_TRF_4_8, "4-8M"}, + {FCDPROPLUS_TRF_8_16, "8-16M"}, + {FCDPROPLUS_TRF_16_32, "16-32M"}, + {FCDPROPLUS_TRF_32_75, "32-75M"}, + {FCDPROPLUS_TRF_75_125, "75-125M"}, + {FCDPROPLUS_TRF_125_250, "125-250M"}, + {FCDPROPLUS_TRF_145, "145M"}, + {FCDPROPLUS_TRF_410_875, "410-875M"}, + {FCDPROPLUS_TRF_435, "435M"}, + {FCDPROPLUS_TRF_875_2000, "875M-2G"} +}; + +int FCDProPlusConstants::fcdproplus_rf_filter_nb_values() +{ + return sizeof(rf_filters) / sizeof(fcdproplus_rf_filter); +} + +const fcdproplus_if_filter FCDProPlusConstants::if_filters[] = { + {FCDPROPLUS_TIF_200KHZ, "200k"}, + {FCDPROPLUS_TIF_300KHZ, "300k"}, + {FCDPROPLUS_TIF_600KHZ, "600k"}, + {FCDPROPLUS_TIF_1536KHZ, "1.5M"}, + {FCDPROPLUS_TIF_5MHZ, "5M"}, + {FCDPROPLUS_TIF_6MHZ, "6M"}, + {FCDPROPLUS_TIF_7MHZ, "7M"}, + {FCDPROPLUS_TIF_8MHZ, "8M"} +}; + +int FCDProPlusConstants::fcdproplus_if_filter_nb_values() +{ + return sizeof(if_filters) / sizeof(fcdproplus_if_filter); +} diff --git a/android/app/src/main/cpp/fcdlib/fcdproplusconst.h b/android/app/src/main/cpp/fcdlib/fcdproplusconst.h new file mode 100644 index 0000000..b25458d --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/fcdproplusconst.h @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + * fcdproplusconst.h + * + * Created on: Sep 5, 2015 + * Author: f4exb + */ + +#ifndef FCDLIB_FCDPROPLUSCONST_H_ +#define FCDLIB_FCDPROPLUSCONST_H_ + +#include + +#include "export.h" + +typedef enum +{ + FCDPROPLUS_TRF_0_4, + FCDPROPLUS_TRF_4_8, + FCDPROPLUS_TRF_8_16, + FCDPROPLUS_TRF_16_32, + FCDPROPLUS_TRF_32_75, + FCDPROPLUS_TRF_75_125, + FCDPROPLUS_TRF_125_250, + FCDPROPLUS_TRF_145, + FCDPROPLUS_TRF_410_875, + FCDPROPLUS_TRF_435, + FCDPROPLUS_TRF_875_2000 +} fcdproplus_rf_filter_value; + +typedef enum +{ + FCDPROPLUS_TIF_200KHZ=0, + FCDPROPLUS_TIF_300KHZ=1, + FCDPROPLUS_TIF_600KHZ=2, + FCDPROPLUS_TIF_1536KHZ=3, + FCDPROPLUS_TIF_5MHZ=4, + FCDPROPLUS_TIF_6MHZ=5, + FCDPROPLUS_TIF_7MHZ=6, + FCDPROPLUS_TIF_8MHZ=7 +} fcdproplus_if_filter_value; + +typedef struct +{ + fcdproplus_rf_filter_value value; + std::string label; +} fcdproplus_rf_filter; + +typedef struct +{ + fcdproplus_if_filter_value value; + std::string label; +} fcdproplus_if_filter; + +class FCDLIB_API FCDProPlusConstants +{ +public: + static const fcdproplus_rf_filter rf_filters[]; + static const fcdproplus_if_filter if_filters[]; + static int fcdproplus_rf_filter_nb_values(); + static int fcdproplus_if_filter_nb_values(); +}; + + +#endif /* FCDLIB_FCDPROPLUSCONST_H_ */ diff --git a/android/app/src/main/cpp/fcdlib/fcdtraits.cpp b/android/app/src/main/cpp/fcdlib/fcdtraits.cpp new file mode 100644 index 0000000..27cf4a6 --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/fcdtraits.cpp @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2020, 2022-2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "fcdtraits.h" + +const char *fcd_traits::alsaDeviceName = "hw:CARD=V10"; +const char *fcd_traits::alsaDeviceName = "hw:CARD=V20"; + +#if defined(__linux__) +const char *fcd_traits::qtDeviceName = "FUNcube_Dongle_V1.0"; +const char *fcd_traits::qtDeviceName = "FUNcube_Dongle_V2.0"; +#else +const char *fcd_traits::qtDeviceName = "FUNcube Dongle V1.0"; +const char *fcd_traits::qtDeviceName = "FUNcube Dongle V2.0"; +#endif + +const char *fcd_traits::hardwareID = "FCDPro"; +const char *fcd_traits::hardwareID = "FCDPro+"; + +const char *fcd_traits::interfaceIID = "sdrangel.samplesource.fcdpro"; +const char *fcd_traits::interfaceIID = "sdrangel.samplesource.fcdproplus"; + +const char *fcd_traits::displayedName = "FunCube Dongle Pro"; +const char *fcd_traits::displayedName = "FunCube Dongle Pro+"; + +const char *fcd_traits::pluginDisplayedName = "FunCube Pro Input"; +const char *fcd_traits::pluginDisplayedName = "FunCube Pro+ Input"; + +const char *fcd_traits::pluginVersion = "7.17.1"; +const char *fcd_traits::pluginVersion = "7.17.1"; + +const int64_t fcd_traits::loLowLimitFreq = 64000000L; +const int64_t fcd_traits::loLowLimitFreq = 150000L; + +const int64_t fcd_traits::loHighLimitFreq = 1700000000L; +const int64_t fcd_traits::loHighLimitFreq = 2000000000L; diff --git a/android/app/src/main/cpp/fcdlib/fcdtraits.h b/android/app/src/main/cpp/fcdlib/fcdtraits.h new file mode 100644 index 0000000..0cb0885 --- /dev/null +++ b/android/app/src/main/cpp/fcdlib/fcdtraits.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef FCDLIB_FCDTRAITS_H_ +#define FCDLIB_FCDTRAITS_H_ + +#include + +#include "export.h" + +typedef enum +{ + Pro, + ProPlus +} fcd_type; + +template +struct FCDLIB_API fcd_traits +{ + static const uint16_t vendorId = 0x0; + static const uint16_t productId = 0x0; + static const int sampleRate = 48000; + static const int convBufSize = (1<<11); + static const char *alsaDeviceName; + static const char *qtDeviceName; + static const char *hardwareID; + static const char *interfaceIID; + static const char *displayedName; + static const char *pluginDisplayedName; + static const char *pluginVersion; + static const int64_t loLowLimitFreq; + static const int64_t loHighLimitFreq; +}; + +template<> +struct FCDLIB_API fcd_traits +{ + static const uint16_t vendorId = 0x04D8; + static const uint16_t productId = 0xFB56; + static const int sampleRate = 96000; + static const int convBufSize = (1<<11); + static const char *alsaDeviceName; + static const char *qtDeviceName; + static const char *hardwareID; + static const char *interfaceIID; + static const char *displayedName; + static const char *pluginDisplayedName; + static const char *pluginVersion; + static const int64_t loLowLimitFreq; + static const int64_t loHighLimitFreq; +}; + +template<> +struct FCDLIB_API fcd_traits +{ + static const uint16_t vendorId = 0x04D8; + static const uint16_t productId = 0xFB31; + static const int sampleRate = 192000; + static const int convBufSize = (1<<10); + static const char *alsaDeviceName; + static const char *qtDeviceName; + static const char *hardwareID; + static const char *interfaceIID; + static const char *displayedName; + static const char *pluginDisplayedName; + static const char *pluginVersion; + static const int64_t loLowLimitFreq; + static const int64_t loHighLimitFreq; +}; + +template const char *fcd_traits::alsaDeviceName = ""; +template const char *fcd_traits::hardwareID = ""; +template const char *fcd_traits::interfaceIID = ""; +template const char *fcd_traits::displayedName = ""; +template const char *fcd_traits::pluginDisplayedName = ""; +template const char *fcd_traits::pluginVersion = "---"; + +#endif /* FCDLIB_FCDTRAITS_H_ */ diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp new file mode 100644 index 0000000..ab5e931 --- /dev/null +++ b/android/app/src/main/cpp/native-lib.cpp @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "demod.h" + +#define BUF_SIZE 8192 + +static std::thread workerThread; +static std::atomic running(false); +static std::atomic sockfd_atomic(-1); + +static std::mutex msgMutex; +static std::vector messageBuffer; + +static std::mutex demodDataMutex; + +static JavaVM *g_vm = nullptr; +static jobject g_obj = nullptr; + +void clientThread(std::string host, int port); + +extern "C" JNIEXPORT void JNICALL +Java_org_noxylva_lbjconsole_flutter_RtlTcpChannelHandler_nativeStopClient(JNIEnv *, jobject) +{ + __android_log_print(ANDROID_LOG_DEBUG, "RTL-TCP", "stop_client"); + running = false; + int old_sockfd = sockfd_atomic.exchange(-1); + if (old_sockfd >= 0) + { + __android_log_print(ANDROID_LOG_DEBUG, "RTL-TCP", "close_sock: %d", old_sockfd); + close(old_sockfd); + } + else + { + __android_log_print(ANDROID_LOG_DEBUG, "RTL-TCP", "sock_closed"); + } + if (workerThread.joinable()) + { + workerThread.detach(); + } + { + std::lock_guard lock(msgMutex); + messageBuffer.clear(); + } + __android_log_print(ANDROID_LOG_DEBUG, "RTL-TCP", "stop_done"); +} + +extern "C" JNIEXPORT void JNICALL +Java_org_noxylva_lbjconsole_flutter_RtlTcpChannelHandler_startClientAsync( + JNIEnv *env, jobject thiz, jstring host_, jstring port_) +{ + const char *host = env->GetStringUTFChars(host_, nullptr); + const char *portStr = env->GetStringUTFChars(port_, nullptr); + int port = atoi(portStr); + if (g_obj == nullptr) + { + env->GetJavaVM(&g_vm); + g_obj = env->NewGlobalRef(thiz); + } + if (workerThread.joinable()) + { + workerThread.detach(); + } + if (running) + { + env->ReleaseStringUTFChars(host_, host); + env->ReleaseStringUTFChars(port_, portStr); + return; + } + running = true; + workerThread = std::thread(clientThread, std::string(host), port); + workerThread.detach(); + env->ReleaseStringUTFChars(host_, host); + env->ReleaseStringUTFChars(port_, portStr); +} + +extern "C" JNIEXPORT jdouble JNICALL +Java_org_noxylva_lbjconsole_flutter_RtlTcpChannelHandler_getSignalStrength(JNIEnv *, jobject) +{ + return (jdouble)magsqRaw; +} +extern "C" JNIEXPORT jboolean JNICALL +Java_org_noxylva_lbjconsole_flutter_RtlTcpChannelHandler_isConnected(JNIEnv *, jobject) +{ + return (running && sockfd_atomic.load() >= 0) ? JNI_TRUE : JNI_FALSE; +} + +extern "C" JNIEXPORT jstring JNICALL +Java_org_noxylva_lbjconsole_flutter_RtlTcpChannelHandler_pollMessages(JNIEnv *env, jobject /*this*/) +{ + std::lock_guard demodLock(demodDataMutex); + + std::lock_guard msgLock(msgMutex); + + if (messageBuffer.empty()) + { + return env->NewStringUTF(""); + } + std::ostringstream ss; + for (auto &msg : messageBuffer) + ss << msg << "\n"; + messageBuffer.clear(); + return env->NewStringUTF(ss.str().c_str()); +} + +void clientThread(std::string host, int port) +{ + int localSockfd = -1; + struct sockaddr_in servaddr; + uint8_t buffer[BUF_SIZE]; + ssize_t n; + int flags = 0; + int connectResult = 0; + bool connected = false; + fd_set writefds; + struct timeval timeout; + int selectResult = 0; + int so_error = 0; + socklen_t len = 0; + const int DECIM = 5; + int decim_counter = 0; + uint32_t acc_i = 0, acc_q = 0; + + localSockfd = socket(AF_INET, SOCK_STREAM, 0); + if (localSockfd < 0) + { + goto cleanup; + } + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_port = htons(port); + servaddr.sin_addr.s_addr = inet_addr(host.c_str()); + flags = fcntl(localSockfd, F_GETFL, 0); + fcntl(localSockfd, F_SETFL, flags | O_NONBLOCK); + connectResult = connect(localSockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); + if (connectResult == 0) + { + connected = true; + } + else if (errno == EINPROGRESS) + { + FD_ZERO(&writefds); + FD_SET(localSockfd, &writefds); + timeout.tv_sec = 5; + timeout.tv_usec = 0; + selectResult = select(localSockfd + 1, NULL, &writefds, NULL, &timeout); + if (selectResult > 0) + { + len = sizeof(so_error); + getsockopt(localSockfd, SOL_SOCKET, SO_ERROR, &so_error, &len); + if (so_error == 0) + { + connected = true; + } + } + } + fcntl(localSockfd, F_SETFL, flags); + if (!connected) + { + __android_log_print(ANDROID_LOG_ERROR, "RTL-TCP", "conn_fail"); + goto cleanup; + } + + lowpassBaud.create(301, SAMPLE_RATE, BAUD_RATE * 5.0f); + phaseDiscri.setFMScaling(SAMPLE_RATE / (2.0f * DEVIATION)); + sockfd_atomic.store(localSockfd); + { + std::lock_guard lock(msgMutex); + messageBuffer.emplace_back("Connected to " + host + ":" + std::to_string(port) + "\n"); + } + + while (running && (n = read(localSockfd, buffer, BUF_SIZE)) > 0) + { + for (int j = 0; j < n; j += 2) + { + acc_i += buffer[j]; + acc_q += buffer[j + 1]; + if (++decim_counter == DECIM) + { + int8_t i_ds = (int8_t)(((float)acc_i / DECIM) - 128); + int8_t q_ds = (int8_t)(((float)acc_q / DECIM) - 128); + + std::lock_guard demodLock(demodDataMutex); + processOneSample(i_ds, q_ds); + + acc_i = acc_q = 0; + decim_counter = 0; + } + } + + if (is_message_ready) + { + std::ostringstream ss; + + std::lock_guard demodLock(demodDataMutex); + std::lock_guard msgLock(msgMutex); + + std::string message_content = alpha_msg.empty() ? numeric_msg : alpha_msg; + ss << "[MSG]" << address << "|" << function_bits << "|" << message_content; + messageBuffer.push_back(ss.str()); + + is_message_ready = false; + + numeric_msg.clear(); + alpha_msg.clear(); + } + } + + if (n < 0 && running) + { + __android_log_print(ANDROID_LOG_ERROR, "RTL-TCP", "read_err: %s", strerror(errno)); + } + else if (n == 0) + { + __android_log_print(ANDROID_LOG_DEBUG, "RTL-TCP", "srv_closed"); + } + +cleanup: + int old_sockfd = sockfd_atomic.exchange(-1); + if (old_sockfd >= 0) + { + close(old_sockfd); + } + + running = false; + __android_log_print(ANDROID_LOG_DEBUG, "RTL-TCP", "thread_exit"); +} \ No newline at end of file diff --git a/android/app/src/main/cpp/util/CRC64.cpp b/android/app/src/main/cpp/util/CRC64.cpp new file mode 100644 index 0000000..085777e --- /dev/null +++ b/android/app/src/main/cpp/util/CRC64.cpp @@ -0,0 +1,160 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/CRC64.h" + +/** +* poly is: x^64 + x^62 + x^57 + x^55 + x^54 + x^53 + x^52 + x^47 + x^46 + x^45 + x^40 + x^39 + +* x^38 + x^37 + x^35 + x^33 + x^32 + x^31 + x^29 + x^27 + x^24 + x^23 + x^22 + x^21 + +* x^19 + x^17 + x^13 + x^12 + x^10 + x^9 + x^7 + x^4 + x^1 + 1 +* +* represented here with lsb = highest degree term +* +* 1100100101101100010101111001010111010111100001110000111101000010_ +* || | | || || | | |||| | | ||| | |||| ||| |||| | | | +* || | | || || | | |||| | | ||| | |||| ||| |||| | | +- x^64 (implied) +* || | | || || | | |||| | | ||| | |||| ||| |||| | | +* || | | || || | | |||| | | ||| | |||| ||| |||| | +--- x^62 +* || | | || || | | |||| | | ||| | |||| ||| |||| +-------- x^57 +* ....................................................................... +* || +* |+---------------------------------------------------------------- x^1 +* +----------------------------------------------------------------- x^0 (1) +*/ +const uint64_t CRC64::m_poly = 0xC96C5795D7870F42ull; + +CRC64::CRC64() +{ + build_crc_table(); +} + +CRC64::~CRC64() +{} + +/** +* input is dividend: as 0000000000000000000000000000000000000000000000000000000000000000<8-bit byte> +* where the lsb of the 8-bit byte is the coefficient of the highest degree term (x^71) of the dividend +* so division is really for input byte * x^64 +* +* you may wonder how 72 bits will fit in 64-bit data type... well as the shift-right occurs, 0's are supplied +* on the left (most significant) side ... when the 8 shifts are done, the right side (where the input +* byte was placed) is discarded +* +* when done, table[XX] (where XX is a byte) is equal to the CRC of 00 00 00 00 00 00 00 00 XX +*/ +void CRC64::build_crc_table() +{ + for(int i = 0; i < 256; ++i) + { + uint64_t crc = i; + + for(unsigned int j = 0; j < 8; ++j) + { + if(crc & 1) // is current coefficient set? + { + crc >>= 1; // yes, then assume it gets zero'd (by implied x^64 coefficient of dividend) + crc ^= m_poly; // and add rest of the divisor + } + else // no? then move to next coefficient + { + crc >>= 1; + } + } + + m_crcTable[i] = crc; + } +} + +/** +* will give an example CRC calculation for input array {0xDE, 0xAD} +* +* each byte represents a group of 8 coefficients for 8 dividend terms +* +* the actual polynomial dividend is: +* +* = DE AD 00 00 00 00 00 00 00 00 (hex) +* = 11011110 10101101 0000000000000000000...0 (binary) +* || |||| | | || | +* || |||| | | || +------------------------ x^71 +* || |||| | | |+-------------------------- x^69 +* || |||| | | +--------------------------- x^68 +* || |||| | +----------------------------- x^66 +* || |||| +------------------------------- x^64 +* || |||| +* || |||+---------------------------------- x^78 +* || ||+----------------------------------- x^77 +* || |+------------------------------------ x^76 +* || +------------------------------------- x^75 +* |+--------------------------------------- x^73 +* +---------------------------------------- x^72 +* +* +* the basic idea behind how the table lookup results can be used with one +* another is that: +* +* Mod(A * x^n, P(x)) = Mod(x^n * Mod(A, P(X)), P(X)) +* +* in other words, an input data shifted towards the higher degree terms +* changes the pre-computed crc of the input data by shifting it also +* the same amount towards higher degree terms (mod the polynomial) +* +* here is an example: +* +* 1) input: +* +* 00 00 00 00 00 00 00 00 AD DE +* +* 2) index crc table for byte DE (really for dividend 00 00 00 00 00 00 00 00 DE) +* +* we get A8B4AFBDC5A6ACA4 +* +* 3) apply that to the input stream: +* +* 00 00 00 00 00 00 00 00 AD DE +* A8 B4 AF BD C5 A6 AC A4 +* ----------------------------- +* 00 A8 B4 AF BD C5 A6 AC 09 +* +* 4) index crc table for byte 09 (really for dividend 00 00 00 00 00 00 00 00 09) +* +* we get 448FCBB7FCB9E309 +* +* 5) apply that to the input stream +* +* 00 A8 B4 AF BD C5 A6 AC 09 +* 44 8F CB B7 FC B9 E3 09 +* -------------------------- +* 44 27 7F 18 41 7C 45 A5 +* +*/ +uint64_t CRC64::calculate_crc(uint8_t *stream, int length) +{ + uint64_t crc = 0; + + for (int i = 0 ; i < length; ++i) + { + uint8_t index = stream[i] ^ crc; + uint64_t lookup = m_crcTable[index]; + + crc >>= 8; + crc ^= lookup; + } + + return crc; +} + + diff --git a/android/app/src/main/cpp/util/CRC64.h b/android/app/src/main/cpp/util/CRC64.h new file mode 100644 index 0000000..7e626eb --- /dev/null +++ b/android/app/src/main/cpp/util/CRC64.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CRC64_H_ +#define INCLUDE_CRC64_H_ + +#include + +#include "export.h" + +class SDRBASE_API CRC64 +{ +public: + CRC64(); + ~CRC64(); + uint64_t calculate_crc(uint8_t *stream, int length); + +private: + void build_crc_table(); + + uint64_t m_crcTable[256]; + static const uint64_t m_poly; +}; + + + +#endif /* INCLUDE_CRC64_H_ */ diff --git a/android/app/src/main/cpp/util/airlines.cpp b/android/app/src/main/cpp/util/airlines.cpp new file mode 100644 index 0000000..1a3f3dd --- /dev/null +++ b/android/app/src/main/cpp/util/airlines.cpp @@ -0,0 +1,5567 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "airlines.h" + +// Data from: https://en.wikipedia.org/wiki/List_of_airline_codes +// MSVC is comically slow if we use QStringLiterals for this data +const char *Airline::Init::m_airlines[] = { + "BOI", "2GO", "ABAIR", "Philippines", + "AAC", "Army Air Corps", "ARMYAIR", "United Kingdom", + "AAD", "Mann Air Ltd", "Ambassador", "United Kingdom", + "AAF", "Aigle Azur", "AIGLE AZUR", "France", + "AAG", "Atlantic Flight Training", "ATLANTIC", "United Kingdom", + "AAH", "Aloha Air Cargo", "ALOHA", "United States", + "AAI", "Air Aurora", "BOREALIS", "United States", + "AAJ", "Alfa Airlines", "ALFA SUDAN", "Sudan", + "AAK", "Alaska Island Air", "ALASKA ISLAND", "United States", + "AAL", "American Airlines", "AMERICAN", "United States", + "AAM", "Aim Air", "", "Moldova", + "AAN", "Oasis International Airlines Now assigned to Boliviana de Aviacion (BoA)", "OASIS", "Spain", + "AAO", "Atlantis Airlines (USA)", "ATLANTIS AIR", "United States", + "AAP", "Arabasco Air Services", "ARABASCO", "Saudi Arabia", + "AAQ", "Copterline", "COPTERLINE", "Finland", + "AAR", "Asiana Airlines", "ASIANA", "South Korea", + "AAS", "Askari Aviation", "AL-ASS", "Pakistan", + "AAT", "Air Central Asia", "", "Kyrgyzstan", + "AAU", "Australia Asia Airlines", "AUSTASIA", "Australia", + "AAV", "Astro Air International", "ASTRO-PHIL", "United States", + "AAW", "Afriqiyah Airways", "AFRIQIYAH", "Libya", + "AAX", "Advance Aviation", "ADVANCE AVIATION", "Thailand", + "AAY", "Allegiant Air", "ALLEGIANT", "United States", + "AAZ", "Asian Air", "", "Kyrgyzstan", + "ABA", "Aero-Beta", "AEROBETA", "Germany", + "ABB", "African Business and Transportations", "AFRICAN BUSINESS", "Democratic Republic of the Congo", + "ABD", "Air Atlanta Icelandic", "ATLANTA", "Iceland", + "ABE", "Aban Air", "ABAN", "Iran", + "ABF", "Aerial Oy", "SKYWINGS", "Finland", + "ABG", "Abakan-Avia", "ROYAL FLIGHT", "Russia", + "ABH", "Hokuriki-Koukuu Company", "", "Japan", + "ABL", "Air Busan", "AIR BUSAN", "South Korea", + "ABM", "Aero Albatros", "ALBATROS ESPANA", "Spain", + "ABN", "Air Albania", "AIR ALBANIA", "Albania", + "ABO", "APSA Colombia", "AEROEXPRESO", "Colombia", + "ABP", "ABS Jets", "BAIR", "Czech Republic", + "ABQ", "Airblue", "PAKBLUE", "Pakistan", + "ABR", "ASL Airlines Ireland", "CONTRACT", "Ireland", + "ABS", "Transwest Air", "ATHABASKA", "Canada", + "ABT", "Ambeo", "AMBITION", "United Kingdom", + "ABU", "Eagle Aviation Services", "", "United States", + "ABV", "Antrak Air", "ANTRAK", "Ghana", + "ABW", "AirBridge Cargo", "AIRBRIDGE CARGO", "Russia", + "ABX", "ABX Air", "ABEX", "United States", + "ABY", "Air Arabia", "ARABIA", "United Arab Emirates", + "ABZ", "Air Ambulance Services", "ISLAND LIFEFLIGHT", "Bahamas", + "ACA", "Air Canada", "AIR CANADA", "Canada", + "ACB", "African Cargo Services", "AFRICARGO", "Kenya", + "ACC", "Airspeed Charter", "", "Jamaica", + "ACD", "Academy Airlines", "ACADEMY", "United States", + "ACE", "Air Charter Express", "", "Ghana", + "ACF", "Centro de Formación Aeronáutica de Canarias", "FORCAN", "Spain", + "ACG", "Air Partner", "AIR PARTNER", "United Kingdom", + "ACH", "Air Cargo Plus", "AIR PLUS", "Liberia", + "ACI", "Air Caledonie International", "AIRCALIN", "France", + "ACJ", "Avicon", "AVICHARTER", "Kenya", + "ACK", "Nantucket Airlines", "ACK AIR", "United States", + "ACL", "Itali Airlines", "SPADA", "Italy", + "ACM", "Air Caledonia", "WEST CAL", "Canada", + "ACN", "Central American Airlines", "AEROCENTRO", "Nicaragua", + "ACP", "Astral Aviation", "ASTRAL CARGO", "Kenya", + "ACQ", "Aryan Cargo Express", "", "India", + "ACR", "Aerocenter Escuela de Formación de Pilotos Privados de Avión", "AEROCENTER", "Spain", + "ACS", "Aircraft Sales and Services", "AIRCRAFT SALES", "Pakistan", + "ACT", "Flight Line", "AMERICAN CHECK", "United States", + "ACU", "Air Cargo Transportation System", "AFRISPIRIT", "Kenya", + "ACW", "Royal Air Force", "AIR CADET", "United Kingdom", + "ACY", "Fly Arna", "ARNA", "Armenia", + "ADA", "Airservices Australia", "AUSCAL", "Australia", + "ADB", "Antonov Airlines", "ANTONOV BUREAU", "Ukraine", + "ADC", "AD Astra Executive Charter", "AD ASTRA", "Poland", + "ADF", "Ade Aviación Deportiva", "ADE AVIACION", "Spain", + "ADG", "Aerea Flying Training Organization", "AEREA TRAINING", "Spain", + "ADI", "Audeli Air", "AUDELI", "Spain", + "ADL", "Aero Dynamics", "COTSWOLD", "United Kingdom", + "ADN", "Aero-Dienst", "AERODIENST", "Germany", + "ADO", "AIRDO", "AIR DO", "Japan", + "ADP", "Aerodiplomatic", "AERODIPLOMATIC", "Mexico", + "ADQ", "Avion Taxi", "AIR DATA", "Canada", + "ADS", "Aviones de Sonora", "SONORAV", "Mexico", + "ADT", "Arrendaminetos y Transportes Turisticos", "ARRENDA-TRANS", "Mexico", + "ADU", "Airdeal Oy", "AIRDEAL", "Finland", + "ADV", "Advanced Flight Training", "ADVANCED", "United Kingdom", + "ADX", "Anderson Aviation", "ANDAX", "United States", + "ADY", "Air Arabia Abu Dhabi", "Nawras [6][7]", "United Arab Emirates", + "ADZ", "Avio Delta", "", "Bulgaria", + "AEA", "Air Europa", "EUROPA", "Spain", + "AEE", "Aegean Airlines", "AEGEAN", "Greece", + "AEF", "Aerea", "", "Spain", + "AEH", "Aero4m", "AEROCUTTER", "Slovenia", + "AEJ", "Air Express", "KHAKI EXPRESS", "Tanzania", + "AEK", "Aerocon", "AEROCON", "Bolivia", + "AEM", "Aero Madrid", "AEROMADRID", "Spain", + "AEO", "Aeroservicios Ejecutivos Del Occidente", "AERO OCCIDENTE", "Mexico", + "AEP", "Aerotec Escuela de Pilotos", "AEROTEC", "Spain", + "AEQ", "Air Express", "LUNA", "Sweden", + "AER", "Alaska Central Express", "ACE AIR", "United States", + "AES", "Aerosur Paraguay", "AEROPARAGUAY", "Paraguay", + "AEV", "Aeroventas", "AEROVENTAS", "Mexico", + "AEY", "Air Italy", "AIR ITALY", "Italy", + "AEZ", "Aerial Transit", "AERIAL TRANZ", "United States", + "AFA", "Alfa Air", "BLUE ALFA", "Czech Republic", + "AFC", "African West Air", "AFRICAN WEST", "Senegal", + "AFD", "Panorama Flight Service", "AIRFED", "United States", + "AFE", "Airfast Indonesia", "AIRFAST", "Indonesia", + "AFG", "Ariana Afghan Airlines", "ARIANA", "Afghanistan", + "AFI", "Africaone", "AFRICAWORLD", "The Gambia", + "AFK", "Africa Air Links", "AFRICA LINKS", "Sierra Leone", + "AFL", "Aeroflot", "AEROFLOT", "Russia", + "AFM", "AEROSPEED FORMATION ET MAINTENANCE", "EPIC AIR", "France", + "AFN", "African International Airlines", "SIMBA", "Lesotho", + "AFO", "Aero Empresa Mexicana", "AERO EMPRESA", "Mexico", + "AFP", "Portuguese Air Force", "PORTUGUESE AIR FORCE", "Portugal", + "AFQ", "Alba Servizi Aerotrasporti", "ALBA", "Italy", + "AFR", "Air France", "AIRFRANS", "France", + "AFS", "Air Data", "", "United Kingdom", + "AFV", "Air Afrique Vacancies", "AFRIQUE VACANCE", "Côte d'Ivoire", + "AFW", "Africa World Airlines", "BLACKSTAR", "Ghana", + "AFY", "Africa Chartered Services", "AFRICA CHARTERED", "Nigeria", + "AFZ", "Africa Freight Services", "AFREIGHT", "Zambia", + "AGA", "AG Air", "GEOLINE", "Georgia", + "AGC", "Arab Agricultural Aviation Company", "AGRICO", "Egypt", + "AGE", "Servicios Aéreos de Los Ángeles", "AEROANGEL", "Mexico", + "AGH", "Altagna", "ALTAGNA", "France", + "AGI", "Aereo Transportes Los Angeles de America", "ANGELES AMERICA", "Mexico", + "AGM", "Aviation West Charters", "ANGEL MED", "United States", + "AGO", "Angola Air Charter", "ANGOLA CHARTER", "Angola", + "AGP", "AERFI Group", "AIR TARA", "Ireland", + "AGR", "United States Department Of Agriculture", "AGRICULTURE", "United States", + "AGT", "Amadeus IT Group", "AMADEUS", "Spain", + "AGU", "Angara Airlines", "SARMA", "Russia", + "AGV", "Air Glaciers", "AIR GLACIERS", "Switzerland", + "AGY", "Aero Flight Service", "FLIGHT GROUP", "United States", + "AGZ", "Agrolet-Mci", "AGROLET", "Slovakia", + "AHA", "Air Alpha Greenland", "AIR ALPHA", "Denmark", + "AHC", "Azal Avia Cargo", "AZALAVIACARGO", "Azerbaijan", + "AHD", "Czech Air Handling", "AIRHANDLING", "Czech Republic", + "AHE", "Airport Helicopter Basel Muller & Co.", "AIRPORT HELICOPTER", "Switzerland", + "AHF", "Aspen Helicopters", "ASPEN", "United States", + "AHH", "Airplanes Holdings", "AIRHOLD", "Ireland", + "AHI", "Servicios Aéreos de Chihuahua Aerochisa", "AEROCHISA", "Mexico", + "AHK", "Air Hong Kong", "AIR HONG KONG", "Hong Kong", + "AHL", "Aerolíneas Hidalgo", "HIDALGO", "Mexico", + "AHM", "Garrison Aviation", "AIR HURON", "Canada", + "AHN", "Air Hungaria", "AIR HUNGARIA", "Hungary", + "AHO", "Air Hamburg", "AIR HAMBURG", "Germany", + "AHP", "Aerochiapas", "AEROCHIAPAS", "Mexico", + "AHS", "Air Viggi San Raffaele", "AIRSAR", "Italy", + "AHT", "HTA Helicopteros", "HELIAPRA", "Portugal", + "AHY", "Azerbaijan Airlines", "AZAL", "Azerbaijan", + "AIA", "Avies", "AVIES", "Estonia", + "AIB", "Airbus Industrie", "AIRBUS INDUSTRIE", "France", + "AIC", "Air India Limited", "AIRINDIA", "India", + "AID", "Christian Konig - Century Airbirds", "CENTURY AIRBIRD", "Austria", + "AIE", "Air Inuit", "AIR INUIT", "Canada", + "AIF", "Compañía Aérea de Valencia", "", "Spain", + "AIG", "Air Inter Gabon", "", "Gabon", + "AIH", "Air Incheon", "AIR INCHEON", "South Korea", + "AII", "Air Integra", "INTEGRA", "Canada", + "AIJ", "Interjet", "ABC AEROLINEAS", "Mexico", + "AIK", "African Airlines International Limited", "AFRICAN AIRLINES", "Kenya", + "AIM", "Trabajos Aéreos Murcianos", "PIJO", "Spain", + "AIN", "African International Airways", "FLY CARGO", "Eswatini", + "AIO", "United States Air Force", "AIR CHIEF", "United States", + "AIP", "Alpine Air Express", "ALPINE AIR", "United States", + "AIQ", "Thai AirAsia", "THAI ASIA", "Thailand", + "AIS", "Air Sureste", "SURESTE", "Spain", + "AIT", "Airest", "AIREST CARGO", "Estonia", + "AIU", "Alicante Internacional Airlines", "ALIA", "Spain", + "AIV", "Airvias S/A Linhas Aéreas", "AIRVIAS", "Brazil", + "AIW", "Atlantic Island Airways", "TARTAN", "Canada", + "AIX", "Aircruising Australia", "CRUISER", "Australia", + "AIY", "Aircrew Check and Training Australia", "AIRCREW", "Australia", + "AIZ", "Arkia Israel Airlines", "ARKIA", "Israel", + "AJA", "Afghan Jet International Airlines", "AFGHAN JET", "Afghanistan", + "AJB", "Aero JBR", "AERO JBR", "Mexico", + "AJC", "Bar Harbor Airlines", "BAR HARBOR", "United States", + "AJF", "Avia Consult Flugbetriebs", "AVIACONSULT", "Austria", + "AJH", "Aeroaljarafe", "ALJARAFE", "Spain", + "AJI", "Ameristar Jet Charter", "AMERISTAR", "United States", + "AJJ", "A2 Jet Leasing", "ATLANTIC JET", "United States", + "AJK", "Allied Air", "BAMBI", "Nigeria", + "AJP", "Aero Jets Corporativos", "AEROJETS", "Mexico", + "AJR", "A-Jet Aviation Company", "JET MONGOLIA", "Mongolia", + "AJS", "Aeroejecutivos Colombia", "AEROEJECUTIVOS", "Colombia", + "AJT", "Amerijet International", "AMERIJET", "United States", + "AJU", "Air Jetsul", "AIRJETSUL", "Portugal", + "AJV", "ANA & JP Express", "AYJAY CARGO", "Japan", + "AJW", "Alpha Jet International", "ALPHAJET", "United States", + "AJX", "Air Japan", "AIR JAPAN", "Japan", + "AKA", "Air Korea Co. Ltd.", "", "Republic of Korea", + "AKB", "Aktjubavia", "KARAB", "Kazakhstan", + "AKC", "Arca Aerovías Colombianas Ltda.", "ARCA", "Colombia", + "AKF", "Anikay Air Company", "ANIKAY", "Kyrgyzstan", + "AKG", "No. 84 Squadron RAF", "GRIFTER", "United Kingdom", + "AKH", "Akhal", "AKHAL", "Turkmenistan", + "AKI", "Ibk-Petra", "", "Sudan", + "AKJ", "Akasa Air", "AKASA AIR", "India", + "AKK", "Aklak Air", "AKLAK", "Canada", + "AKL", "Air Kiribati", "KIRIBATI", "Kiribati", + "AKM", "Mak Air", "MAKAIR", "Kazakhstan", + "AKN", "Alkan Air", "ALKAN AIR", "Canada", + "AKR", "Aero Clinker", "AERO CLINKER", "Mexico", + "AKT", "Canadian North", "ARCTIC", "Canada", + "AKX", "Air Nippon Network Co. Ltd.", "ALFA WING", "Japan", + "AKY", "Yak-Service", "YAK-SERVICE", "Russia", + "ALB", "Aero Albatros", "ALBATROS", "Mexico", + "ALC", "Southern Jersey Airways Inc.", "ACOM", "United States", + "ALD", "Albion Aviation", "ALBION", "United Kingdom", + "ALF", "Allied Command Europe (Mobile Force)", "ACEFORCE", "Belgium", + "ALG", "Air Logistics", "AIRLOG", "United States", + "ALJ", "Heli Ambulance Team", "ALPIN HELI", "Austria", + "ALK", "SriLankan Airlines", "SRILANKAN", "Sri Lanka", + "ALL", "Aerovallarta", "VALLARTA", "Mexico", + "ALN", "Air Lincoln", "CHICAGO LINCOLN", "United States", + "ALO", "Allegheny Commuter Airlines", "ALLEGHENY", "United States", + "ALP", "Allpoints Jet", "ALLPOINTS", "China", + "ALQ", "Altair Aviation (1986)", "ALTAIR", "Canada", + "ALR", "Líneas Aéreas Alaire S.L.", "AEROLAIRE", "Spain", + "ALS", "Aeralp", "AERALP", "France", + "ALT", "Aerolíneas Centrales", "AERLINEAS CENTRALES", "Mexico", + "ALU", "Air Luxor STP", "LUXORJET", "São Tomé and Príncipe", + "ALV", "Aeropostal Alas de Venezuela", "AEROPOSTAL", "Venezuela", + "ALW", "Alas Nacionales S.A.", "ALNACIONAL", "Dominican Republic", + "ALX", "ALPI Jets", "ALPIJETS", "Austria", + "ALY", "Alyeska Air Service", "ALYESKA", "United States", + "ALZ", "Alta Flights (Charters) Ltd.", "", "Canada", + "AMA", "ATMA", "ADIK", "Kazakhstan", + "AMB", "Deutsche Rettungsflugwacht", "CIVIL AIR AMBULANCE", "Germany", + "AMC", "Air Malta", "AIR MALTA", "Malta", + "AME", "Spanish Air Force", "AIRMIL", "Spain", + "AMF", "Ameriflight", "AMFLIGHT", "United States", + "AMJ", "Aviation Amos", "AVIATION AMOS", "Canada", + "AMK", "Amerer Air", "AMER AIR", "Austria", + "AMM", "Aeroputul International Marculesti", "AEROM", "Moldova", + "AMO", "Air Montreal (Air Holdings Inc.)", "AIR MONTREAL", "Canada", + "AMP", "Aero Transporte S.A. (ATSA)", "ATSA", "Peru", + "AMQ", "Aircraft Management and Consulting", "AMEX", "Poland", + "AMR", "Air Specialties Corporation", "AIR AM", "United States", + "AMS", "Air Muskoka", "AIR MUSKOKA", "Canada", + "AMU", "Air Macau", "AIR MACAO", "Macao", + "AMV", "AMC Airlines", "AMC AIRLINES", "Egypt", + "AMW", "Armenia Airways", "ARMENIA", "Armenia", + "AMX", "Aeroméxico", "AEROMEXICO", "Mexico", + "AMY", "Air Ambar", "AIR AMBAR", "Dominican Republic", + "AMZ", "Amiya Airline", "AMIYA AIR", "Nigeria", + "ANA", "All Nippon Airways", "ALL NIPPON", "Japan", + "ANB", "Air Navigation And Trading Co. Ltd.", "AIR NAV", "United Kingdom", + "AND", "National Jet Service", "AIR INDIANA", "United States", + "ANE", "Air Nostrum", "AIR NOSTRUM", "Spain", + "ANG", "Air Niugini", "NIUGINI", "Papua New Guinea", + "ANH", "Alajnihah for Air Transport", "ALAJNIHAH", "Libya", + "ANI", "Air Atlantic (Nig) Limited", "NIGALANTIC", "Nigeria", + "ANL", "Nacoia Lda", "AIR NACOIA", "Angola", + "ANM", "Aerotransportacion de Norteamerica", "NORAM", "Mexico", + "ANO", "Airnorth", "TOPEND", "Australia", + "ANQ", "Aerolínea de Antioquia", "ANTIOQUIA", "Colombia", + "ANS", "Andes Líneas Aéreas", "AEROANDES", "Argentina", + "ANT", "Air North Charter - Canada", "AIR NORTH", "Canada", + "ANV", "Air Nevada", "AIR NEVADA", "United States", + "ANX", "Secretaria de Marina", "SECRETARIA DEMARINA", "Mexico", + "ANZ", "Air New Zealand", "NEW ZEALAND", "New Zealand", + "AOA", "Alcon Servicios Aéreos S.A. de C.V.", "ALCON", "Mexico", + "AOB", "Aerocaribe Coro", "CARIBE CORO", "Venezuela", + "AOC", "AVCOM", "AERO AVCOM", "Russia", + "AOD", "Aero Vodochody", "AERO CZECH", "Czech Republic", + "AOE", "Livingstone Executive", "LIVINGSTONE AIR", "Italy", + "AOF", "Atair Pty Ltd.", "ATAIR", "South Africa", + "AOJ", "Avcon Jet", "ASTERIX", "Austria", + "AON", "Aero Entreprise", "AERO ENTERPRISE", "France", + "AOO", "As Opened Joint Stock Company", "COMPANY AS", "Ukraine", + "AOP", "Aeropiloto", "AEROPILOTO", "Portugal", + "AOR", "Afro International Ent. Limited", "INTER-AFRO", "Nigeria", + "AOS", "Servicios Aéreos Del Sol S.A. de C.V.", "AEROSOL", "Mexico", + "AOT", "Asia Overnight Express", "ASIA OVERNIGHT", "Philippines", + "AOU", "Air Tractor", "AIR TRACTOR", "Croatia", + "AOV", "Aero Vision", "AEROVISION", "France", + "AOX", "Aerotaxi Del Valle", "AEROVALLE", "Colombia", + "APA", "Air Park Aviation Ltd.", "CAN-AM", "Canada", + "APC", "Airpac Airlines Inc.", "AIRPAC", "United States", + "APD", "Sabre Pacific", "", "Australia", + "APE", "Parcel Express", "AIR PARCEL", "United States", + "APF", "Amapola Flyg AB", "AMAPOLA", "Sweden", + "APG", "Air People International", "AIR PEOPLE", "Thailand", + "APH", "Alpha Aviation Inc.", "AIRFLIGHT", "United States", + "API", "ASA Pesada Lda.", "ASA PESADA", "Angola", + "APJ", "Peach Aviation", "AIR PEACH", "Japan", + "APL", "Appalachian Flying Service Inc.", "APPALACHIAN", "United States", + "APM", "Airpac Inc.", "ALASKA PACIFIC", "United States", + "APN", "Alpine Airlines", "AIR ALPES", "France", + "APP", "AlpAvia d.o.o.", "ALPAVIA", "Slovenia", + "APQ", "Aspen Aviation", "ASPEN BASE", "United States", + "APR", "Aerolíneas Primordiales", "AEROPERLAS", "Mexico", + "APT", "LAP Colombia - Líneas Aéreas Petroleras S.A.", "LAP", "Colombia", + "APU", "Aeropuma S.A.", "AEROPUMA", "El Salvador", + "APV", "Air Plan International", "AIR PLAN", "Democratic Republic of the Congo", + "APX", "Apex Air Cargo", "PARCEL EXPRESS", "United States", + "APY", "APA Internacional", "APA INTERNACIONAL", "Dominican Republic", + "APZ", "Air Premia", "AIR PREMIA", "South Korea", + "AQA", "Aeroatlas S.A.", "ATCO", "Colombia", + "AQL", "Aquila Air Ltd.", "AQUILA", "Canada", + "AQO", "Aluminum Company Of America", "ALCOA SHUTTLE", "United States", + "AQT", "Avioquintana", "AVIOQUINTANA", "Mexico", + "AQZ", "Aerodyne Charter Company", "QUANZA", "United States", + "ARA", "Arik Air", "ARIK AIR", "Nigeria", + "ARB", "Avia Air N.V.", "AVIAIR", "Aruba", + "ARC", "Air Routing International Corp.", "", "United States", + "ARE", "LATAM Colombia", "LAN COLOMBIA", "Colombia", + "ARG", "Aerolíneas Argentinas", "ARGENTINA", "Argentina", + "ARH", "Arrowhead Airways", "ARROWHEAD", "United States", + "ARI", "Aero Vics", "AEROVICS", "Mexico", + "ARJ", "Aerojet de Costa Rica S.A.", "", "Costa Rica", + "ARK", "Aero Link Air Services S.L.", "LINK SERVICE", "Spain", + "ARL", "Airlec - Air Aquitaine Transport", "AIRLEC", "France", + "ARO", "Acero Taxi", "ACERO", "Mexico", + "ARP", "Aero Corporate", "IVORYCORP", "Côte d'Ivoire", + "ARQ", "Armstrong Air Inc.", "ARMSTRONG", "Canada", + "ARR", "Air Armenia", "AIR ARMENIA", "Armenia", + "ARS", "Aeromet Servicios", "METSERVICE", "Chile", + "ART", "Smartlynx Airlines", "SMART LYNX", "Latvia", + "ARU", "Aruba Airlines", "ARUBA", "Aruba", + "ARV", "Aravco Ltd.", "ARAVCO", "United Kingdom", + "ARW", "Aria", "ARIABIRD", "France", + "ARX", "Air Xpress Inc.", "AIREX", "United States", + "ARY", "Argosy Airways", "GOSEY", "United States", + "ARZ", "Air Resorts", "AIR RESORTS", "United States", + "ASA", "Alaska Airlines Inc.", "ALASKA", "United States", + "ASB", "Air-Spray 1967 Ltd.", "AIR SPRAY", "Canada", + "ASC", "Air Star Corporation", "AIR STAR", "Canada", + "ASD", "Air Sinai", "AIR SINAI", "Egypt", + "ASF", "Austrian Air Force", "AUSTRIAN AIRFORCE", "Austria", + "ASG", "African Star Airways (PTY) Ltd.", "AFRICAN STAR", "South Africa", + "ASH", "Mesa Airlines", "AIR SHUTTLE", "United States", + "ASI", "Aerosun International Inc.", "AEROSUN", "United States", + "ASK", "Aerosky", "MULTISKY", "Spain", + "ASL", "Air Serbia", "AIR SERBIA", "Serbia", + "ASM", "Awesome Flight Services (PTY) Ltd.", "AWESOME", "South Africa", + "ASN", "Air and Sea Transport", "", "Russia", + "ASO", "Aero Slovakia", "AERO NITRA", "Slovakia", + "ASP", "Airsprint", "AIRSPRINT", "Canada", + "ASQ", "ExpressJet", "ACEY", "United States", + "ASR", "All Star Airlines Inc.", "ALL STAR", "United States", + "ASS", "Air Class S.A. de C.V.", "AIR CLASS", "Mexico", + "AST", "Aerolíneas Del Oeste", "AEROESTE", "Mexico", + "ASV", "Air Seoul", "AIR SEOUL", "South Korea", + "ASX", "Air Special", "AIRSPEC", "Czech Republic", + "ASY", "Royal Australian Air Force", "AUSSIE", "Australia", + "ATA", "Air Transport Association", "", "United States", + "ATB", "Atlantair Ltd.", "STARLITE", "Canada", + "ATC", "Air Tanzania", "TANZANIA", "Tanzania", + "ATD", "Aerotours Dominicana", "AEROTOURS", "Dominican Republic", + "ATE", "Atlantis Transportation Services Ltd.", "ATLANTIS CANADA", "Canada", + "ATF", "Compañía Aerotécnicas Fotográficas", "AEROTECNICAS", "Spain", + "ATG", "Aerotranscargo[9]", "MOLDCARGO", "Moldova", + "ATH", "Air Travel Corp.", "AIR TRAVEL", "United States", + "ATJ", "Air Traffic GmbH", "SNOOPY", "Germany", + "ATL", "Atlas Air Service AG", "AIR BREMEN", "Germany", + "ATM", "Airlines of Tasmania", "AIRTAS", "Australia", + "ATN", "Air Transport International", "AIR TRANSPORT", "United States", + "ATP", "ASTRAL Colombia - Aerotransportes Especiales Ltda.", "ASTRAL", "Colombia", + "ATQ", "Air Transport Schiphol", "MULTI", "Netherlands", + "ATR", "Atlas Airlines", "ATLAS-AIR", "United States", + "ATS", "Air Transport Service", "", "Democratic Republic of the Congo", + "ATT", "Attawasol Airlines", "ATTAWASOL AIR", "Libya", + "ATU", "Atlant Aerobatics Ltd.", "ATLANT HUNGARY", "Hungary", + "ATV", "Avanti Air", "AVANTI AIR", "Germany", + "ATX", "Warwickshire Aerocentre Ltd.", "AIRTAX", "United Kingdom", + "AUA", "Austrian Airlines", "AUSTRIAN", "Austria", + "AUD", "Audi Air Inc.", "AUDI AIR", "United States", + "AUF", "Augusta Air Luftfahrtunternehmen", "AUGUSTA", "Germany", + "AUH", "Presidential Flight", "SULTAN", "United Arab Emirates", + "AUI", "Ukraine International Airlines", "UKRAINE INTERNATIONAL", "Ukraine", + "AUJ", "Business Flight Salzburg", "AUSTROJET", "Austria", + "AUL", "Nordavia", "ARCHANGELSK AIR", "Russia", + "AUM", "Air Atlantic Uruguay", "ATLAMUR", "Uruguay", + "AUN", "Common Sky", "COMMON SKY", "Austria", + "AUO", "Empresa (Aero Uruguay) S.A.", "UNIFORM OSCAR", "Uruguay", + "AUP", "Avia Business Group", "", "Russia", + "AUR", "Aurigny Air Services", "AYLINE", "United Kingdom", + "AUT", "Austral Líneas Aéreas", "AUSTRAL", "Argentina", + "AUX", "Air Uganda International Ltd.", "", "Uganda", + "AUY", "Aerolíneas Uruguayas S.A.", "AUSA", "Uruguay", + "AUZ", "Australian Airlines", "AUSTRALIAN", "Australia", + "AVA", "Avianca", "AVIANCA", "Colombia", + "AVB", "Aviation Beauport", "BEAUPAIR", "United Kingdom", + "AVD", "Álamo Aviación S.L.", "ALAMO", "Spain", + "AVF", "Aviair Aviation Ltd.", "CARIBOO", "Canada", + "AVG", "Aviation Legacy", "AVILEF", "Gambia", + "AVH", "AV8 Helicopters", "KENT HELI", "United Kingdom", + "AVJ", "Avia Traffic Company", "ATOMIC", "Kyrgyzstan", + "AVK", "AV8 Helicopters", "AVIATE-COPTER", "South Africa", + "AVM", "Aviación Ejecutiva Mexicana S.A.", "AVEMEX", "Mexico", + "AVN", "Air Vanuatu", "AIR VAN", "Vanuatu", + "AVO", "Aviation at Work", "AVIATION WORK", "South Africa", + "AVP", "Aviacion Corporativa de Peubla", "AVIA PUEBLA", "Mexico", + "AVQ", "Aviation Services Inc.", "AQUILINE", "United States", + "AVR", "Active Aero Charter Inc.", "ACTIVE AERO", "United States", + "AVS", "Avialsa T-35", "AVIALSA", "Spain", + "AVU", "Avia Sud Aérotaxi", "AVIASUD", "France", + "AVV", "Airvantage Incorporated", "AIRVANTAGE", "United States", + "AVW", "Aviator Airways", "AVIATOR", "Greece", + "AVX", "Aeroclub de Vitoria", "", "Spain", + "AVY", "Aerovaradero S.A.", "AEROVARADERO", "Cuba", + "AVZ", "Air Valencia", "AIR VALENCIA", "Spain", + "AWB", "Airways International Inc.", "AIRNAT", "United States", + "AWC", "Titan Airways", "ZAP", "United Kingdom", + "AWD", "Providence Aviation Services", "", "Pakistan", + "AWE", "America West Airlines", "CACTUS", "United States", + "AWF", "Aeroforward", "", "United States", + "AWG", "Animawings", "ANIMA WINGS", "Romania", + "AWI", "Air Wisconsin", "WISCONSIN", "United States", + "AWJ", "Sahel Airlines", "SAHEL AIRLINES", "Niger", + "AWK", "Airwork", "AIRWORK", "New Zealand", + "AWL", "Australian Wetleasing", "AUSSIEWORLD", "Australia", + "AWN", "Air Niamey", "AIR NIAMEY", "Niger", + "AWO", "Awood Air Ltd.", "AWOOD AIR", "Canada", + "AWP", "Aeroworld Pakistan", "", "Pakistan", + "AWQ", "Indonesia AirAsia", "WAGON AIR", "Indonesia", + "AWR", "Arctic Wings And Rotors Ltd.", "ARCTIC WINGS", "Canada", + "AWS", "Arab Wings", "ARAB WINGS", "Jordan", + "AWT", "Albawings", "ALBAWINGS", "Albania", + "AWU", "Sylt Air GmbH", "SYLT-AIR", "Germany", + "AWV", "Airwave Transport Inc.", "AIRWAVE", "Canada", + "AWX", "Civil Aviation Authority Directorate of Airspace Policy", "ALLWEATHER", "United Kingdom", + "AWY", "Aeroway S.L.", "AEROWEE", "Spain", + "AWZ", "AirWest", "", "Sudan", + "AXB", "Air India Express", "EXPRESS INDIA", "India", + "AXD", "Air Express", "AIR SUDEX", "Sudan", + "AXE", "AirExplore", "GALILEO", "Slovakia", + "AXH", "Aeromexhaga", "AEROMEXHAGA", "Mexico", + "AXI", "Aeron International Airlines Inc.", "AIR FREIGHTER", "United States", + "AXK", "African Express Airways", "EXPRESS JET", "Kenya", + "AXP", "Aeromax", "AEROMAX SPAIN", "Spain", + "AXQ", "Action Airlines (Action Air Charter)", "ACTION AIR", "United States", + "AXR", "Axel Rent S.A.", "RENTAXEL", "Mexico", + "AXS", "Altus Airlines", "ALTUS", "United States", + "AXV", "Aviaxess", "AXAVIA", "France", + "AXX", "Advance Air Luftfahrtgesellschaft", "SKY SHUTTLE", "Germany", + "AXY", "Air X Charter", "LEGEND", "Malta", + "AYB", "Belgian Army", "BELGIAN ARMY", "Belgium", + "AYE", "Yunnan Yingan Airlines", "AIR YING AN", "China", + "AYG", "Yangon Airways", "AIR YANGON", "Burma", + "AYM", "Airman S.L.", "AIRMAN", "Spain", + "AYN", "Atlantic Airlines S.A.", "ATLANTIC NICARAGUA", "Nicaragua", + "AYR", "Flight Training Europe", "CYGNET", "Spain", + "AYT", "Ayeet Aviation & Tourism", "AYEET", "Israel", + "AYU", "Yuhi Air Lines", "", "Japan", + "AZB", "Zaab Air", "ZAAB AIR", "Ghana", + "AZE", "Arcus-Air Logistic", "ARCUS AIR", "Germany", + "AZF", "Air Zermatt AG", "AIR ZERMATT", "Switzerland", + "AZG", "Silk Way West Airlines", "SILK WEST", "Azerbaijan", + "AZI", "Astra Airlines", "ASTRA", "Greece", + "AZJ", "Zas Air", "", "Egypt", + "AZK", "Azalhelikopter", "AZALHELICOPTER", "Azerbaijan", + "AZM", "Aerocozumel", "AEROCOZUMEL", "Mexico", + "AZN", "Línea Aérea Amaszonas", "", "Bolivia", + "AZP", "Arizona Pacific Airways", "ARIZONA PACIFIC", "United States", + "AZQ", "Silk Way Airlines", "SILK LINE", "Azerbaijan", + "AZR", "Zenith Air", "ZENAIR", "South Africa", + "AZS", "Aviacon Zitotrans Air Company", "ZITOTRANS", "Russia", + "AZT", "Azimut S.A.", "AZIMUT", "Spain", + "AZU", "Azul Linhas Aéreas Brasileiras", "Azul", "Brazil", + "AZW", "Air Zimbabwe", "AIR ZIMBABWE", "Zimbabwe", + "AZY", "Aztec Worldwide Airlines", "AZTEC WORLD", "United States", + "AZZ", "Azza Transport", "AZZA TRANSPORT", "Sudan", + "BAA", "Balkan Agro Aviation", "BALKAN AGRO", "Bulgaria", + "BAB", "Bahrain Air BSC (Closed)", "AWAL", "Bahrain", + "BAC", "BAC Leasing Limited", "", "United Kingdom", + "BAE", "BAE Systems", "FELIX", "United Kingdom", + "BAF", "Belgian Air Force", "BELGIAN AIRFORCE", "Belgium", + "BAG", "Dba", "SPEEDWAY", "Germany", + "BAH", "The Amiri Flight", "BAHRAIN", "Bahrain", + "BAJ", "Baker Aviation", "RODEO", "United States", + "BAK", "Blackhawk Airways", "BLACKHAWK", "United States", + "BAL", "Belle Air Europe", "BELLEAIR EUROPE", "Italy", + "BAM", "Business Air Services", "BUSINESS AIR", "Canada", + "BAN", "British Antarctic Survey", "PENGUIN", "United Kingdom", + "BAP", "Trans International Express Aviation", "BIG APPLE", "United States", + "BAR", "Bradly Air (Charter) Services", "BRADLEY", "Canada", + "BAS", "Aero Services", "AEROSERV", "Barbados", + "BAT", "Premiair", "BALLISTIC", "Luxembourg", + "BAU", "Bissau Airlines", "AIR BISSAU", "Guinea-Bissau", + "BAV", "Bamboo Airways", "BAMBOO", "Vietnam", + "BAW", "British Airways", "SPEEDBIRD", "United Kingdom", + "BAX", "Best Aero Handling Ltd", "", "Russia", + "BAY", "Bravo Airways", "BRAVOAVIANCA", "Ukraine", + "BBA", "Bannert Air", "BANAIR", "Austria", + "BBB", "SwedJet Airways", "BLACKBIRD", "Sweden", + "BBC", "Biman Bangladesh Airlines", "BANGLADESH", "Bangladesh", + "BBD", "Bluebird Nordic", "BLUE CARGO", "Iceland", + "BBE", "Ababeel Aviation", "BABEL AIR", "Sudan", + "BBF", "B-Air Charter", "SPEEDCHARTER", "Germany", + "BBJ", "Blue Air Lines", "BLUE KOREA", "South Korea", + "BBL", "IBM Euroflight Operations", "BLUE", "Switzerland", + "BBN", "Civil Aviation Authority Airworthiness Division", "BRABAZON", "United Kingdom", + "BBO", "Flybaboo", "BABOO", "Switzerland", + "BBR", "Santa Barbara Airlines", "SANTA BARBARA", "Venezuela", + "BBS", "Beibars CJSC", "BEIBARS", "Kazakhstan", + "BBV", "Bravo Airlines", "BRAVO EUROPE", "Spain", + "BBW", "BB Airways", "BEEBEE AIRWAYS", "Nepal", + "BBZ", "Bluebird Aviation", "COBRA", "Kenya", + "BCB", "Carib Express", "WAVEBIRD", "Barbados", + "BCF", "BACH Flugbetriebsges", "BACH", "Austria", + "BCH", "Phillips Air", "BEACHBALL", "United States", + "BCI", "Blue Islands", "BLUE ISLAND", "United Kingdom", + "BCJ", "Blue Jet Charters", "BLUE BOY", "Poland", + "BCK", "Priority Aviation Company", "BANKCHECK", "United States", + "BCN", "Ocean Air", "BLUE OCEAN", "Mauritania", + "BCR", "British Charter", "BACKER", "United Kingdom", + "BCS", "European Air Transport", "POSTMAN", "Belgium", + "BCT", "BCT Aviation", "BOBCAT", "United Kingdom", + "BCV", "Business Aviation Center", "BUSINESS AVIATION", "Ukraine", + "BCY", "CityJet", "CITY JET", "Ireland", + "BDA", "Blue Dart Aviation", "BLUE DART", "India", + "BDF", "Bissau Discovery Flying Club", "BISSAU DISCOVERY", "Guinea-Bissau", + "BDG", "Mississippi State University", "BULLDOG", "United States", + "BDM", "Air Bandama", "BANDAMA", "Ivory Coast", + "BDN", "DERA Boscombe Down", "GAUNTLET", "United Kingdom", + "BDR", "Badr Airlines", "BADR AIR", "Sudan", + "BDS", "South Asian Airlines", "SOUTH ASIAN", "Bangladesh", + "BDV", "Aberdair Aviation", "ABERDAV", "Kenya", + "BEA", "Best Aviation Ltd", "BEST AIR", "Bangladesh", + "BEC", "State Air Company Berkut", "", "Kazakhstan", + "BEE", "Flybe", "JERSEY", "United Kingdom", + "BEF", "Balear Express", "BALEAR EXPRESS", "Spain", + "BEH", "Bel Air Helicopters", "BLUECOPTER", "Denmark", + "BEK", "Berkut Air", "BERKUT", "Kazakhstan", + "BEL", "Brussels Airlines", "BEE-LINE", "Belgium", + "BES", "Aero Services Executive", "BIRD EXPRESS", "France", + "BET", "BETA - Brazilian Express Transportes Aéreos", "BETA CARGO", "Brazil", + "BEZ", "Kingfisher Air Services", "SEA BREEZE", "United States", + "BFA", "Presidence Du Faso", "", "Burkina Faso", + "BFC", "Basler Flight Service", "BASLER", "United States", + "BFF", "Air Baffin", "AIR BAFFIN", "Canada", + "BFG", "Bear Flight", "BEARFLIGHT", "Sweden", + "BFL", "Buffalo Airways", "BUFFALO", "Canada", + "BFN", "Compagnie Nationale Naganagani", "", "Burkino Faso", + "BFO", "Bombardier", "BOMBARDIER", "Canada", + "BFR", "Burkina Airlines", "BURKLINES", "Burkina Faso", + "BFS", "Business Flight Sweden", "BUSINESS FLIGHT", "Sweden", + "BFW", "Bahrain Defence Force", "SUMMAN", "Bahrain", + "BGA", "Airbus Transport International", "BELUGA", "France", + "BGF", "Aviodetachment-28", "BULGARIAN", "Bulgaria", + "BGG", "Aero BG", "AERO BG", "Mexico", + "BGH", "BH Air", "BALKAN HOLIDAYS", "Bulgaria", + "BGI", "British Gulf International", "BRITISH GULF", "São Tomé and Príncipe", + "BGK", "British Gulf International-Fez", "GULF INTER", "Kyrgyzstan", + "BGL", "Benin Golf Air", "BENIN GOLF", "Benin", + "BGM", "Bugulma Air Enterprise", "BUGAVIA", "Russia", + "BGR", "Budget Air Bangladesh", "BUDGET AIR", "Bangladesh", + "BGT", "Bergen Air Transport", "BERGEN AIR", "Norway", + "BHA", "Buddha Air", "BUDDHA AIR", "Nepal", + "BHC", "Aerotaxis De La Bahia", "BAHIA", "Mexico", + "BHI", "Balkh Airlines", "SHARIF", "Afghanistan", + "BHK", "Blu Halkin", "BLUEHAKIN", "United Kingdom", + "BHL", "Bristow Helicopters", "BRISTOW", "United Kingdom", + "BHN", "Bristow Helicopters Nigeria", "BRISTOW HELICOPTERS", "Nigeria", + "BHO", "Bhoja Airlines", "BHOJA", "Pakistan", + "BHP", "Belair Airlines", "BELAIR", "Switzerland", + "BHR", "Bighorn Airways", "BIGHORN AIR", "United States", + "BHS", "Bahamasair", "BAHAMAS", "Bahamas", + "BHV", "Specavia Air Company", "AVIASPEC", "Russia", + "BHY", "Bosphorus European Airways", "BOSPHORUS", "Turkey", + "BIB", "Michelin Air Services", "", "France", + "BID", "Binair", "BINAIR", "Germany", + "BIG", "Big Island Air", "BIG ISLE", "United States", + "BIH", "British International Helicopters", "BRINTEL", "United Kingdom", + "BIL", "Billund Air Center", "BILAIR", "Denmark", + "BIN", "Boise Interagency Fire Center", "BISON-AIR", "United States", + "BIO", "Bioflight A/S", "BIOFLIGHT", "Denmark", + "BIR", "Bird Leasing", "BIRD AIR", "United States", + "BIS", "Sky Bishek", "JUMA AIR", "Kyrgyzstan", + "BIV", "Aviaservice", "AVIASERVICE", "Georgia", + "BIZ", "Bizjet Ltd", "BIZZ", "United Kingdom", + "BJA", "Baja Air", "BAJA AIR", "Mexico", + "BJC", "Baltic Jet Aircompany", "BALTIC JET", "Latvia", + "BJK", "Atlantic Airlines", "BLACKJACK", "United States", + "BJS", "Business Jet Solutions", "SOLUTION", "United States", + "BJT", "ACM Aviation", "BAY JET", "United States", + "BJU", "AeroJet", "JET EXPRESS", "Angola", + "BJV", "Beijing Vistajet Aviation", "BEIJING VISTA", "China", + "BKA", "Bankair", "BANKAIR", "United States", + "BKF", "BF-Lento OY", "BAKERFLIGHT", "Finland", + "BKH", "RAF Barkston Heath", "", "United Kingdom", + "BKJ", "Barken International", "BARKEN JET", "United States", + "BKK", "Blink", "BLINKAIR", "United Kingdom", + "BKL", "Aircompany Barcol", "BARCOL", "Russia", + "BKP", "Bangkok Airways", "BANGKOK AIR", "Thailand", + "BKR", "Civil Air Patrol South Carolina Wing", "BOX KAR", "United States", + "BKV", "Bukovyna", "BUKOVYNA", "Ukraine", + "BLA", "Blue Air", "BLUE AIR", "Romania", + "BLB", "Blue Bird Aviation", "BLUEBIRD SUDAN", "Sudan", + "BLC", "Bellesavia", "BELLESAVIA", "Belarus", + "BLE", "Blue Line", "BLUE BERRY", "France", + "BLF", "Blue1", "BLUEFIN", "Finland", + "BLG", "Belgavia", "BELGAVIA", "Belgium", + "BLH", "Blue Horizon Travel Club", "BLUE HORIZON", "United States", + "BLI", "Thyssen Krupp AG", "BLUELINE", "Germany", + "BLJ", "Blue Jet", "BLUEWAY", "Spain", + "BLK", "Westcoast Energy", "BLUE FLAME", "Canada", + "BLL", "Baltic Airlines", "BALTIC AIRLINES", "Russia", + "BLN", "Bali International Air Service", "BIAR", "Indonesia", + "BLS", "Bearskin Lake Air Service", "BEARSKIN", "Canada", + "BLT", "Baltic Aviation", "BALTAIR", "United States", + "BLU", "IMP Aviation Services", "BLUENOSE", "Canada", + "BLV", "Bellview Airlines", "BELLVIEW AIRLINES", "Nigeria", + "BLW", "Wermlandsflyg AB", "BLUESTAR", "Sweden", + "BLX", "TUI fly Nordic", "BLUESCAN", "Sweden", + "BLY", "Starair", "BLARNEY", "Ireland", + "BLZ", "Aero Barloz", "AEROLOZ", "Mexico", + "BMD", "British Medical Charter", "BRITISH MEDICAL", "United Kingdom", + "BME", "Briggs Marine Environmental Services", "BRIGGS", "United Kingdom", + "BMH", "Bristow Masayu Helicopters", "MASAYU", "Indonesia", + "BMJ", "Bemidji Airlines", "BEMIDJI", "United States", + "BMK", "GST Aero Aircompany", "MURAT", "Kazakhstan", + "BML", "Bismillah Airlines", "BISMILLAH", "Bangladesh", + "BMN", "Bowman Aviation", "BOWMAN", "United States", + "BMR", "BMI Regional", "MIDLAND", "United Kingdom", + "BMV", "Alatau Airlines", "OLIGA", "Kazakhstan", + "BMW", "BMW", "BMW-FLIGHT", "Germany", + "BMX", "Banco de Mexico", "BANXICO", "Mexico", + "BNA", "Bun Air Corporation", "BUN AIR", "United States", + "BNB", "Aero Banobras", "AEROBANOBRAS", "Mexico", + "BNC", "Sundance Air", "BARNACLE AIR", "United States", + "BND", "Bond Offshore Helicopters", "BOND", "United Kingdom", + "BNE", "Benina Air", "BENINA AIR", "Libya", + "BNG", "BN Group Limited", "VECTIS", "United Kingdom", + "BNI", "Alberni Airways", "ALBERNI", "Canada", + "BNJ", "Air Service Liège (ASL)", "JET BELGIUM", "Belgium", + "BNL", "Blue Nile Ethiopia Trading", "NILE TRADING", "Ethiopia", + "BNR", "Bonair Aviation", "BONAIR", "Canada", + "BNS", "Bancstar - Valley National Corporation", "BANCSTAR", "United States", + "BNT", "Bentiu Air Transport", "BENTIU AIR", "Sudan", + "BNV", "Benane Aviation Corporation", "BENANE", "Mauritania", + "BNW", "British North West Airlines", "BRITISH NORTH", "United Kingdom", + "BNX", "LAI - Línea Aérea IAACA", "AIR BARINAS", "Venezuela", + "BNZ", "Bonza", "BONZA", "Australia", + "BOA", "Boniair", "KUMANOVO", "North Macedonia", + "BOB", "Backbone A/S", "BACKBONE", "Denmark", + "BOC", "Aerobona", "AEROBONA", "Mexico", + "BOD", "Bond Air Services", "UGABOND", "Uganda", + "BOE", "Boeing", "BOEING", "United States", + "BOF", "Bordaire", "BORDAIR", "Canada", + "BOL", "Transportes Aéreos Bolivianos", "BOL", "Bolivia", + "BON", "B&H Airlines", "Air Bosna", "Bosnia and Herzegovina", + "BOO", "Bookajet Limited", "BOOKAJET", "United Kingdom", + "BOS", "OpenSkies", "MISTRAL", "United Kingdom", + "BOT", "Air Botswana", "BOTSWANA", "Botswana", + "BOU", "Bouraq Indonesia Airlines", "BOURAQ", "Indonesia", + "BOV", "Boliviana de Aviación", "BOLIVIANA", "Bolivia", + "BOX", "Aerologic", "GERMAN CARGO", "Germany", + "BPA", "Blue Panorama Airlines", "BLUE PANOROMA", "Italy", + "BPH", "Phoenix Helicopter Academy", "BLACK PHOENIX", "United Kingdom", + "BPK", "Berkhut ZK", "VENERA", "Kazakhstan", + "BPO", "Bundespolizei-Fliegertruppe", "PIROL", "Germany", + "BPS", "Budapest Aircraft Services/Manx2", "BASE", "Hungary", + "BPT", "Bonus Aviation", "BONUS", "United Kingdom", + "BPX", "British Petroleum Exploration", "", "Colombia", + "BRB", "BRA-Transportes Aéreos", "BRA-TRANSPAEREOS", "Brazil", + "BRD", "Brock Air Services", "BROCK AIR", "Canada", + "BRE", "Breeze Ltd", "AVIABREEZE", "Ukraine", + "BRF", "Air Bravo", "AIR BRAVO", "Uganda", + "BRG", "Bering Air", "BERING AIR", "United States", + "BRJ", "Borajet", "BORA JET", "Turkey", + "BRK", "Briansk State Air Enterprise", "BRIANSK-AVIA", "Russia", + "BRL", "Air Brasd'or", "BRASD'OR", "Canada", + "BRN", "Branson Airlines", "BRANSON", "United States", + "BRO", "2Excel Aviation", "BROADSWORD", "United Kingdom", + "BRP", "AeroBratsk", "AEROBRA", "Russia", + "BRQ", "El-Buraq Air Transport", "BURAQAIR", "Libya", + "BRR", "Mountain Air Service", "MOUNTAIN AIR", "United States", + "BRS", "Brazilian Air Force", "BRAZILIAN AIR FORCE", "Brazil", + "BRU", "Belavia Belarusian Airlines", "BELARUS AVIA", "Belarus", + "BRW", "Bright Aviation Services", "BRIGHT SERVICES", "Bulgaria", + "BRX", "Buffalo Express Airlines", "BUFF EXPRESS", "United States", + "BRY", "Burundayavia", "BURAIR", "Kazakhstan", + "BSB", "Air Wings", "ARBAS", "Moldova", + "BSC", "Bistair - Fez", "BIG SHOT", "Kyrgyzstan", + "BSD", "Blue Star Airlines", "AIRLINES STAR", "Mexico", + "BSI", "Brasair Transportes Aéreos", "BRASAIR", "Brazil", + "BSJ", "Skybus Jet", "SKYBUS JET", "Bahamas", + "BSK", "Miami Air International", "BISCAYNE", "United States", + "BSM", "Blue Sky Aviation", "", "Lebanon", + "BSN", "Connectair Charters", "BASTION", "Canada", + "BSO", "Aeroclub Barcelona-Sabadell", "", "Spain", + "BSP", "Global Airways (BSP)", "", "Democratic Republic of Congo", + "BSR", "Guine Bissaur Airlines", "BISSAU AIRLINES", "Guinea-Bissau", + "BSS", "Bissau Aero Transporte", "BISSAU AIRSYSTEM", "Guinea-Bissau", + "BST", "Best Air", "TUNCA", "Turkey", + "BSW", "Blue Sky Airways", "SKY BLUE", "Czech Republic", + "BSY", "Big Sky Airlines", "BIG SKY", "United States", + "BTC", "BAL Bashkirian Airlines", "BASHKIRIAN", "Russia", + "BTH", "Baltijas Helicopters", "BALTIJAS HELICOPTERS", "Latvia", + "BTI", "Air Baltic", "AIRBALTIC", "Latvia", + "BTK", "Batik Air", "BATIK", "Indonesia", + "BTL", "Baltia Air Lines", "BALTIA", "United States", + "BTP", "Veritair", "NET RAIL", "United Kingdom", + "BTQ", "Boutique Air", "BOUTIQUE", "United States", + "BTR", "Botir-Avia", "BOTIR-AVIA", "Kyrgyzstan", + "BTS", "Aerotaxis Albatros", "AEROLINEAS ALBATROS", "Mexico", + "BTT", "BT-Slavuta", "BEETEE-SLAVUTA", "Ukraine", + "BTU", "Rolls-Royce plc", "ROLLS", "United Kingdom", + "BTV", "Batavia Air", "BATAVIA", "Indonesia", + "BTZ", "Bristow U.S. LLC", "BRISTOW", "United States", + "BUC", "Bulgarian Air Charter", "BULGARIAN CHARTER", "Bulgaria", + "BUE", "Orebro Aviation", "BLUELIGHT", "Sweden", + "BUG", "UVT Aero", "", "Russia", + "BUL", "Blue Airlines", "BLUE AIRLINES", "Democratic Republic of the Congo", + "BUN", "Buryat Airlines Aircompany", "BURAL", "Russia", + "BUZ", "Buzz Stansted", "BUZZ", "United Kingdom", + "BVA", "Buffalo Airways", "BUFFALO AIR", "United States", + "BVC", "Bulgarian Aeronautical Centre", "BULGARIAN WINGS", "Bulgaria", + "BVN", "Baron Aviation Services", "SHOW-ME", "United States", + "BVR", "ACM Air Charter", "BAVARIAN", "Germany", + "BVT", "Berjaya Air", "BERJAYA", "Malaysia", + "BVU", "Bellview Airlines Sierra Leone", "BELLVIEW AIRLINES", "Sierra Leone", + "BVV", "Sparc Avia", "SPARC", "Russia", + "BWA", "Caribbean Airlines", "CARIBBEAN", "Trinidad and Tobago", + "BWD", "Bluewest Helicopters-Greenland", "BLUEWEST", "Denmark", + "BWI", "Blue Wing Airlines", "BLUE TAIL", "Suriname", + "BWL", "British World Airlines", "BRITWORLD", "United Kingdom", + "BWY", "Fleet Requirements Air Direction Unit", "BROADWAY", "United Kingdom", + "BXA", "Bahrain Executive Air Services", "BEXAIR", "Bahrain", + "BXH", "Bar XH Air", "PALLISER", "Canada", + "BXI", "Brussels Airlines", "XENIA", "Belgium", + "BXJ", "Brixtel Group", "BRIXTEL JET", "United States", + "BYA", "Berry Aviation", "BERRY", "United States", + "BYC", "Northern Airlines Sanya", "BEIYA", "China", + "BYE", "Bayu Indonesia Air", "BAYU", "Indonesia", + "BYF", "San Carlos Flight Center", "BAY FLIGHT", "United States", + "BYG", "Bygone Aviation", "BYGONE", "United States", + "BYL", "Bylina Joint-Stock Company", "BYLINA", "Russia", + "BYR", "Berytos Airlines", "", "Lebanon", + "BYT", "Patriot Aviation Limited", "BYTE", "United Kingdom", + "BZA", "Bizair Fluggesellschaft", "BERLIN BEAR", "Germany", + "BZE", "Zenith Aviation", "ZENSTAR", "United Kingdom", + "BZF", "Jet Aviation Business Jets", "BIZFLEET", "United States", + "BZH", "Brit Air", "BRITAIR", "France", + "BZQ", "Seneca College", "STING", "Canada", + "BZS", "Aero Biniza", "BINIZA", "Mexico", + "BZY", "Fresh Air Aviation", "BREEZY", "United States", + "BZZ", "Butane Buzzard Aviation Corporation", "BUZZARD", "United Kingdom", + "CAA", "Civil Aviation Authority of the Czech Republic", "INSPECTOR", "Czech Republic", + "CAB", "Chesapeake Air Service", "CHESAPEAKE AIR", "United States", + "CAC", "Conquest Airlines", "CONQUEST AIR", "United States", + "CAD", "Chilliwack Aviation", "CHILLIWACKAIR", "Canada", + "CAE", "Colibri Aviation", "HUMMINGBIRD", "Canada", + "CAG", "China National Aviation Corporation", "CHINA NATIONAL", "China", + "CAI", "Corendon Airlines", "CORENDON", "Turkey", + "CAJ", "Air Caraibes Atlantique", "CAR LINE", "France", + "CAK", "Congo Air", "", "Bahamas", + "CAL", "China Airlines", "DYNASTY", "Taiwan", + "CAM", "Camai Air", "AIR CAMAI", "United States", + "CAN", "Crest Aviation", "CREST", "United Kingdom", + "CAO", "Air China Cargo", "AIRCHINA FREIGHT", "China", + "CAP", "Civil Air Patrol", "CAP", "United States", + "CAQ", "Cityair (Chester) Limited", "AIR CHESTER", "United Kingdom", + "CAR", "Inter RCA", "QUEBEC ROMEO", "Central African Republic", + "CAS", "Christman Air System", "CHRISTMAN", "United States", + "CAT", "Copenhagen Air Taxi", "AIRCAT", "Denmark", + "CAV", "Calm Air", "CALM AIR", "Canada", + "CAW", "Comair", "COMMERCIAL", "South Africa", + "CAX", "Central Air Express", "CENTRAL EXPRESS", "Democratic Republic of the Congo", + "CAY", "Cayman Airways", "CAYMAN", "Cayman Islands", + "CAZ", "Cat Aviation", "EUROCAT", "Switzerland", + "CBA", "Civil Aviation Inspectorate of the Czech Republic", "CALIBRA", "Czech Republic", + "CBB", "Cheboksary Airenterprise JSC", "CHEBAIR", "Russia", + "CBD", "Lockheed Martin Aeronautics", "CATBIRD", "United States", + "CBE", "Aerovías Caribe", "AEROCARIBE", "Mexico", + "CBH", "Corporate Eagle Management Services", "CLUB HOUSE", "United States", + "CBI", "Cabi", "CABI", "Ukraine", + "CBJ", "Beijing Capital Airlines", "CAPITAL JET", "China", + "CBL", "Cumberland Airways (Nicholson Air Service)", "CUMBERLAND", "United States", + "CBM", "Cherokee Express", "BLUE MAX", "United States", + "CBN", "Swedish Civil Aviation Administration", "CALIBRATION", "Sweden", + "CBO", "Aerotaxi del Cabo", "TAXI CABO", "Mexico", + "CBR", "Cabair College of Air Training", "CABAIR", "United Kingdom", + "CBS", "Air Columbus", "AIR COLUMBUS", "Ukraine", + "CBT", "Catalina Flying Boats", "CATALINA AIR", "United States", + "CBV", "Aereo Cabo", "CABOAEREO", "Mexico", + "CBY", "RAF Coningsby", "TYPHOON", "United Kingdom", + "CCA", "Air China", "AIR CHINA", "China", + "CCB", "Caricom Airways", "DOLPHIN", "Barbados", + "CCE", "Cairo Air Transport Company", "", "Egypt", + "CCF", "CCF Manager Airline", "TOMCAT", "Germany", + "CCH", "Chilchota Taxi Aéreo", "CHILCHOTA", "Mexico", + "CCK", "Flight Trac", "CABLE CHECK", "United States", + "CCL", "Cambodia Airlines", "ANGKOR WAT", "Cambodia", + "CCM", "Air Corsica", "CORSICA", "France", + "CCP", "Champion Air", "CHAMPION AIR", "United States", + "CCQ", "Capital City Air Carriers", "CAP CITY", "United States", + "CCT", "Connect Air", "CONNECT", "Canada", + "CCV", "Centro De Helicopteros Corporativos", "HELICORPORATIVO", "Mexico", + "CCW", "Central Charter", "CENTRAL CHARTER", "Czech Republic", + "CCX", "Colt International", "", "United States", + "CCY", "Cherry Air", "CHERRY", "United States", + "CDA", "Aerocardal", "CARDAL", "Chile", + "CDC", "Loong Air", "LOONG AIR", "China", + "CDE", "Comed Group", "COMEX", "United Kingdom", + "CDG", "Shandong Airlines", "SHANDONG", "China", + "CDI", "Cards Air Services", "CARDS", "Philippines", + "CDL", "Sunbird Airlines", "CAROLINA", "United States", + "CDM", "Carga Aérea Dominicana", "CARGA AEREA", "Dominican Republic", + "CDN", "Canadian Helicopters", "CANADIAN", "Canada", + "CDP", "Aero Condor Peru", "CONDOR-PERU", "Peru", + "CDS", "Spectrem Air", "SPECDAS", "South Africa", + "CDT", "Real Aero Club de Reus-Costa Dorado", "AEROREUS", "Spain", + "CDU", "Aerotrans", "", "Russia", + "CDV", "Airline Skol", "SKOL", "Russia", + "CDY", "Heliaviation Limited", "CADDY", "United Kingdom", + "CEA", "RegionsAir", "CORP-X", "United States", + "CEB", "Cebu Pacific", "CEBU", "Philippines", + "CEC", "Celtic Airways", "CELTAIR", "United Kingdom", + "CED", "CEDTA (Compañía Ecuatoriana De Transportes Aéreos)", "CEDTA", "Ecuador", + "CEE", "Servicios Aéreos Centrales", "CENTRA AEREOS", "Mexico", + "CEF", "Czech Air Force", "CZECH AIR FORCE", "Czech Republic", + "CEG", "Cega Aviation", "CEGA", "United Kingdom", + "CEM", "Central Mongolia Airways", "CENTRAL MONGOLIA", "Mongolia", + "CEP", "Chipola Aviation", "CHIPOLA", "United States", + "CER", "Cetraca Aviation Service", "CETRACA", "Democratic Republic of the Congo", + "CES", "China Eastern Airlines", "CHINA EASTERN", "China", + "CET", "Centrafrican Airlines", "CENTRAFRICAIN", "Central African Republic", + "CEV", "Centre d'Essais en Vol", "CENTEV", "France", + "CEX", "Capitol Air Express", "CAPITOL EXPRESS", "United States", + "CFA", "China Flying Dragon Aviation", "FEILONG", "China", + "CFB", "Chongqing Forebase General Aviation", "FOREBASE", "China", + "CFC", "Canadian Forces", "CANFORCE", "Canada", + "CFD", "Cranfield University", "AERONAUT", "United Kingdom", + "CFE", "BA CityFlyer", "FLYER", "United Kingdom", + "CFF", "Aerofan", "AEROFAN", "Spain", + "CFG", "Condor Flugdienst", "CONDOR", "Germany", + "CFH", "CareFlight", "CARE FLIGHT", "Australia", + "CFI", "Flight Inspection Center of the General Administration of Civil Aviation in China", "CHINA JET", "China", + "CFJ", "Fujian Airlines", "FUJIAN", "China", + "CFL", "Swedish Airlines", "SWEDISH", "Sweden", + "CFN", "RAF Church Fenton", "CHURCH FENTON", "United Kingdom", + "CFS", "Empire Airlines", "EMPIRE AIR", "United States", + "CFT", "Jet Freighters", "CASPER FREIGHT", "United States", + "CFU", "United Kingdom Civil Aviation Authority", "MINAIR", "United Kingdom", + "CFV", "Aero Calafia", "CALAFIA", "Mexico", + "CFZ", "Zhongfei General Aviation", "ZHONGFEI", "China", + "CGA", "Congressional Air", "CONGRESSIONAL", "United States", + "CGB", "Air Cargo Belize", "CARGO BELIZE", "Belize", + "CGC", "Cal Gulf Aviation", "CAL-GULF", "São Tomé and Príncipe", + "CGD", "Charlotte Air National Guard", "", "United States", + "CGE", "Nelson Aviation College", "COLLEGE", "New Zealand", + "CGG", "Walmart Aviation", "CHARGE", "United States", + "CGH", "Guizhou Airlines", "GUIZHOU", "China", + "CGI", "Rusair JSAC", "CGI-RUSAIR", "Russia", + "CGK", "Click Airways", "CLICK AIR", "Kyrgyzstan", + "CGL", "Seagle Air", "SEAGLE", "Slovakia", + "CGM", "Cargoman", "HOTEL CHARLIE", "Oman", + "CGN", "Chang An Airlines", "CHANGAN", "China", + "CGO", "Chicago Air", "WILD ONION", "United States", + "CGP", "Cargo Plus Aviation", "", "United Arab Emirates", + "CGR", "Compagnia Generale Ripreseaeree", "COMPRIP", "Italy", + "CGS", "Centre of Applied Geodynamica", "GEO CENTRE", "Russia", + "CGU", "Chinguetti Airlines", "CHINGUETTI", "Mauritania", + "CGV", "Aero Clube Do Algarve", "CLUBE ALGARVE", "Portugal", + "CGX", "United States Coast Guard Auxiliary", "COASTGUARD AUXAIR", "United States", + "CGY", "Corporacion Paraguaya De Aeronautica", "", "Paraguay", + "CHA", "Central Flying Service", "CHARTER CENTRAL", "United States", + "CHB", "West Air (China)", "WEST CHINA", "China", + "CHC", "China Ocean Helicopter Corporation", "CHINA HELICOPTER", "China", + "CHD", "223rd Flight Unit", "CHKALOVSK-AVIA", "Russia", + "CHE", "Top Flight Air Service", "CHECK AIR", "United States", + "CHF", "Chitaavia", "CHITA", "Russia", + "CHG", "Challenge Airlines BE", "Challenge", "Belgium", + "CHH", "Hainan Airlines", "HAINAN", "China", + "CHI", "Cougar Helicopters", "COUGAR", "Canada", + "CHJ", "Aircompany Chaika", "AIR CHAIKA", "Ukraine", + "CHK", "Chalk's International Airlines", "CHALKS", "United States", + "CHL", "Cohlmia Aviation", "COHLMIA", "United States", + "CHM", "Chemech Aviation", "", "Pakistan", + "CHN", "Channel Island Aviation", "CHANNEL", "United States", + "CHO", "Chrome Air Services", "CHROME AIR", "United States", + "CHP", "Aviacsa", "AVIACSA", "Mexico", + "CHQ", "Chautauqua Airlines", "CHAUTAUQUA", "United States", + "CHR", "Air Charter Services", "ZAIRE CHARTER", "Democratic Republic of the Congo", + "CHS", "Challenge Aviation", "CHALLENGE AVIATION", "Australia", + "CHU", "Church Aircraft", "CHURCHAIR", "United States", + "CHV", "Air Charter Professionals", "CHARTAIR", "United States", + "CHW", "Charter Air", "CHARTER WIEN", "Austria", + "CHZ", "Cherline", "CHERL", "Russia", + "CIA", "Civil Aviation Authority", "CALIMERA", "Slovakia", + "CIB", "Condor", "CONDOR BERLIN", "Germany", + "CIC", "ICC Canada", "AIR TRADER", "Canada", + "CIE", "Czech Government Flying Service", "CZECH REPUBLIC", "Czech Republic", + "CIG", "Sirius-Aero", "SIRIUS AERO", "Russia", + "CII", "Cityfly", "CITYFLY", "Italy", + "CIL", "Cecil Aviation", "CECIL", "United Kingdom", + "CIM", "Cimber Sterling", "CIMBER", "Denmark", + "CIN", "Cinnamon Air", "CINNAMON", "Sri Lanka", + "CIO", "Il Ciocco International Travel Service", "CIOCCO", "Italy", + "CIR", "Arctic Circle Air Service", "AIR ARCTIC", "United States", + "CIT", "Zanesville Aviation", "ZANE", "United States", + "CIU", "Cielos Airlines", "CIELOS", "Peru", + "CIV", "Civil Aviation Authority of New Zealand", "CIVAIR", "New Zealand", + "CIW", "Civair Airways", "CIVFLIGHT", "South Africa", + "CJA", "CanJet", "CANJET", "Canada", + "CJC", "Colgan Air", "COLGAN", "United States", + "CJE", "Aeroservices Corporate", "BIRD JET", "France", + "CJI", "Corporate Jets", "SEA JET", "United States", + "CJR", "Caverton Helicopters", "CAVERTON AIR", "Nigeria", + "CJS", "Commonwealth Jet Service", "COMMONWEALTH", "United States", + "CJT", "Cargojet Airways", "CARGOJET", "Canada", + "CJZ", "Caliber Jet", "CALIBER JET", "United States", + "CKA", "Cook Inlet Aviation", "COOK-AIR", "United States", + "CKC", "CKC Services", "", "United States", + "CKE", "Corporate Aviation Services", "CHECKMATE", "United States", + "CKK", "China Cargo Airlines", "CARGO KING", "China", + "CKL", "Aviation Charter Services", "CIRCLE CITY", "United States", + "CKM", "BKS Air (Rivaflecha)", "COSMOS", "Spain", + "CKR", "Crown Air Systems", "CROWN AIR", "United States", + "CKS", "Kalitta Air", "CONNIE", "United States", + "CLA", "Comlux Aviation", "COMLUX", "Switzerland", + "CLB", "Flight Precision Limited", "CALIBRATOR", "United Kingdom", + "CLD", "Clowes Estates Limited", "CLOWES", "United Kingdom", + "CLE", "Colemill Enterprises", "COLEMILL", "United States", + "CLF", "Bristol Flying Centre", "CLIFTON", "United Kingdom", + "CLG", "Chalair Aviation", "CHALLAIR", "France", + "CLH", "Lufthansa CityLine", "HANSALINE", "Germany", + "CLI", "Clickair", "CLICKJET", "Spain", + "CLK", "Clark Aviation", "CLARKAIR", "United States", + "CLL", "Aerovías Castillo", "AEROCASTILLO", "Mexico", + "CLM", "Cargo Link (Caribbean)", "CARGO LINK", "Barbados", + "CLN", "Barnes Olsen Aeroleasing", "SEELINE", "United Kingdom", + "CLP", "Aero Club De Portugal", "CLUB PORTUGAL", "Portugal", + "CLR", "Trans America", "CLINTON AIRWAYS", "United States", + "CLS", "Challenge Air Transport", "AIRISTO", "Germany", + "CLT", "Club Aerocelta de Vuelo Con Motor", "", "Spain", + "CLU", "Cargo Logic Air", "FIREBIRD", "United Kingdom", + "CLV", "Lom Praha Flying School", "AEROTRAINING", "Czech Republic", + "CLW", "Centralwings", "CENTRALWINGS", "Poland", + "CLX", "Cargolux", "CARGOLUX", "Luxembourg", + "CLY", "Clay Lacy Aviation", "CLAY-LACY", "United States", + "CLZ", "Cloud 9 Air Charters", "CLOUDLINE", "South Africa", + "CMA", "Central European Airlines", "EUROCENTRAL", "Czech Republic", + "CME", "Commerce Bank", "COMMERCE BANK", "United States", + "CMF", "Air Care Alliance", "COMPASSION", "United States", + "CMG", "Comet Airlines", "SUNSPY", "Nigeria", + "CMH", "Commair Aviation", "COMMODORE", "United Kingdom", + "CMI", "Continental Micronesia", "AIR MIKE", "United States", + "CMJ", "Mudanjiang General Aviation", "MUDANJIANG", "China", + "CMK", "Chernomor-Avia", "CHERAVIA", "Russia", + "CML", "Commander Air Charter", "COMMANDAIR", "Canada", + "CMM", "Compagnie Aérienne du Mali", "CAMALI", "Mali", + "CMN", "Eckles Aircraft", "CIMMARON AIRE", "United States", + "CMP", "Copa Airlines", "COPA", "Panama", + "CMR", "CAM Air Management", "CAMEO", "United Kingdom", + "CMS", "Commercial Aviation", "ACCESS", "Canada", + "CMT", "Casement Aviation", "CASEMENT", "United States", + "CMU", "Northern Aviation Service", "LANNA AIR", "Thailand", + "CMV", "Calima Aviación", "CALIMA", "Spain", + "CMX", "El Caminante Taxi Aéreo", "EL CAMINANTE", "Mexico", + "CMY", "Cape Smythe Air", "CAPE SMYTHE AIR", "United States", + "CMZ", "CM Stair", "CEE-EM STAIRS", "Mauritania", + "CNB", "Cityline Hungary", "CITYHUN", "Hungary", + "CNC", "Corporación Aéreo Cencor", "CENCOR", "Mexico", + "CND", "Corendon Dutch Airlines", "DUTCH CORENDON", "Netherlands", + "CNE", "Air Toronto", "CONNECTOR", "Canada", + "CNG", "Coastal Airways", "SID-AIR", "United States", + "CNH", "Aquila Air", "CHENANGO", "United States", + "CNI", "Empresa Nacional De Servicios Aéreos", "SERAER", "Cuba", + "CNJ", "Nanjing Airlines", "NINGHANG", "China", + "CNK", "Sunwest Home Aviation", "CHINOOK", "Canada", + "CNL", "Centennial Airlines", "WYO-AIR", "United States", + "CNM", "Air China Inner Mongolia", "MENGYUAN", "China", + "CNO", "SAS Braathens", "SCANOR", "Norway", + "CNR", "Condor Aero Services", "CONAERO", "United States", + "CNS", "Cobalt Air LLC", "CHRONOS", "United States", + "CNT", "Centre national d'études des télécommunications - C.N.E.T.", "KNET", "France", + "CNU", "Air Consul", "AIR CONSUL", "Spain", + "CNV", "U.S. Navy Reserve Logistic Air Forces", "CONVOY", "United States", + "CNW", "Continental Airways", "", "Moldovoa", + "CNY", "Central Airways", "CENTRAL LEONE", "Sierra Leone", + "COD", "Concordavia", "CONCORDAVIA", "Ukraine", + "COF", "Confort Air", "CONFORT", "Canada", + "COH", "RAF Coltishall", "COLT", "United Kingdom", + "COM", "Comair", "COMAIR", "United States", + "CON", "Continental Oil", "CONOCO", "United States", + "COO", "Corporate Airlink", "CORPORATE", "Canada", + "COP", "Copper State Air Service", "COPPER STATE", "United States", + "COT", "RAF Cottesmore", "COTTESMORE", "United Kingdom", + "COV", "Helicentre Coventry", "HELICENTRE", "United Kingdom", + "COW", "COWI", "COWI", "Denmark", + "COY", "Coyne Aviation", "COYNE AIR", "United Kingdom", + "COZ", "Cosmic Air", "COSMIC AIR", "Nepal", + "CPA", "Cathay Pacific", "CATHAY", "Hong Kong", + "CPB", "Corpac Canada", "PENTA", "Canada", + "CPD", "Capital Airlines", "CAPITAL DELTA", "Kenya", + "CPG", "Corporacion Aeroangeles", "CORPORANG", "Mexico", + "CPH", "Champagne Airlines", "CHAMPAGNE", "France", + "CPI", "Compagnia Aeronautica Italiana", "AIRCAI", "Italy", + "CPJ", "Baltimore Air Transport", "CORPJET", "United States", + "CPL", "Chaparral Airlines", "CHAPARRAL", "United States", + "CPM", "Compagnie Mauritanienne Des Transports", "", "Mauritania", + "CPN", "Caspian Airlines", "CASPIAN", "Iran", + "CPO", "Corporate Aircraft Company", "MOKAN", "United States", + "CPR", "Corporate Air", "CORPAIR", "United States", + "CPS", "Compass International Airways", "COMPASS", "United Kingdom", + "CPT", "Corporate Air", "AIR SPUR", "United States", + "CPV", "Air Corporate", "AIRCORPORATE", "Italy", + "CPW", "Chippewa Air Commuter", "CHIPPEWA-AIR", "United States", + "CPX", "Capital Air Service", "CAPAIR", "United States", + "CPZ", "Compass Airlines", "COMPASS ROSE", "United States", + "CQC", "Central Queensland Aviation College", "", "Australia", + "CQH", "Spring Airlines", "AIR SPRING", "China", + "CQN", "Chongqing Airlines", "CHONG QING", "China", + "CRA", "Coronado Aerolíneas", "CORAL", "Colombia", + "CRB", "Caricom Airways", "CARIBBEAN COMMUTER", "Suriname", + "CRC", "Conair Aviation", "CONAIR-CANADA", "Canada", + "CRE", "Cree Airways", "CREE AIR", "Canada", + "CRF", "Croix Rouge Francais", "CROIX ROUGE", "France", + "CRG", "Cargoitalia", "WHITE PELICAN", "Italy", + "CRH", "CRI Helicopters Mexico", "HELI-MEX", "Mexico", + "CRJ", "Air Cruzal", "AIR CRUZAL", "Angola", + "CRK", "Hong Kong Airlines", "BAUHINIA", "Hong Kong", + "CRL", "Corsairfly", "CORSAIR", "France", + "CRM", "Commander Mexicana", "COMMANDERMEX", "Mexico", + "CRN", "Carson Air Ltd", "CARSON", "Canada", + "CRO", "Crown Airways", "CROWN AIRWAYS", "United States", + "CRP", "Aerotransportes Corporativos", "AEROTRANSCORP", "Mexico", + "CRQ", "Air Creebec", "CREE", "Canada", + "CRS", "Comercial Aérea", "COMERCIAL AEREA", "Mexico", + "CRT", "Caribintair", "CARIBINTAIR", "Haiti", + "CRV", "Acropolis Aviation", "ACROPOLIS", "United Kingdom", + "CRW", "Crownair", "REGAL", "Canada", + "CRX", "Cross Aviation", "CROSSAIR", "United Kingdom", + "CRY", "Primavia Limited", "CARRIERS", "United Kingdom", + "CSA", "Czech Airlines", "CSA", "Czech Republic", + "CSC", "Sichuan Airlines", "SI CHUAN", "China", + "CSD", "Courier Services", "DELIVERY", "United States", + "CSE", "CSE Aviation", "OXFORD", "United Kingdom", + "CSF", "Clasair", "CALEDONIAN", "United Kingdom", + "CSH", "Shanghai Airlines", "SHANGHAI AIR", "China", + "CSI", "Central Skyport", "SKYPORT", "United States", + "CSJ", "Castle Aviation", "CASTLE", "United States", + "CSK", "Flightcraft", "CASCADE", "United States", + "CSL", "California Air Shuttle", "CALIFORNIA SHUTTLE", "United States", + "CSM", "Ratkhan Air", "LORRY", "Kazakhstan", + "CSN", "China Southern Airlines", "CHINA SOUTHERN", "China", + "CSO", "Casino Airline", "CASAIR", "United States", + "CSP", "Casper Air Service", "CASPER AIR", "United States", + "CSQ", "IBC Airways", "CHASQUI", "United States", + "CSS", "SF Airlines", "SHUN FENG", "China", + "CST", "Coast Air", "COAST CENTER", "Norway", + "CSU", "Chari Aviation Services", "CHARI SERVICE", "Chad", + "CSV", "Coastal Travels", "COASTAL TRAVEL", "Tanzania", + "CSW", "SW Italia", "SILKITALIA", "Italy", + "CSX", "Choice Airways", "CHOICE AIR", "United States", + "CSY", "Shuangyang General Aviation", "SHUANGYANG", "China", + "CSZ", "Shenzhen Airlines", "SHENZHEN AIR", "China", + "CTA", "Aero Charter and Transport", "CHAR-TRAN", "United States", + "CTD", "Aerocorporativos", "AEROCORPORATIVOS", "Mexico", + "CTE", "Air Tenglong", "TENGLONG", "China", + "CTF", "Cutter Aviation", "CUTTER FLIGHT", "United States", + "CTG", "Canadian Coast Guard", "CANADIAN COAST GUARD", "Canada", + "CTH", "China General Aviation Corporation", "TONGHANG", "China", + "CTK", "East Midlands Helicopters", "COSTOCK", "United Kingdom", + "CTL", "Central Airlines", "CENTRAL COMMUTER", "United States", + "CTM", "Commandement Du Transport Aerien Militaire Francais", "COTAM", "France", + "CTN", "Croatia Airlines", "CROATIA", "Croatia", + "CTO", "Cape Air Transport", "", "Australia", + "CTP", "Tashkent Aircraft Production Corporation", "CORTAS", "Uzbekistan", + "CTQ", "CTK Network Aviation", "CITYLINK", "Ghana", + "CTR", "Aerolíneas Centauro", "CENTAURO", "Mexico", + "CTS", "Center-South", "CENTER-SOUTH", "Russia", + "CTT", "Custom Air Transport", "CATT", "United States", + "CTV", "Escuela De Pilotos Are Aviación", "ARE AVIACION", "Spain", + "CTW", "Cargo Three", "THIRD CARGO", "Panama", + "CTY", "Cryderman Air Service", "CENTURY", "United States", + "CTZ", "CATA Línea Aérea", "CATA", "Argentina", + "CUA", "China United Airlines", "LIANHANG", "China", + "CUB", "Cubana de Aviación", "CUBANA", "Cuba", + "CUH", "Urumqi Airlines", "LOULAN", "China", + "CUI", "Cancun Air", "CAN-AIR", "Mexico", + "CUK", "Polo Aviation", "CHUKKA", "United Kingdom", + "CUO", "Aerocuahonte", "CUAHONTE", "Mexico", + "CUT", "Court Helicopters", "COURT AIR", "South Africa", + "CVA", "Air Chathams", "CHATHAM", "New Zealand", + "CVC", "Centre-Avia", "AVIACENTRE", "Russia", + "CVE", "Cabo Verde Express", "KABEX", "Cape Verde", + "CVF", "Dassault Falcon Jet Corporation", "CLOVERLEAF", "United States", + "CVG", "Carill Aviation", "CARILL", "United Kingdom", + "CVK", "CAVOK Airlines", "CARGO LINE", "Ukraine", + "CVL", "Coval Air", "COVAL", "Canada", + "CVO", "Center Vol", "CENTERVOL", "Spain", + "CVR", "Chevron U.S.A", "CHEVRON", "United States", + "CVT", "Peran", "CVETA", "Kazakhstan", + "CVU", "Grand Canyon Airlines", "CANYON VIEW", "United States", + "CVV", "Comeravia", "COMERAVIA", "Venezuela", + "CWA", "Canadian Western Airlines", "CANADIAN WESTERN", "Canada", + "CWC", "Centurion Air Cargo", "CHALLENGE CARGO", "United States", + "CWD", "Caernarfon Airworld", "AMBASSADOR", "United Kingdom", + "CWE", "Celtic West", "CELTIC", "United Kingdom", + "CWH", "Canadian Warplane Heritage Museum", "WARPLANE HERITAGE", "Canada", + "CWK", "Comores Airlines", "CONTICOM", "Comoros", + "CWL", "RAF Cranwell", "CRANWELL", "United Kingdom", + "CWM", "Air Marshall Islands", "AIR MARSHALLS", "Marshall Islands", + "CWN", "Cardiff Wales Flying Club", "CAMBRIAN", "United Kingdom", + "CWP", "Australian Customs Service", "COASTWATCH", "Australia", + "CWR", "Beijing City International Jet", "CITY WORLD", "China", + "CWT", "Texas Airways", "TEXAS AIRWAYS", "United States", + "CWU", "Wuhan Airlines", "WUHAN AIR", "China", + "CWW", "Canair", "CANAIR", "China", + "CWX", "Crow Executive Air", "CROW EXPRESS", "United States", + "CWY", "Woodgate Aviation", "CAUSEWAY", "United Kingdom", + "CWZ", "Capitol Wings Airline", "CAPWINGS", "United States", + "CXA", "Xiamen Airlines", "XIAMEN AIR", "China", + "CXB", "Comlux Aruba", "STARLUX", "Aruba", + "CXE", "Caicos Express Airways", "CAICOS", "Turks and Caicos Islands", + "CXH", "China Xinhua Airlines", "XINHUA", "China", + "CXI", "Corendon Airlines Europe", "TOURIST", "Malta", + "CXJ", "Xinjiang Airlines", "XINJIANG", "China", + "CXO", "Conroe Aviation Services", "CONROE AIR", "United States", + "CXS", "Boston-Maine Airways", "CLIPPER CONNECTION", "United States", + "CYA", "Cheyenne Airways", "CHEYENNE AIR", "United States", + "CYC", "Cyprair Tours", "CYPRAIR", "Cyprus", + "CYE", "Aerocheyenne", "AEROCHEYENNE", "Mexico", + "CYF", "Company Flight", "COMPANY FLIGHT", "Denmark", + "CYG", "Yana Airlines", "VICAIR", "Cambodia", + "CYH", "China Southern Airlines Henan", "YUHAO", "China", + "CYL", "Air One Cityliner", "CITYLINER", "Italy", + "CYO", "Air Transport", "COYOTE", "United States", + "CYP", "Cyprus Airways", "CYPRUS", "Cyprus", + "CYS", "Cypress Airlines", "SKYBIRD", "Canada", + "CYT", "Crystal Shamrock Airlines", "CRYSTAL-AIR", "United States", + "CYZ", "China Postal Airlines", "CHINA POST", "China", + "DAA", "Decatur Aviation", "DECUR", "United States", + "DAB", "Dassault Aviation", "", "France", + "DAC", "McDonnell Douglas", "DACO", "United States", + "DAD", "Dorado Air", "DORADO AIR", "Dominican Republic", + "DAE", "DHL Aero Expreso", "YELLOW", "Panama", + "DAF", "Danish Air Force", "DANISH AIRFORCE", "Denmark", + "DAG", "Dagestan Airlines", "DAGAL", "Russia", + "DAH", "Air Algérie", "AIR ALGERIE", "Algeria", + "DAL", "Delta Air Lines", "DELTA", "United States", + "DAM", "Kyrgyzstan Department of Aviation", "FLIGHT RESCUE", "Kyrgyzstan", + "DAO", "Daallo Airlines", "DALO AIRLINES", "Djibouti", + "DAP", "Aerovías DAP", "DAP", "Chile", + "DAR", "Danish Army", "DANISH ARMY", "Denmark", + "DAS", "Damascene Airways", "AIRDAM", "Syrian Arab Republic", + "DAT", "Lynx Air", "DAUNTLESS", "Canada", + "DAU", "Dauair", "DAUAIR", "Germany", + "DAV", "Dornier Aviation Nigeria", "DANA AIR", "Nigeria", + "DAY", "Daya Aviation", "DAYA", "Sri Lanka", + "DBA", "Air Alpha", "DOUBLE-A", "United States", + "DBC", "Gemini Air Group", "DIAMOND BACK", "United States", + "DBD", "Air Niagara Express", "AIR NIAGARA", "Canada", + "DBJ", "Duchess of Brittany (Jersey) Limited", "DUCHESS", "United Kingdom", + "DBK", "Dubrovnik Air", "SEAGULL", "Croatia", + "DCA", "Dreamcatcher Airways", "DREAM CATCHER", "United Kingdom", + "DCC", "Caribbean Air Cargo", "CARICARGO", "Barbados", + "DCE", "Dutch Caribbean Express", "DUTCH CARIBBEAN", "Netherlands Antilles", + "DCL", "Transportes Aéreos Don Carlos", "DON CARLOS", "Chile", + "DCM", "FLTPLAN (anonymized service)", "DOT COM", "United States", + "DCN", "Federal Armed Forces", "DIPLOMATIC CLEARANCE", "Germany", + "DCO", "David Crawshaw Consultants Limited", "", "United Kingdom", + "DCS", "DC Aviation", "TWIN STAR", "Germany", + "DCT", "Direct Flight", "", "United Kingdom", + "DCV", "Discover Air", "DISCOVER", "United States", + "DCX", "Daimler-Chrysler", "DAIMLER", "United States", + "DDA", "D & D Aviation", "DUSTY", "United States", + "DEA", "Delta Aerotaxi", "JET SERVICE", "Italy", + "DEE", "Dixie Airways", "TACAIR", "United States", + "DEF", "Aviation Defense Service", "TIRPA", "France", + "DEI", "Ecoair", "", "Algeria", + "DEL", "Carib Aviation", "RED TAIL", "Antigua and Barbuda", + "DES", "Chilcotin Caribou Aviation", "CHILCOTIN", "Canada", + "DET", "DETA Air", "SAMAL", "Kazakhstan", + "DEV", "Red Devils Parachute Display Team", "RED DEVILS", "United Kingdom", + "DFA", "Aero Coach Aviation", "AERO COACH", "United States", + "DFS", "Dwyer Aircraft Services", "DWYAIR", "United States", + "DGA", "Yellow River Delta General Aviation", "YELLOW RIVER", "China", + "DGO", "DGO Jet", "DGO JET", "Mexico", + "DGT", "Digital Equipment Corporation", "DIGITAL", "United States", + "DGX", "Dasnair", "DASNA", "Switzerland", + "DHA", "Dahla Airlines", "", "Democratic Republic of Congo", + "DHC", "De Havilland", "DEHAVILLAND", "Canada", + "DHE", "DAP Helicopteros", "HELIDAP", "Chile", + "DHK", "DHL Air Limited", "WORLD EXPRESS", "United Kingdom", + "DHV", "DHL Aviation", "WORLDSTAR", "South Africa", + "DHX", "DHL International", "DILMUN", "Bahrain", + "DIA", "Direct Air", "BLUE SKY", "United States", + "DIC", "Aeromedica", "AEROMEDICA", "Mexico", + "DIN", "Aerodin", "AERODIN", "Mexico", + "DIP", "Diplomatic Freight Services", "DIPFREIGHT", "United Kingdom", + "DIR", "Dirgantara Air Service", "DIRGANTARA", "Indonesia", + "DIS", "Di Air", "DI AIR", "Serbia", + "DIX", "Dix Aviation", "DIX FLIGHT", "Germany", + "DJE", "Durango Jet", "DURANGO JET", "Mexico", + "DJR", "Desert Jet", "DESERT FLIGHT", "United States", + "DJS", "DayJet", "DAYJET", "United States", + "DJT", "Dreamjet", "DREAMJET", "France", + "DKA", "Daka", "", "Kazakhstan", + "DKE", "Jubilee Airways", "DUKE", "United Kingdom", + "DKH", "Juneyao Airlines", "AIR JUNEYAO", "China", + "DKN", "Deccan Charters", "DECCAN", "India", + "DKT", "Sioux Falls Aviation", "DAKOTA", "United States", + "DKY", "Servicios Aéreos Elite", "DAKOY", "Spain", + "DLA", "Air Dolomiti", "DOLOMITI", "Italy", + "DLC", "Dehong South Asian General Aviation", "SOARCOPTER", "China", + "DLH", "Lufthansa", "LUFTHANSA", "Germany", + "DLI", "Delta Express International", "DELTA EXPRESS", "Ukraine", + "DLK", "Millennium Airlines", "DEKKANLANKA", "Sri Lanka", + "DLR", "Dala Air Services", "DALA AIR", "Nigeria", + "DLS", "Aero Modelo", "AEROMODELO", "Mexico", + "DMC", "Aerodinamica de Monterrey", "DINAMICAMONT", "Mexico", + "DMD", "Namdeb Diamond Corporation", "DIAMONDJET", "Namibia", + "DMF", "DMCFLY", "DEMLY", "Mexico", + "DMI", "Aeroservicios Dinamicos", "AERODINAMICO", "Mexico", + "DML", "Aerotaxis Dosmil", "", "Mexico", + "DNA", "Aerodespachos de El Salvador", "AERODESPACHOS", "El Salvador", + "DNC", "Aerodynamics Málaga", "FLYINGOLIVE", "Spain", + "DND", "Eldinder Aviation", "DINDER", "Sudan", + "DNI", "Servicios Aéreos Denim", "AERO DENIM", "Mexico", + "DNJ", "Aerodynamics Incorporated", "DYNAJET", "United States", + "DNK", "D&K Aviation", "DIRECT JET", "United States", + "DNL", "Dutch Antilles Express", "DUTCH ANTILLES", "Netherlands Antilles", + "DNR", "Dynamair Aviation", "DYNAMAIR", "Canada", + "DNS", "Dniproaviaservis Company", "", "Ukraine", + "DNU", "Danu Oro Transportas", "DANU", "Lithuania", + "DNV", "Donavia", "DONAVIA", "Russia", + "DNY", "Danish Navy", "DANISH NAVY", "Denmark", + "DOC", "Norsk Luftambulanse", "HELIDOC", "Norway", + "DOD", "USAF Air Mobility Operations Control Center", "", "United States", + "DOI", "U.S. Department of the Interior", "INTERIOR", "United States", + "DOJ", "Justice Prisoner and Alien Transportation System", "JUSTICE", "United States", + "DOM", "Dos Mundos", "DOS MUNDOS", "Dominican Republic", + "DON", "Donair Flying Club", "DONAIR", "United Kingdom", + "DOP", "Dancopter", "DANCOPTER", "Denmark", + "DOR", "Dornier", "DORNIER", "Germany", + "DOT", "Telnic Limited", "DOT TEL", "United Kingdom", + "DPJ", "Delta Private Jets", "JET CARD", "United States", + "DPL", "Dome Petroleum", "DOME", "Canada", + "DQA", "Maldivian (airline)", "SKYSURFER", "Maldives", + "DRB", "Didier Rousset Buy", "DIDIER", "Chile", + "DRC", "Triton Airlines", "TRITON AIR", "Canada", + "DRD", "Aereo Dorado", "AEREO DORADO", "Mexico", + "DRE", "Drummond Island Air", "MICHIGAN", "United States", + "DRF", "Dream Flyers Training Center", "", "Spain", + "DRK", "Druk Air", "ROYAL BHUTAN", "Bhutan", + "DRL", "Omni Air Transport", "DRILLER", "United States", + "DRM", "Airways Flight Training", "DARTMOOR", "United Kingdom", + "DRN", "Central De Discos De Reynosa", "DISCOS REYNOSA", "Mexico", + "DRO", "Aeronaves Del Noreste", "AERONORESTE", "Mexico", + "DRT", "Darta", "DARTA", "France", + "DRU", "Alrosa Air Company", "MIRNY", "Russia", + "DRX", "Des R Cargo Express", "", "Mauritania", + "DRY", "Deraya Air Taxi", "DERAYA", "Indonesia", + "DSA", "Danbury Airways", "DANBURY AIRWAYS", "United States", + "DSC", "Addis Air Cargo Services", "ADDIS CARGO", "Ethiopia", + "DSH", "Dash Air Charter", "DASH CHARTER", "United States", + "DSL", "Meridian Aviation", "DIESEL", "United Kingdom", + "DSM", "LATAM Argentina", "LAN AR", "Argentina", + "DSN", "DESNA", "DESNA", "Ukraine", + "DSO", "Dassault Falcon Service", "DASSAULT", "France", + "DSQ", "Dasab Airlines", "DASAB AIR", "Uganda", + "DSR", "DAS Air Cargo", "DAIRAIR", "Uganda", + "DST", "Aex Air", "DESERT", "United States", + "DSU", "Delta State University", "DELTA STATE", "United States", + "DTA", "TAAG Angola Airlines", "DTA", "Angola", + "DTH", "Tassili Airlines", "TASSILI AIR", "Algeria", + "DTN", "Data International", "DATA AIR", "Sudan", + "DTR", "DAT Danish Air Transport", "DANISH", "Denmark", + "DTV", "Centre Airlines", "DUTCH VALLEY", "United States", + "DTY", "Destiny Air Services", "DESTINY", "Sierra Leone", + "DUB", "Dubai Airwing", "DUBAI", "United Arab Emirates", + "DUK", "Ducair", "LION KING", "Luxembourg", + "DUN", "Dun'Air", "DUNAIR", "Mauritania", + "DUO", "Duo Airways", "FLY DUO", "United Kingdom", + "DVA", "Discovery Airways", "DISCOVERY AIRWAYS", "United States", + "DVB", "Don Avia", "DONSEBAI", "Kazakhstan", + "DVI", "Aerodavinci", "AERO DAVINCI", "Mexico", + "DVN", "Adventia", "", "Spain", + "DWN", "Dawn Air", "DAWN AIR", "United States", + "DWR", "Delaware Skyways", "DELAWARE", "United States", + "DWT", "Darwin Airline", "DARWIN", "Switzerland", + "DWW", "Jet Courier Service", "DON JUAN", "United States", + "DXH", "East Star Airlines", "EAST STAR", "China", + "DXP", "Dallas Express Airlines", "DALLAS EXPRESS", "United States", + "DYA", "Dynamic Airways", "DYNAMIC AIR", "United States", + "DZR", "Midwest Aviation", "DOZER", "United States", + "EAB", "Swiss Eagle", "SWISS EAGLE", "Switzerland", + "EAC", "Executive Air Charter", "EXECAIR", "United States", + "EAD", "Escola De Aviacao Aerocondor", "AERO-ESCOLA", "Portugal", + "EAE", "Aeroservicios Ecuatorianos", "AECA", "Ecuador", + "EAF", "European Aviation Air Charter", "EUROCHARTER", "United Kingdom", + "EAG", "Eagle Airways", "EAGLE", "New Zealand", + "EAH", "Baltimore Airways", "EASTERN", "United States", + "EAI", "Emerald Airlines", "GEMSTONE", "Ireland", + "EAK", "Euro-Asia Air", "EAKAZ", "Kazakhstan", + "EAL", "European Air Express", "STAR WING", "Germany", + "EAM", "Embassy Airlines", "EMBASSY AIR", "Nigeria", + "EAN", "Skypower Express Airways", "NIGERIA EXPRESS", "Nigeria", + "EAP", "Aero-Pyrenees", "AERO-PYRENEES", "France", + "EAQ", "Eastern Australia Airlines", "EASTERN", "Australia", + "EAR", "Transporte Ejecutivo Aéreo", "EJECUTIVO-AEREO", "Mexico", + "EAT", "Air Transport", "TRANS EUROPE", "Slovakia", + "EAV", "Eagle Airlines Luftverkehrsges", "MAYFLOWER", "Austria", + "EAX", "Eastern Air Executive", "EASTEX", "United Kingdom", + "EAZ", "Eastern Air", "EASAIR", "Zambia", + "EBA", "Bond Aviation", "BOND AVIATION", "Italy", + "EBC", "Aero Ejecutivo De Baja California", "CALIXJET", "Mexico", + "EBE", "Minebea Technologies", "MINEBEA", "United States", + "EBF", "MSR Flug-Charter", "SKYRUNNER", "Germany", + "EBG", "Eurosense", "EUROSENSE", "Bulgaria", + "EBJ", "European Business Jets", "", "United Kingdom", + "EBS", "AEG Aviation Services", "", "United States", + "ECA", "Eurocypria Airlines", "EUROCYPRIA", "Cyprus", + "ECB", "European Coastal Airlines", "COASTAL CLIPPER", "Croatia", + "ECC", "Eclair Aviation", "ECLAIR", "Czech Republic", + "ECD", "Ecotour", "ECOTOUR", "Mexico", + "ECE", "Air City", "AIRCITY", "Germany", + "ECF", "Eurocopter", "EUROCOPTER", "France", + "ECG", "Aero Ejecutivos RCG", "EJECTUIVOS RCG", "Mexico", + "ECI", "Eastern Carolina Aviation", "EASTERN CAROLINA", "United States", + "ECJ", "East Coast Jets", "EASTCOAST JET", "United States", + "ECL", "Aeronáutica Castellana", "AERO CASTELLANA", "Spain", + "ECM", "Aerolíneas Comerciales", "AERO COMERCIALES", "Mexico", + "ECN", "Euro Continental AIE", "EURO CONTINENTAL", "Spain", + "ECQ", "Eco Air", "SKYBRIDGE", "Nigeria", + "ECS", "Executive Aircraft Charter and Charter Services", "ECHO", "Ireland", + "ECT", "East Coast Airways", "EASTWAY", "South Africa", + "ECU", "Ecuavia", "ECUAVIA", "Ecuador", + "ECV", "Ecuatoguineana De Aviación (EGA)", "EQUATOGUINEA", "Equatorial Guinea", + "ECX", "Ecomex Air Cargo", "AIR ECOMEX", "Angola", + "ECY", "Euroceltic Airways", "ECHELON", "United Kingdom", + "EDC", "Air Charter Scotland", "SALTIRE", "United Kingdom", + "EDD", "Líneas Aéreas Ejectuivas De Durango", "LINEAS DURANGO", "Mexico", + "EDJ", "Edwards Jet Center of Montana", "EDWARDS", "United States", + "EDL", "Polizeihubschrauberstaffel Bayern", "POLICE EDELWEISS", "Germany", + "EDO", "Elidolomiti", "ELIDOLOMITI", "Italy", + "EDR", "Líneas Aéreas Eldorado", "ELDORADRO", "Colombia", + "EDV", "Endeavor Air", "ENDEAVOR", "United States", + "EDW", "Edelweiss Air", "EDELWEISS", "Switzerland", + "EDY", "Apollo Air Service", "STOBART", "United Kingdom", + "EEA", "Empresa Ecuatoriana De Aviación", "ECUATORIANA", "Ecuador", + "EEF", "Estonian Air Force", "ESTONIAN AIR FORCE", "Estonia", + "EES", "Eagle Express", "", "Serbia", + "EET", "Air Este", "AESTE", "Spain", + "EEU", "Eurofly Service", "EUROFLY", "Italy", + "EEZ", "Eurofly", "E-FLY", "Italy", + "EFC", "Air Mana", "FLIGHT TAXI", "France", + "EFD", "Eisele Flugdienst", "EVER FLIGHT", "Germany", + "EFG", "Elifriulia", "ELIFRIULIA", "Italy", + "EFS", "EFAOS- Agencia De Viagens e Turismo", "EFAOS", "Angola", + "EFT", "Embassy Freight Company", "EMBASSY FREIGHT", "United States", + "EFW", "BA Euroflyer", "GRIFFIN", "United Kingdom", + "EGJ", "Eagle Jet Charter", "EAGLE JET", "United States", + "EGL", "Capital Trading Aviation", "PRESTIGE", "United Kingdom", + "EGN", "Eagle Aviation France", "FRENCH EAGLE", "France", + "EGO", "Gauteng Air Cargo", "GAUTENG", "South Africa", + "EGR", "Eagle Air", "EAGLE SIERRA", "Sierra Leone", + "EGU", "Eagle Air", "AFRICAN EAGLE", "Uganda", + "EGV", "Enrique Gleisner Vivanco", "GLEISNER", "Chile", + "EGX", "Eagle Air Company", "THAI EAGLE", "Thailand", + "EGY", "Egyptian Air Force", "", "Egypt", + "EHA", "East Hampton Aire", "AIRE HAMPTON", "United States", + "EHD", "E H Darby Aviation", "PLATINUM AIR", "United States", + "EHR", "Valfell-Verkflug", "ROTOR", "Iceland", + "EIA", "Evergreen International Airlines", "EVERGREEN", "United States", + "EIC", "Express International Cargo", "EXCARGO", "São Tomé and Príncipe", + "EIJ", "Efata Papua Airlines", "EFATA", "Indonesia", + "EIN", "Aer Lingus", "SHAMROCK", "Ireland", + "EIR", "Eirjet", "EIRJET", "Ireland", + "EIS", "EIS Aircraft", "COOL", "Germany", + "EIX", "Ei Air Exports", "AIR EXPORTS", "Ireland", + "EJA", "NetJets", "EXECJET", "United States", + "EJC", "Grupo De Aviación Ejecutiva", "GRUPOEJECUTIVA", "Mexico", + "EJD", "Elite Jets", "ELITE DUBAI", "United Arab Emirates", + "EJM", "Executive Jet Management", "JET SPEED", "United States", + "EJO", "Execujet Middle East", "MIDJET", "United Arab Emirates", + "EJP", "Aeroservicios Ejecutivos Corporativos", "EJECCORPORATIVOS", "Mexico", + "EJS", "Eurojet Servis", "EEJAY SERVICE", "Czech Republic", + "EJT", "Eclipse Aviation", "ECLIPSE JET", "United States", + "EJU", "easyJet Europe", "ALPINE", "Austria", + "EJV", "Compania Ejecutiva", "EJECUTIVA", "Mexico", + "EJX", "Egyptian Aviation", "", "Egypt", + "EKA", "Equaflight Service", "EQUAFLIGHT", "Republic of the Congo", + "EKC", "East Kansas City Aviation", "BLUE GOOSE", "United States", + "ELA", "Eastland Air", "", "Australia", + "ELB", "Ellinair", "ELLINAIR HELLAS", "Greece", + "ELH", "Elilario Italia", "LARIO", "Italy", + "ELJ", "Delta Private Jets", "ELITE JET", "United States", + "ELL", "Estonian Air", "ESTONIAN", "Estonia", + "ELM", "Crelam", "CRELAM", "Mexico", + "ELN", "Eleron Aviation Company", "ELERON", "Ukraine", + "ELO", "Eurolot", "EUROLOT", "Poland", + "ELR", "Elrom Aviation and Investments", "", "Israel", + "ELS", "El Sal Air", "EL SAL", "El Salvador", + "ELT", "Elliott Aviation", "ELLIOT", "United States", + "ELU", "Egyptian Leisure Airlines", "EGYPTIAN LEISURE", "Egypt", + "ELV", "Transportes Aéreos Nacionales De Selva Tans", "AEREOS SELVA", "Peru", + "ELW", "Yellow Wings Air Services", "YELLOW WINGS", "Kenya", + "ELX", "Elan Express", "ELAN", "United States", + "ELY", "El Al Israel Airlines", "ELAL", "Israel", + "EMA", "Egyptian Aviation Company", "", "Egypt", + "EMB", "Empresa Brasileira De Aeronáutica", "EMBRAER", "Brazil", + "EMD", "Eaglemed (Ballard Aviation)", "EAGLEMED", "United States", + "EME", "Eastern Metro Express", "EMAIR", "United States", + "EMI", "Premium Air Shuttle", "BLUE SHUTTLE", "Nigeria", + "EMJ", "Yellow Air Taxi/Friendship Airways", "", "United States", + "EMN", "Examiner Training Agency", "AGENCY", "United Kingdom", + "EMP", "Empire Air Service", "EMPIRE", "United States", + "EMR", "Zenmour Airlines", "ZENMOUR", "Mauritania", + "EMS", "Aero Servicios Empresariales", "SERVIEMPRESARIAL", "Mexico", + "EMT", "Emetebe", "EMETEBE", "Ecuador", + "EMU", "East Asia Airlines", "", "Macao", + "EMX", "Euromanx Airways", "EUROMANX", "Austria", + "ENA", "Dirección General de Aviación Civil y Telecomunicasciones", "ENA", "Spain", + "ENC", "Endecots", "ENDECOTS", "Ecuador", + "END", "Arrendadora y Transportadora Aérea", "ARRENDADORA", "Mexico", + "ENI", "Enimex", "ENIMEX", "Estonia", + "ENK", "Executive Airlink", "SUNBIRD", "United States", + "ENR", "Scenic Air", "", "Namibia", + "ENS", "Entergy Services", "ENTERGY SHUTTLE", "United States", + "ENT", "Enter Air", "ENTER", "Poland", + "ENV", "Victoria Aviation", "ENDEAVOUR", "United Kingdom", + "ENW", "Aeronaves Del Noroeste", "AERONOR", "Spain", + "ENY", "Envoy Air", "ENVOY", "United States", + "ENZ", "Jota Aviation", "ENZO", "United Kingdom", + "EOA", "Elilombarda", "LOMBARDA", "Italy", + "EOK", "Aero K", "AEROHANKUK", "South Korea", + "EOL", "Airailes", "EOLE", "France", + "EOM", "Aero Ermes", "AERO ERMES", "Mexico", + "EPB", "Eastern Pacific Aviation", "EAST PAC", "Canada", + "EPC", "Espace Aviation Services", "ESPACE", "Democratic Republic of the Congo", + "EPE", "Aero Empresarial", "AEROEMPRESARIAL", "Mexico", + "EPL", "Aero Transportes Empresariales", "EMPRESARIALES", "Mexico", + "EPR", "Express Airways", "EMPEROR", "Slovenia", + "EPU", "Aero Elite Acapulco", "ELITACAPULCO", "Mexico", + "EQC", "Ecuatorial Cargo", "ECUA-CARGO", "Equatorial Guinea", + "EQT", "Equatorial Airlines", "", "Equatorial Guinea", + "EQZ", "Equatair Air Services (Zambia)", "ZAMBIA CARGO", "Zambia", + "ERC", "Esso Resources Canada", "ESSO", "Canada", + "ERE", "Erie Airways", "AIR ERIE", "United States", + "ERF", "Erfoto", "ERFOTO", "Portugal", + "ERH", "Era Helicopters", "ERAH", "United States", + "ERI", "Aero Servicios Regiomontanos", "ASERGIO", "Mexico", + "ERJ", "Eurojet Italia", "JET ITALIA", "Italy", + "ERK", "Aerosec", "AEROSEC", "Chile", + "ERM", "Aeromaan", "EOMAAN", "Mexico", + "ERO", "Aeroecom", "AEROECOM", "Venezuela", + "ERT", "Eritrean Airlines", "ERITREAN", "Eritrea", + "ERV", "Yerevan-Avia", "YEREVAN-AVIA", "Armenia", + "ERX", "Earth Airlines Services", "EARTH AIR", "Nigeria", + "ESB", "Aereosaba", "AEREOSABA", "Mexico", + "ESC", "El Sol De América", "SOLAMERICA", "Venezuela", + "ESE", "Ensenada Vuelos Especiales", "ENSENADA ESPECIAL", "Mexico", + "ESF", "Estafeta Carga Aérea", "", "Mexico", + "ESI", "ESI Eliservizi Italiani", "ELISERVIZI", "Italy", + "ESJ", "Eastern SkyJets", "EASTERN SKYJETS", "United Arab Emirates", + "ESL", "Russian Sky Airlines", "RADUGA", "Russia", + "ESN", "Euro Sun", "EURO SUN", "Turkey", + "ESO", "Avitat", "", "United Kingdom", + "ESR", "Eastar Jet", "EASTAR", "South Korea", + "ESS", "Eos Airlines", "NEW DAWN", "United States", + "EST", "Carga Express Internacional", "CARGAINTER", "Mexico", + "ESU", "Aerolíneas Ejecutivas Del Sureste", "ALESUR", "Mexico", + "ESX", "Euroskylink", "CATFISH", "United Kingdom", + "ESZ", "Aeronáutica La Esperanza", "ESPERANZA", "Mexico", + "ETA", "Estrellas Del Aire", "ESTRELLAS", "Mexico", + "ETC", "African Transport Trading and Investment Company", "TRANATTICO", "Sudan", + "ETD", "Etihad Airways", "ETIHAD", "United Arab Emirates", + "ETE", "Aero Siete", "AEROSIETE", "Mexico", + "ETH", "Ethiopian Airlines", "ETHIOPIAN", "Ethiopia", + "ETI", "H-Bird Aviation Services AB", "JETHAWK", "Sweden", + "ETL", "Patterson Aviation Company", "ENTEL", "United States", + "ETM", "Etram Air Wing", "ETRAM", "Angola", + "ETN", "Chim-Nir Aviation", "CHIMNIR", "Israel", + "ETP", "Empire Test Pilots' School", "TESTER", "United Kingdom", + "ETS", "Flygtransporter I Nykoping", "EXTRANS", "Sweden", + "ETV", "European Executive", "EURO EXEC", "United Kingdom", + "EUC", "Eurocontrol", "", "Belgium", + "EUG", "Euroguineana de Aviación", "EUROGUINEA", "Equatorial Guinea", + "EUK", "Aer Lingus UK", "GREEN FLIGHT", "United Kingdom", + "EUP", "Pan Europeenne Air Service", "SAVOY", "France", + "EUT", "European 2000 Airlines", "FIESTA", "Malta", + "EUU", "Euroamerican Air", "EUROAMERICAN", "Uruguay", + "EUW", "EFS European Flight Service", "EUROWEST", "Sweden", + "EUY", "EU Airways", "EUROAIRWAYS", "Ireland", + "EVA", "EVA Air", "EVA", "Taiwan", + "EVE", "Air Evex", "SUNBEAM", "Germany", + "EVK", "Everett Aviation", "EVERETT", "Kenya", + "EVL", "Evolem Aviation", "EVOLEM", "France", + "EVM", "Natural Environment Research Council", "SCIENCE", "United Kingdom", + "EVN", "Euraviation", "EURAVIATION", "Italy", + "EVR", "Aeronautical Academy of Europe", "DIANA", "Portugal", + "EVS", "Exploits Valley Air Services", "EVAS", "Canada", + "EVT", "Everett Limited", "", "Tanzania", + "EVY", "34 Squadron Royal Australian Air Force", "Multiple", "", + "EWE/EWL", "Eurowings Europe", "EUROPWINGS BLACK PEARL", "Austria", + "EWG", "Eurowings", "EUROWINGS", "Germany", + "EWL", "Eurowings Europe", "Black Pearl", "Malta", + "EWR", "Ewa Air", "MAYOTTE AIR", "France", + "EWS", "Enterprise World Airways", "WORLD ENTERPRISE", "Democratic Republic of the Congo", + "EWW", "Emery Worldwide Airlines", "EMERY", "United States", + "EXA", "Execair Aviation", "CANADIAN EXECAIRE", "Canada", + "EXB", "Brazilian Army Aviation", "BRAZILIAN ARMY", "Brazil", + "EXC", "European Executive Express", "ECHO EXPRESS", "Sweden", + "EXE", "Executive Flight", "EXEC", "United States", + "EXF", "Eximflight", "EXIMFLIGHT", "Mexico", + "EXG", "Air Exchange", "EXCHANGE", "United States", + "EXH", "G5 Executive", "BATMAN", "Switzerland", + "EXJ", "Executive Jet Charter", "", "United Kingdom", + "EXK", "Executive Airlines", "EXECUTIVE EAGLE", "United States", + "EXL", "Sunshine Express Airlines", "", "Australia", + "EXM", "United Kingdom Civil Aviation Authority", "EXAM", "United Kingdom", + "EXN", "Exin", "EXIN", "Poland", + "EXP", "Island Air Express", "ISLAND EXPRESS", "United States", + "EXR", "Expertos En Carga", "EXPERTOS ENCARGA", "Mexico", + "EXS", "Jet2.com", "CHANNEX", "United Kingdom", + "EXT", "Night Express", "EXECUTIVE", "Germany", + "EXU", "Executive Airlines", "SACAIR", "Spain", + "EXV", "FitsAir", "EXPOAVIA", "Sri Lanka", + "EXW", "Executive Airlines Services", "ECHOLINE", "Nigeria", + "EXX", "International Air Corporation", "EXPRESS INTERNATIONAL", "United States", + "EXY", "South African Express", "EXPRESSWAYS", "South Africa", + "EXZ", "East African Safari Air Express", "TWIGA", "Kenya", + "EYE", "F.S. Air Service", "SOCKEYE", "United States", + "EZB", "Flugschule Eichenberger", "EICHENBURGER", "Switzerland", + "EZD", "Philippines AirAsia", "REDHOT", "Philippines", + "EZE", "Eastern Airways", "EASTFLIGHT", "United Kingdom", + "EZJ", "Ezjet GT", "GUYANA JET", "Guyana", + "EZS", "easyJet Switzerland", "TOPSWISS", "Switzerland", + "EZX", "Eagle Express Air Charter", "EAGLEXPRESS", "Malaysia", + "EZY", "easyJet UK", "EASY", "United Kingdom", + "FAB", "First Air", "FIRST AIR", "Canada", + "FAC", "Atlantic Helicopters", "FAROECOPTER", "Denmark", + "FAD", "Rog-Air", "AIR FRONTIER", "Canada", + "FAE", "Freshaer", "WILDGOOSE", "United Kingdom", + "FAF", "Force Aerienne Francaise", "FRENCH AIR FORCE", "France", + "FAG", "Argentine Air Force", "FUAER", "Argentina", + "FAH", "Farnair Hungary", "BLUE STRIP", "Hungary", + "FAK", "Financial Airxpress", "FACTS", "United States", + "FAL", "Friendship Air Alaska", "FRIENDSHIP", "United States", + "FAM", "Fumigación Aérea Andaluza", "FAASA", "Spain", + "FAN", "AF-Air International", "FANBIRD", "Angola", + "FAO", "Falcon Air Express", "PANTHER", "United States", + "FAP", "F Air", "FAIR SCHOOL", "Czech Republic", + "FAQ", "Aerofaq", "", "Ecuador", + "FAR", "Falcon Air", "FALCAIR", "Slovenia", + "FAS", "Florida Air Cargo", "FLORIDA CARGO", "United States", + "FAT", "ASL Airlines Switzerland", "FARNER", "Switzerland", + "FAU", "Falcon Airline", "FALCON AIRLINE", "Nigeria", + "FAV", "Fair Aviation", "FAIRAVIA", "South Africa", + "FAW", "Falwell Aviation", "FALWELL", "United States", + "FAX", "Midwest Air Freighters", "FAIRFAX", "United States", + "FAY", "Fayban Air Services", "FAYBAN AIR", "Nigeria", + "FAZ", "Flint Aviation Services", "FLINT AIR", "United States", + "FBA", "Fab Air", "FAB AIR", "Kyrgyzstan", + "FBF", "Fine Airlines", "FINE AIR", "United States", + "FBO", "TAG Farnborough Airport", "", "United Kingdom", + "FBU", "French Bee", "FRENCH BEE", "France", + "FBW", "Aviation Data Systems", "", "United States", + "FBZ", "Flybondi", "BONDI", "Argentina", + "FCA", "First Choice Airways", "JETSET", "United Kingdom", + "FCB", "Cobalt", "NEW AGE", "Cyprus", + "FCC", "First Cambodia Airlines", "FIRST CAMBODIA", "Cambodia", + "FCE", "Flycolumbia", "FLYCOLUMBIA", "Spain", + "FCI", "Air Carriers", "FAST CHECK", "United States", + "FCJ", "AirSprint US", "FRACJET", "United States", + "FCK", "FCS Flight Calibration Services", "NAV CHECKER", "Germany", + "FCL", "Florida Coastal Airlines", "FLORIDA COASTAL", "United States", + "FCN", "Falcon Air", "FALCON", "Sweden", + "FCO", "Aerofrisco", "AEROFRISCO", "Mexico", + "FCP", "Nelson Aviation College Ltd", "FLIGHTCORP", "New Zealand", + "FCR", "Flying Carpet Company", "FLYING CARPET", "Lebanon", + "FCS", "Facts Air", "MEXFACTS", "Mexico", + "FCT", "Fly CI Limited", "DEALER", "United Kingdom", + "FCU", "Alfa 4", "", "Mexico", + "FCV", "Flight Centre Victoria", "NAVAIR", "Canada", + "FDA", "Fuji Dream Airlines", "FUJI DREAM", "Japan", + "FDB", "Flydubai", "SKYDUBAI", "UAE", + "FDF", "IVV Femida", "", "Russia", + "FDL", "Farmingdale State University", "FARMINGDALE STATE", "United States", + "FDN", "Dolphin Air", "FLYING DOLPHIN", "United Arab Emirates", + "FDO", "France Douanes", "FRENCH CUSTOM", "France", + "FDP", "Flight Dispatch Services", "", "Poland", + "FDR", "Federal Air", "FEDAIR", "South Africa", + "FDS", "African Medical and Research Foundation", "FLYDOC", "Kenya", + "FDX", "FedEx Express", "FEDEX", "United States", + "FDY", "Sun Air International", "FRIENDLY", "United States", + "FEA", "Far Eastern Air Transport", "Far Eastern", "Taiwan", + "FEC", "Denver Express", "FALCON EXPRESS", "United States", + "FED", "Líneas Aéreas Federales", "FEDERALES", "Argentina", + "FEE", "Fly Europa Limited", "FLY EURO", "United Kingdom", + "FEG", "FlyEgypt", "SKY EGYPT", "Egypt", + "FEI", "Eagle Air", "ARCTIC EAGLE", "Iceland", + "FEO", "Aeroferinco", "FERINCO", "Mexico", + "FER", "Feria Aviación", "FERIA", "Spain", + "FES", "Aero Taxis Y Servicios Alfe", "AERO ALFE", "Mexico", + "FET", "Old Dominion Freight Lines", "FREIGHT LINE", "United States", + "FEX", "Flightexec", "FLIGHTEXEC", "Canada", + "FEY", "Fly Easy", "FLYEASY", "India", + "FFA", "Avialesookhrana", "AVIALESOOKHRANA", "Russia", + "FFB", "Africair Service", "FOXTROT FOXTROT", "Senegal", + "FFC", "Fairoaks Flight Centre", "FAIROAKS", "United Kingdom", + "FFD", "Stuttgarter Flugdienst", "FIRST FLIGHT", "Germany", + "FFF", "Freedom Air Services", "INTER FREEDOM", "Nigeria", + "FFG", "Flugdienst Fehlhaber", "WITCHCRAFT", "Germany", + "FFH", "Shalom Air Services", "PEACE AIR", "Nigeria", + "FFI", "Infinit Air", "INFINIT", "Spain", + "FFL", "ForeFlight", "FOREFLIGHT", "United States", + "FFM", "Firefly", "FIREFLY", "Malaysia", + "FFP", "Fischer Air Polska", "FLYING FISH", "Poland", + "FFR", "Fischer Air", "FISCHER", "Czech Republic", + "FFS", "Florida Department of Agriculture", "FORESTRY", "United States", + "FFT", "Frontier Airlines", "FRONTIER FLIGHT", "United States", + "FFU", "GEC Marconi Avionics", "FERRANTI", "United Kingdom", + "FFY", "Fun Flying Thai Air Service", "FUN FLYING", "Thailand", + "FGA", "Georgian Aviation Federation", "GEORGIA FED", "Georgia", + "FGC", "Departament d'Agricultura de la Generalitat de Catalunya", "FORESTALS", "Spain", + "FGE", "Fly Georgia", "GEORGIA WING", "Georgia", + "FGN", "National Gendarmerie", "FRANCE GENDARME", "France", + "FGP", "Flying-Research Aerogeophysical Center", "FLYING CENTER", "Russia", + "FGS", "Elitellina", "ELITELLINA", "Italy", + "FGT", "Aero Freight", "FREIAERO", "Mexico", + "FGY", "Froggy Corporate Aviation", "", "Australia", + "FHE", "Hello", "FLYHELLO", "Switzerland", + "FHL", "Fast Helicopters", "FINDON", "United Kingdom", + "FHS", "Forth and Clyde Helicopter Services", "HELISCOT", "United Kingdom", + "FHY", "Freebird Airlines", "FREEBIRD AIR", "Turkey", + "FIA", "Fly One", "Fly One", "Moldova", + "FIC", "Aerosafin", "AEROSAFIN", "Mexico", + "FIE", "FlyOne Armenia", "ARMRIDER", "Armenia", + "FIF", "Air Finland", "AIR FINLAND", "Finland", + "FIH", "FinnHEMS", "FINNHEMS", "Finland", + "FIL", "Fly Line", "FLYLINE", "Spain", + "FIN", "Finnair", "FINNAIR", "Finland", + "FIR", "First Line Air", "FIRSTLINE AIR", "Sierra Leone", + "FIV", "CitationAir", "FIVE STAR", "United States", + "FIX", "Airfix Aviation", "AIRFIX", "Finland", + "FJC", "Falcon Jet Centre", "FALCONJET", "United Kingdom", + "FJE", "Silverjet", "ENVOY", "United Kingdom", + "FJI", "Fiji Airways", "PACIFIC", "Fiji", + "FJL", "Fly Jinnah", "Okaab", "Pakistan", + "FJS", "Florida Jet Service", "FLORIDAJET", "United States", + "FJX", "Jet Sky Cargo and Air Charter", "", "Kenya", + "FKI", "FLM Aviation Mohrdieck", "KIEL AIR", "Germany", + "FKL", "Kelner Airways", "KELNER", "Canada", + "FKS", "Focus Air", "FOCUS", "United States", + "FLA", "Midway Express", "PALM", "United States", + "FLC", "FINFO Flight Inspection Aircraft", "FLIGHT CHECK", "United States", + "FLD", "Air Falcon", "", "Pakistan", + "FLE", "Flair Airlines", "FLAIR", "Canada", + "FLF", "Friendship Airlines", "FRIEND AIR", "Uganda", + "FLG", "Pinnacle Airlines", "FLAGSHIP", "United States", + "FLH", "Sky Bus", "MILE HIGH", "United States", + "FLI", "Atlantic Airways", "FAROELINE", "Faroe Islands", + "FLK", "Flylink Express", "FLYLINK", "Spain", + "FLL", "Federal Airlines", "FEDERAL AIRLINES", "Sweden", + "FLM", "Fly Air", "FLY WORLD", "Turkey", + "FLN", "Flamingo Air-Line", "ILIAS", "Kazakhstan", + "FLR", "Fleetair", "FLEETAIR", "South Africa", + "FLU", "Flugschule Basel", "YELLOW FLYER", "Switzerland", + "FLW", "FLowair Aviation", "QUICKFLOW", "France", + "FLX", "Flight Express Inc.", "FLIGHT EXPRESS", "United States", + "FLY", "Fly Me Sweden", "FLYBIRD", "Sweden", + "FLZ", "Aero Leasing", "AIR FLORIDA", "United States", + "FMC", "Claessens International Limited", "CLAESSENS", "United Kingdom", + "FMG", "FMG Verkehrsfliegerschule Flughafen Paderborn-Lippstadt", "HUSKY", "Germany", + "FMI", "FMI Air", "FIRST MYANMAR", "Myanmar", + "FMR", "Flamingo Air", "FLAMINGO AIR", "United States", + "FMS", "Hadison Aviation", "HADI", "United States", + "FMT", "Air Fret De Mauritanie", "", "Mauritania", + "FMY", "Aviation Legere De L'Armee De Terre", "FRENCH ARMY", "France", + "FNA", "Norlandair", "NORLAND", "Iceland", + "FNC", "Finalair Congo", "FINALAIR CONGO", "Republic of the Congo", + "FNF", "Finnish Air Force", "FINNFORCE", "Finland", + "FNG", "Frontier Guard", "FINNGUARD", "Finland", + "FNK", "Feniks Airline", "AURIKA", "Kazakhstan", + "FNL", "Oulun Tilauslento", "FINN FLIGHT", "Finland", + "FNM", "Avio Nord", "", "Italy", + "FNO", "Aeroflota Del Noroeste", "RIAZOR", "Spain", + "FNT", "L-3 Communications Flight International Aviation", "FLIGHT INTERNATIONAL", "United States", + "FNV", "Transaviaservice", "TRANSAVIASERVICE", "Georgia", + "FNY", "France Marine Nationale", "FRENCH NAVY", "France", + "FOB", "Ford Motor Company", "FORDAIR", "United Kingdom", + "FOC", "Office Federal De'Aviation Civile", "FOCA", "Switzerland", + "FOI", "Flight Ops International", "", "United States", + "FOP", "Fokker", "", "Netherlands", + "FOR", "Formula One Management", "FORMULA", "United Kingdom", + "FOS", "Bel Limited", "", "Russia", + "FOX", "Flyr", "GREENSTAR", "Norway", + "FPA", "Flygprestanda", "", "Sweden", + "FPG", "TAG Aviation", "TAG AVIATION", "Switzerland", + "FPO", "Europe Airpost", "FRENCH POST", "France", + "FPR", "Peruvian Air Force", "", "Peru", + "FPS", "Flightpass Limited", "FLIGHTPASS", "United Kingdom", + "FPY", "Play", "PLAYER", "Iceland", + "FQA", "Quikjet Cargo Airlines", "QUIK LIFT", "India", + "FRA", "FR Aviation", "RUSHTON", "United Kingdom", + "FRB", "Fly Rak", "RAKWAY", "United Arab Emirates", + "FRC", "Icare Franche Compte", "FRANCHE COMPTE", "France", + "FRE", "Freedom Air", "FREEDOM", "United States", + "FRF", "Fleet Air International", "FAIRFLEET", "Hungary", + "FRG", "Freight Runners Express", "FREIGHT RUNNERS", "United States", + "FRJ", "Afrijet Airlines", "AFRIJET", "Nigeria", + "FRK", "Afrika Aviation Handlers", "AFRIFAST", "Kenya", + "FRM", "Federal Armored Service", "FEDARM", "United States", + "FRQ", "Afrique Chart'air", "CHARTER AFRIQUE", "Cameroon", + "FRR", "Fresh Air", "FRESH AIR", "Nigeria", + "FRT", "Aerofreight Airlines", "", "Russia", + "FRW", "Farwest Airlines", "FARWEST", "United States", + "FRX", "Fort Aero", "FORT AERO", "Estonia", + "FSA", "Foster Aviation", "FOSTER-AIR", "United States", + "FSB", "FSB Flugservice & Development", "SEABIRD", "Germany", + "FSC", "Four Star Aviation / Four Star Cargo", "FOUR STAR", "United States", + "FSD", "EFS-Flugservice", "FLUGSERVICE", "Germany", + "FSH", "Flash Airlines", "FLASH", "Egypt", + "FSL", "Flight Safety Limited", "FLIGHTSAFETY", "United Kingdom", + "FSR", "Flightstar Corporation", "FLIGHTSTAR", "United States", + "FSU", "Flight Support Sweden", "", "Sweden", + "FSW", "Faso Airways", "FASO", "Burkina Faso", + "FSX", "Flagship Express Services", "FLAG", "United States", + "FSY", "Algonquin Airlink", "FROSTY", "Canada", + "FTA", "Frontier Flying Service", "FRONTIER-AIR", "United States", + "FTC", "Air Affaires Tchad", "AFFAIRES TCHAD", "Chad", + "FTE", "Fotografia F3", "FOTOGRAFIA", "Spain", + "FTL", "Flightline", "FLIGHT-AVIA", "Spain", + "FTM", "Flyteam Aviation", "FLYTEAM", "United Kingdom", + "FTP", "Keystone Aerial Surveys", "FOOTPRINT", "United States", + "FTR", "Finist'air", "FINISTAIR", "France", + "FTS", "First Sabre", "FIRST SABRE", "Mexico", + "FTY", "ABC Bedarfsflug", "FLY TYROL", "Austria", + "FTZ", "Fastjet", "GREY BIRD", "Tanzania", + "FUF", "Servicios Aereos Fun Fly", "SERVIFUN", "Mexico", + "FUJ", "Fujairah Aviation Centre", "FUJAIRAH", "United Arab Emirates", + "FUM", "Fuxion Line Mexico", "FUNLINE", "Mexico", + "FUN", "Funtshi Aviation Service", "FUNTSHI", "Democratic Republic of the Congo", + "FUT", "Aereo Futuro", "AEREO FUTURO", "Mexico", + "FVA", "Cardinal/Air Virginia", "AIR VIRGINIA", "United States", + "FVS", "Falcon Aviation Services", "FALCON AVIATION", "United Arab Emirates", + "FWD", "Fair Wind Air Charter", "FAIR WIND", "United Arab Emirates", + "FWI", "Air Caraïbes", "FRENCH WEST", "France", + "FWK", "Flightworks", "", "United States", + "FWQ", "Flight West Airlines", "UNITY", "Australia", + "FWY", "Fairways Corporation", "FAIRWAYS", "United States", + "FWZ", "Flugwerkzeuge Aviation Software", "", "Austria", + "FXA", "Express Air", "EFFEX", "United States", + "FXC", "Fortunair Canada", "AIR FUTURE", "Canada", + "FXF", "VIP Air Charter", "FOX FLIGHT", "United States", + "FXI", "Air Iceland", "FAXI", "Iceland", + "FXL", "Fly Excellent", "FLY EXCELLENT", "Sweden", + "FXR", "Foxair", "WILDFOX", "Italy", + "FXT", "Flexflight", "", "Denmark", + "FYA", "Flyant", "FLYANT", "Spain", + "FYE", "Easy Link Aviation Services", "FLYME", "Nigeria", + "FYG", "Flying Service", "FLYING GROUP", "Belgium", + "FYH", "Flyhy Cargo Airlines", "FLY HIGH", "Thailand", + "FYN", "Comfort Air", "FLYNN", "Germany", + "FYS", "American Flyers", "AMERICAN FLYERS", "United States", + "GAA", "Business Express", "BIZEX", "United States", + "GAB", "Gendall Air", "GENDALL", "Canada", + "GAC", "GlobeAir", "DREAM TEAM", "Austria", + "GAD", "South Coast Aviation", "SOUTHCOAST", "United Kingdom", + "GAE", "Grand Aire Express", "GRAND EXPRESS", "United States", + "GAF", "German Air Force", "GERMAN AIR FORCE", "Germany", + "GAG", "Greybird Pilot Academy", "GEEBIRD", "Denmark", + "GAH", "Ga-Ma Helicoptere", "GAMHELICO", "France", + "GAI", "Moskovia Airlines", "GROMOV AIRLINE", "Russia", + "GAK", "Global Aviation and Services Group", "AVIAGROUP", "Libya", + "GAL", "Galaxy Air", "GALAXY", "Kyrgyzstan", + "GAM", "German Army", "GERMAN ARMY", "Germany", + "GAN", "Gander Aviation", "GANAIR", "Canada", + "GAO", "Golden Air", "GOLDEN", "Sweden", + "GAP", "Air Philippines", "ORIENT PACIFIC", "Philippines", + "GAQ", "Golfe Air Quebec", "GOLFAIR", "Canada", + "GAR", "Commodore Aviation", "", "Australia", + "GAS", "Galena Air Service", "GALENA AIR SERVICE", "United States", + "GAT", "Gulf Air Inc", "GULF TRANS", "United States", + "GAU", "Aerogaucho", "AEROGAUCHO", "Uruguay", + "GAV", "Granada Aviación", "GRANAVI", "Spain", + "GAX", "Full Express", "GRAND AIRE", "United States", + "GBB", "Global Aviation Operations", "GLOBE", "South Africa", + "GBE", "Gabon Express", "GABEX", "Gabon", + "GBH", "Global Avia Handling", "", "Russia", + "GBJ", "Aero Business Charter", "GLOBAL JET", "Germany", + "GBL", "GB Airways", "GEEBEE AIRWAYS", "United Kingdom", + "GBN", "Atlantic Airlines", "ATLANTIC GABON", "Gabon", + "GBO", "Ogooue Air Cargo", "", "Gabon", + "GBR", "Rader Aviation", "GREENBRIER AIR", "United States", + "GBS", "Global Air Services Nigeria", "GLOBAL SERVE", "Nigeria", + "GBT", "A-Jet Aviation Aircraft Management", "GLOBETROTTER", "Austria", + "GBX", "GB Airlink", "ISLAND TIGER", "United States", + "GCB", "Lignes Nationales Aeriennes - Linacongo", "LINACONGO", "Republic of the Congo", + "GCC", "GECAS", "GECAS", "Ireland", + "GCF", "Aeronor", "AEROCARTO", "Spain", + "GCH", "Gama Aviation Switzerland", "GAMA SWISS", "Switzerland", + "GCM", "Comair Flight Services", "GLOBECOM", "South Africa", + "GCN", "Gulf Central Airlines", "GULF CENTRAL", "United States", + "GCO", "Gemini Air Cargo", "GEMINI", "United States", + "GCR", "Tianjin Airlines", "BO HAI", "China", + "GCS", "GCS Air Service", "GALION", "United States", + "GCW", "Global Air Crew", "GLOBALCREW", "Denmark", + "GCY", "CHC Global Operations International", "HELIBIRD", "United Kingdom", + "GDA", "GoldAir", "AIR PARTNER", "United Kingdom", + "GDB", "Gendarmerie Belge", "BELGIAN GENERMERIE", "Belgium", + "GDD", "Golden Airlines", "GOLDEN AIRLINES", "United States", + "GDE", "Servicios Aéreos Gadel", "GADEL", "Mexico", + "GDF", "DF Aviation", "", "Ukraine", + "GDG", "S.P. Aviation", "GOLDEN GATE", "United States", + "GDH", "Guneydogu Havacilik Isletmesi", "RISING SUN", "Turkey", + "GDK", "Goldeck-Flug", "GOLDECK FLUG", "Austria", + "GDN", "Gerry's Dnata", "", "Pakistan", + "GDQ", "Lincoln Air National Guard", "", "United States", + "GEA", "Guinea Ecuatorial Airlines", "GEASA", "Equatorial Guinea", + "GEC", "Lufthansa Cargo", "LUFTHANSA CARGO", "Germany", + "GED", "Europe Air Lines", "LANGUEDOC", "France", + "GEE", "Geesair", "GEESAIR", "Canada", + "GEN", "GENSA", "GENSA-BRASIL", "Brazil", + "GES", "Gestair", "GESTAIR", "Spain", + "GET", "Get High", "AIR FLOW", "Portugal", + "GFA", "Gulf Air", "GULF AIR", "Bahrain", + "GFC", "Gail Force Express", "GAIL FORCE", "United States", + "GFD", "Gesellschaft Fur Flugzieldarstellung", "KITE", "Germany", + "GFF", "Griffin Aviation", "GRIFFIN AIR", "Cyprus", + "GFG", "Georgian National Airlines", "NATIONAL", "Georgia", + "GFI", "Caribbean Star Airlines", "CARIB STAR", "Antigua and Barbuda", + "GFN", "The 955 Preservation Group", "GRIFFON", "United Kingdom", + "GFO", "Aerovías del Golfo", "AEROVIAS GOLFO", "Mexico", + "GFS", "Gulfstream Airlines", "GULFSTAR", "United States", + "GFT", "Gulfstream International Airlines", "GULF FLIGHT", "United States", + "GFW", "GFW Aviation", "", "Australia", + "GGA", "First Flying Squadron", "JAWJA", "United States", + "GGC", "Cargo 360", "LONG-HAUL", "United States", + "GGE", "Gobierno De Guinea Ecuatorial", "", "Equatorial Guinea", + "GGF", "Georgian Cargo Airlines Africa", "GEORGIAN AFRICA", "Senegal", + "GGL", "Aeronáutica", "GIRA GLOBO", "Angola", + "GGN", "Air Georgian", "GEORGIAN", "Canada", + "GGS", "GATSA", "GATSA", "Mexico", + "GGT", "Trans Island Airways", "THUNDERBALL", "Bahamas", + "GGV", "Houston Jet Services", "GREGG AIR", "Austria", + "GGZ", "Global Georgian Airways", "GLOBAL GEORGIAN", "Georgia", + "GHB", "Ghana International Airlines", "GHANA AIRLINES", "Ghana", + "GHI", "GH Stansted Limited", "", "United Kingdom", + "GHL", "Aviance UK", "HANDLING", "United Kingdom", + "GHM", "Aero Service Bolivia", "", "Bolivia", + "GHN", "Air Ghana", "AIR GHANA", "Ghana", + "GHP", "Colvin Aviation", "GRASSHOPPER EX", "United States", + "GHS", "Gatari Hutama Air Services", "GATARI", "Indonesia", + "GHT", "Ghadames Air Transport", "", "Libya", + "GHV", "Ground Handling Service de Mexico", "GROUND HANDLING", "Mexico", + "GHY", "German Sky Airlines", "GERMAN SKY", "Germany", + "GIA", "Garuda Indonesia", "INDONESIA", "Indonesia", + "GIB", "GR-Avia", "GRAVIA", "Guinea", + "GIC", "Compagnie de Bauxites de Guinee", "CEBEGE", "Guinea", + "GIG", "Gacela Air Cargo", "GACELA AIR", "Mexico", + "GIH", "Union des Transports Africains de Guinee", "TRANSPORT AFRICAIN", "Guinea", + "GIJ", "Guinea Airways", "GUINEA AIRWAYS", "Guinea", + "GIK", "Seba Airlines", "SEBA", "Guinea", + "GIL", "African International Transport", "AFRICAN TRANSPORT", "Guinea", + "GIP", "Air Guinee Express", "FUTURE EXPRESS", "Guinea", + "GIQ", "Guinee Paramount Airlines", "GUIPAR", "Guinea", + "GIY", "Probiz Guinee", "PROBIZ", "Guinea", + "GIZ", "Africa Airlines", "AFRILENS", "Guinea", + "GJA", "Globe Jet", "", "Lebanon", + "GJB", "Trans-Air-Link", "SKY TRUCK", "United States", + "GJS", "GoJet Airlines", "LINDBERGH", "United States", + "GJT", "Gestión Aérea Ejecutiva", "BANJET", "Spain", + "GKA", "US Army Parachute Team", "GOLDEN KNIGHTS", "United States", + "GLA", "Great Lakes Airlines", "LAKES AIR", "United States", + "GLB", "Global Airways (GLB)", "GLO-AIR", "United States", + "GLC", "Global Aircargo", "", "Bahrain", + "GLD", "Golden Star Air Cargo", "GOLDEN STAR", "Sudan", + "GLE", "Goliaf Air", "GOLIAF AIR", "São Tomé and Príncipe", + "GLF", "Gulfstream Aerospace", "GULFSTREAM TEST", "United States", + "GLG", "Avianca Ecuador", "GALAPAGOS", "Ecuador", + "GLJ", "Global Jet Austria", "GLOBAL JET AUSTRIA", "Austria", + "GLL", "Air Gemini", "TWINS", "Angola", + "GLM", "Aero Services Mali", "GLOBAL MALI", "Mali", + "GLO", "Gol Transportes Aéreos", "GOL TRANSPORTE", "Brazil", + "GLP", "Globus Airlines", "GLOBUS", "Russia", + "GLQ", "El Quilada International Aviation", "QUILADA", "Sudan", + "GLR", "Central Mountain Air", "GLACIER", "Canada", + "GLS", "Galaircervis", "GALS", "Ukraine", + "GLT", "Aero Charter", "GASLIGHT", "United States", + "GLU", "Great Lakes Airways (Uganda)", "LAKES CARGO", "Uganda", + "GLW", "Global Wings", "", "Japan", + "GLX", "Gelix Airlines", "RUSSIAN BIRD", "Russia", + "GMA", "Gama Aviation", "GAMA", "United Kingdom", + "GMC", "General Motors", "GENERAL MOTORS", "United States", + "GME", "Aguilas Mayas Internacional", "MAYAN EAGLES", "Guatemala", + "GMG", "GMG Airlines", "GMG", "Bangladesh", + "GMH", "Hughes Aircraft Company", "HUGHES EXPRESS", "United States", + "GMI", "Germania", "GERMANIA", "Germany", + "GMJ", "Gamisa Aviación", "GAMISA", "Spain", + "GML", "G & L Aviation", "GEEANDEL", "South Africa", + "GMM", "Aerotaxis Guamuchil", "AEROGUAMUCHIL", "Mexico", + "GMQ", "Germania Express", "CORGI", "Germany", + "GMR", "Golden Myanmar Airlines", "GOLDEN MYANMAR", "Myanmar", + "GMS", "Aeroservicios Gama", "SERVICIOS GAMA", "Mexico", + "GMT", "Grupo Aéreo Monterrey", "GRUPOMONTERREY", "Mexico", + "GNA", "Servicios Aéreos Gana", "SERVIGANA", "Mexico", + "GNC", "Guinea Cargo", "GUINEA CARGO", "Equatorial Guinea", + "GND", "Grand Airways", "GRAND VEGAS", "United States", + "GNF", "Gandalf Airlines", "Gandalf", "Italy", + "GNJ", "Gain Jet Aviation", "HERCULES JET", "Greece", + "GNL", "135 Airways", "GENERAL", "United States", + "GNR", "Gambia International Airlines", "GAMBIA INTERNATIONAL", "Gambia", + "GNS", "Eastern Executive Air Charter", "GENESIS", "United Kingdom", + "GNT", "Amber Air", "GINTA", "Lithuania", + "GNX", "Genex", "", "Belarus", + "GNY", "German Navy", "GERMAN NAVY", "Germany", + "GNZ", "General Aviation", "GONZO", "Poland", + "GOA", "Alberta Government", "ALBERTA", "Canada", + "GOB", "Dash Aviation", "PILGRIM", "United Kingdom", + "GOF", "Gof-Air", "GOF-AIR", "Mexico", + "GOI", "Gofir", "SWISS HAWK", "Switzerland", + "GOJ", "EuroJet Aviation", "GOJET", "United Kingdom", + "GOL", "Cardolaar", "CARGOLAAR", "Namibia", + "GOM", "Gomel Airlines", "GOMEL", "Belarus", + "GOP", "Gospa Air", "GOSPA AIR", "Mexico", + "GOR", "Gorlitsa Airlines", "GORLITSA", "Ukraine", + "GOS", "Goldfields Air Services", "", "Australia", + "GOT", "WaltAir", "GOTHIC", "Sweden", + "GOW", "GoAir", "GOAIR", "India", + "GPA", "Golden Pacific Airlines", "GOLDEN PAC", "United States", + "GPC", "Gulf Pearl Air Lines", "AIR GULFPEARL", "Libya", + "GPD", "Tradewind Aviation", "GOODSPEED", "United States", + "GPE", "GP Express Airlines", "REGIONAL EXPRESS", "United States", + "GPM", "Grup Air-Med", "GRUPOMED", "Spain", + "GPR", "GPM Aeroservicio", "GPM AEROSERVICIO", "Mexico", + "GRA", "Guardian Air Asset Management", "FLEX", "South Africa", + "GRD", "National Grid plc", "GRID", "United Kingdom", + "GRE", "Air Scotland", "GREECE AIRWAYS", "Greece", + "GRG", "Air Georgia", "AIR GEORGIA", "Georgia", + "GRI", "Air Cargo Center", "", "São Tomé and Príncipe", + "GRL", "Air Greenland", "GREENLAND", "Greenland", + "GRP", "Great Plains Airlines", "GREAT PLAINS", "United States", + "GRR", "Agroar - Trabalhos Aéreos", "AGROAR", "Portugal", + "GRS", "Golden Rule Airlines", "GOLDEN RULE", "Kyrgyzstan", + "GRT", "Gabon-Air-Transport", "", "Gabon", + "GRV", "Vernicos Aviation", "NIGHT RIDER", "Greece", + "GRX", "Aircompany Grodno", "GRODNO", "Belarus", + "GRY", "New York State Police", "GRAY RIDER", "United States", + "GRZ", "Government of Zambia Communications Flight", "COM FLIGHT", "Zambia", + "GSA", "Garden State Airlines", "GARDEN STATE", "United States", + "GSJ", "Grossmann Jet Service", "GROSSJET", "Czech Republic", + "GSK", "Global Sky Aircharter", "GLOBAL SKY", "United States", + "GSL", "Geographic Air Surveys", "SURVEY-CANADA", "Canada", + "GSP", "Airlift Alaska", "GREEN SPEED", "United States", + "GSS", "Global Supply Systems", "JET LIFT", "United Kingdom", + "GSV", "Agrocentr-Avia", "AGRAV", "Kazakhstan", + "GSW", "Sky Wings Airlines", "", "Greece", + "GSY", "Guard Systems", "GUARD AIR", "Norway", + "GTC", "Altin Havayolu Tasimaciligi Turizm Ve Ticaret", "GOLDEN WINGS", "Turkey", + "GTH", "General Aviation Flying Services", "GOTHAM", "United States", + "GTI", "Atlas Air", "GIANT", "United States", + "GTP", "Aerotaxi Grupo Tampico", "GRUPOTAMPICO", "Mexico", + "GTR", "Gestar", "STAR GESTAR", "Chile", + "GTV", "Empresa de Aviación Aerogaviota", "GAVIOTA", "Cuba", + "GTX", "GTA Air", "BIG-DEE", "United States", + "GTY", "National Aviation Company", "", "Egypt", + "GUA", "Aerotaxis de Aguascalientes", "AGUASCALIENTES", "Mexico", + "GUE", "Aero Servicio Guerrero", "AERO GUERRERO", "Mexico", + "GUF", "Gulf African Airlines", "GULF AFRICAN", "The Gambia", + "GUG", "Aviateca", "AVIATECA", "Guatemala", + "GUJ", "Gujarat Airways", "GUJARATAIR", "India", + "GUL", "Gull Air", "GULL-AIR", "United States", + "GUM", "Gum Air", "GUM AIR", "Suriname", + "GUN", "Grant Aviation", "HOOT", "United States", + "GUS", "Guja", "GUJA", "Mexico", + "GUY", "Air Guyane", "GREEN BIRD", "French Guiana", + "GVG", "Flygaktiebolaget Gota Vingar", "BLUECRAFT", "Sweden", + "GVI", "Air Victoria Georgia", "IRINA", "Georgia", + "GVN", "Gavina", "GAVINA", "Spain", + "GWA", "Great Western Air", "G-W AIR", "United States", + "GWI", "Germanwings", "GERMAN WINGS", "Germany", + "GWL", "Great Wall Airlines", "GREAT WALL", "China", + "GWN", "Gwyn Aviation", "GWYN", "United Kingdom", + "GWS", "General Airways", "GENAIR", "South Africa", + "GWY", "USA3000 Airlines", "GETAWAY", "United States", + "GXA", "Grixona", "GRIXONA", "Moldova", + "GXL", "Star XL German Airlines", "STARDUST", "Germany", + "GXY", "Galaxy Airlines", "GALAX", "Japan", + "GYP", "Eagle Aviation", "GYPSY", "United Kingdom", + "GZA", "Excellent Air", "EXCELLENT AIR", "Germany", + "GZD", "Grizodubova Air Company", "GRIZODUBOVA AIR", "Russia", + "GZP", "Gazpromavia", "GAZPROMAVIA", "Russia", + "GZQ", "Zagros Air", "ZAGROS", "Iraq", + "HAA", "Helicopteros Agroforestal", "AGROFORESTAL", "Chile", + "HAC", "Henebury Aviation", "", "Australia", + "HAD", "Air d'Ayiti", "HAITI AVIA", "Haiti", + "HAF", "Hellenic Air Force", "HELLENIC AIR FORCE", "Greece", + "HAG", "Hageland Aviation Services", "HAGELAND", "United States", + "HAH", "Air Comores International", "AIR COMORES", "Comoros", + "HAI", "Century Aviation International", "", "Canada", + "HAJ", "Hajvairy Airlines", "HAJVAIRY", "Pakistan", + "HAK", "Pegasus Helicopters", "HELIFALCON", "Norway", + "HAL", "Hawaiian Airlines", "HAWAIIAN", "United States", + "HAM", "Haiti Ambassador Airlines", "", "Haiti", + "HAN", "Hansung Airlines", "HANSUNG AIR", "Republic of Korea", + "HAP", "Helicopteros Aero Personal", "HELIPERSONAL", "Mexico", + "HAR", "Harbor Airlines", "HARBOR", "United States", + "HAS", "Honduras Airlines", "HONDURAS AIR", "Honduras", + "HAT", "Air Taxi", "TAXI BIRD", "France", + "HAU", "Skyhaul", "SKYHAUL", "Japan", + "HAV", "Havilah Air Services", "HAVILAH", "Nigeria", + "HAW", "Bangkok Aviation Center", "THAI HAWK", "Thailand", + "HAX", "Benair", "SCOOP", "Norway", + "HAY", "Hamburg Airways", "HAMBURG AIRWAYS", "Germany", + "HBA", "Trail Lake Flying Service", "HARBOR AIR", "United States", + "HBC", "Haitian Aviation Line", "HALISA", "Haiti", + "HBI", "CHC Denmark", "HELIBIRD", "Denmark", + "HBL", "Faroecopter", "HELIBLUE", "Denmark", + "HBT", "Polizeihubschrauberstaffel Thüringen", "HABICHT", "Germany", + "HBU", "Universal Avia", "KHARKIV UNIVERSAL", "Ukraine", + "HCA", "Lake Havasu Air Service", "HAVASU", "United States", + "HCB", "Helenair (Barbados)", "HELEN", "Barbados", + "HCC", "Holidays Czech Airlines", "CZECH HOLIDAYS", "Czech Republic", + "HCK", "Heli-Charter", "HELI-CHARTER", "United Kingdom", + "HCL", "Helenair Corporation", "HELENCORP", "Saint Lucia", + "HCP", "Helicopter", "HELI CZECH", "Czech Republic", + "HCV", "Halcyonair", "CREOLE", "Cape Verde", + "HCY", "Helios Airways", "HELIOS", "Cyprus", + "HDC", "Helcopteros De Cataluna", "HELICATALUNA", "Spain", + "HDI", "Hoteles Dinamicos", "DINAMICOS", "Mexico", + "HDR", "Helikopterdrift", "HELIDRIFT", "Norway", + "HEA", "Heliavia-Transporte Aéreo", "HELIAVIA", "Portugal", + "HEB", "Heli Bernina", "HELIBERNINA", "Switzerland", + "HEC", "Heliservicio Campeche", "HELICAMPECHE", "Mexico", + "HED", "Heritage Aviation Developments", "FLAPJACK", "United Kingdom", + "HEI", "Aerohein", "AEROHEIN", "Chile", + "HEL", "Helicol", "HELICOL", "Colombia", + "HEM", "CHC Helicopter", "HEMS", "Australia", + "HEN", "Helicópteros Y Vehículos Nacionales Aéreos", "HELINAC", "Mexico", + "HER", "Hex'Air", "HEX AIRLINE", "France", + "HET", "TAF Helicopters", "HELITAF", "Spain", + "HEX", "Honiara Cargo Express", "HONIARA CARGO", "Solomon Islands", + "HEZ", "Arrow Aviation", "ARROW", "Israel", + "HFM", "Hi Fly Malta", "MOONRAKER", "Malta", + "HFR", "Heli France", "HELIFRANCE", "France", + "HFY", "Hi Fly", "SKY FLYER", "Portugal", + "HGA", "Hogan Air", "HOGAN AIR", "United States", + "HGB", "Greater Bay Airlines", "GREATER BAY", "Hong Kong", + "HGD", "Hangard Aviation", "HANGARD", "Mongolia", + "HGH", "Atlantic Air Lift", "HIGHER", "France", + "HGK", "Fika Salaama Airlines", "SALAAMA", "Uganda", + "HGR", "Hangar 8", "HANG", "United Kingdom", + "HGT", "GMJ Air Shuttle", "HIGHTECH", "United States", + "HHE", "Heli-Holland", "HELI HOLLAND", "Netherlands", + "HHH", "Helicsa", "HELICSA", "Spain", + "HHI", "Hamburg International", "HAMBURG JET", "Germany", + "HHN", "Hahn Air", "ROOSTER", "Germany", + "HHO", "Houston Helicopters", "HOUSTON HELI", "United States", + "HHP", "Helenia Helicopter Service", "HELENIA", "Denmark", + "HHS", "Hi-Jet Helicopter Services", "HIJET", "Suriname", + "HIA", "Canadian Eagle Aviation", "HAIDA", "Canada", + "HIB", "Helibravo Aviacao", "HELIBRAVO", "Portugal", + "HID", "Aviación Ejecutiva De Hildago", "EJECUTIVA HIDALGO", "Mexico", + "HIF", "Heli-Iberica Fotogrametria", "HIFSA", "Spain", + "HIG", "Heli-Inter Guyane", "INTER GUYANNE", "France", + "HIM", "Himalaya Airlines", "HIMALAYA", "Nepal", + "HIN", "Holding International Group", "HOLDING GROUP", "Mexico", + "HIP", "Starship", "STARSA", "Mexico", + "HIS", "Hispaniola Airways", "HISPANIOLA", "Dominican Republic", + "HIT", "Helitalia", "HELITALIA", "Italy", + "HJA", "Air Haiti", "AIRHAITI", "Haiti", + "HJE", "Servicios Ejecutivos Gosa", "GOSA", "Mexico", + "HJL", "Hamlin Jet", "BIZJET", "United Kingdom", + "HJT", "Al Rais Cargo", "AL-RAIS CARGO", "United Arab Emirates", + "HKA", "Superior Aviation", "SPEND AIR", "United States", + "HKB", "Hawker Beechcraft", "CLASSIC", "United States", + "HKC", "Hong Kong Air Cargo", "MASCOT", "Hong Kong", + "HKE", "Hong Kong Express Airways", "HONGKONG SHUTTLE", "Hong Kong", + "HKG", "Government Flying Service", "HONGKONG GOVERNMENT", "Hong Kong SAR of China", + "HKH", "Air-Invest", "HAWKHUNGARY", "Hungary", + "HKI", "Hawkaire", "HAWKEYE", "United States", + "HKL", "Hak Air", "HAK AIRLINE", "Nigeria", + "HKN", "Jim Hankins Air Service", "HANKINS", "United States", + "HKR", "Hawk Air", "AIR HAW", "Argentina", + "HKS", "CHC Helikopter Service", "HELIBUS", "Norway", + "HLA", "HC Airlines", "HEAVYLIFT", "United Kingdom", + "HLB", "High-Line Airways", "HIGH-LINE", "Canada", + "HLC", "Helicap", "HELICAP", "France", + "HLD", "Grampian Flight Centre", "GRANITE", "United Kingdom", + "HLE", "UK HEMS", "HELIMED", "United Kingdom", + "HLF", "Hapagfly", "HAPAG LLOYD", "Germany", + "HLG", "Helog", "HELOG", "Switzerland", + "HLH", "Hala Air", "HALA AIR", "Sudan", + "HLI", "Heli Securite", "HELI SAINT-TROPEZ", "France", + "HLK", "Heli-Link", "HELI-LINK", "Switzerland", + "HLM", "Heli Medwest De Mexico", "HELIMIDWEST", "Mexico", + "HLO", "Samaritan Air Service", "HALO", "Canada", + "HLP", "Helipistas", "HELIPISTAS", "Spain", + "HLR", "Heli Air Services", "HELI BULGARIA", "Bulgaria", + "HLS", "Haiti Air Freight", "", "Haiti", + "HLT", "Helitours", "HELITOURS", "Sri Lanka", + "HLU", "Heli Union Heli Prestations", "HELI UNION", "France", + "HLW", "Heliworks", "HELIWORKS", "Chile", + "HLX", "Hapag-Lloyd Express", "YELLOW CAB", "Germany", + "HMC", "Heliamerica De Mexico", "HELIAMERICA", "Mexico", + "HMD", "Charlie Hammonds Flying Service", "HAMMOND", "United States", + "HMF", "Norrlandsflyg", "LIFEGUARD SWEDEN", "Sweden", + "HMM", "Hamra Air", "HAMRA", "United Arab Emirates", + "HMP", "Papair Terminal", "PAPAIR TERMINAL", "Haiti", + "HMR", "North American Charters", "HAMMER", "Canada", + "HMS", "Hemus Air", "HEMUS AIR", "Bulgaria", + "HMV", "Homac Aviation", "HOMAC", "Luxembourg", + "HMX", "Hawk De Mexico", "HAWK MEXICO", "Mexico", + "HMY", "Harmony Airways", "HARMONY", "Canada", + "HNA", "Greek Navy", "HELLENIC NAVY", "Greece", + "HNL", "CHC Helicopters Netherlands", "MAPLELEAF", "Netherlands", + "HNR", "Haiti National Airlines (HANA)", "HANAIR", "Haiti", + "HNT", "Helicopteros Internacionales", "HELICOP INTER", "Mexico", + "HOA", "Hola Airlines", "HOLA", "Spain", + "HOG", "Mahogany Air Charters", "HOGAN AIR", "Zambia", + "HOL", "Holiday Airlines (US Airline)", "HOLIDAY", "United States", + "HOM", "Aero Homex", "AERO HOMEX", "Mexico", + "HOP", "Hop!", "AIR HOP", "France", + "HOR", "Horizon Air-Taxi", "HORIZON", "Switzerland", + "HOZ", "Horizontes Aéreos", "HORIZONTES AEREOS", "Mexico", + "HPA", "Pearl Airways", "PEARL AIRWAYS", "Haiti", + "HPJ", "Hop-A-Jet", "HOPA-JET", "United States", + "HPL", "Heliportugal", "HELIPORTUGAL", "Portugal", + "HPO", "Almiron Aviation", "ALMIRON", "Uganda", + "HPR", "Rick Lucas Helicopters", "HELIPRO", "New Zealand", + "HPS", "Horizon Plus", "HORIZON PLUS", "Bangladesh", + "HQO", "Avinor", "", "Norway", + "HRA", "Heli-Iberica", "ERICA", "Spain", + "HRB", "Haiti International Airline", "HAITI AIRLINE", "Haiti", + "HRH", "Royal Tongan Airlines", "TONGA ROYAL", "Tonga", + "HRI", "Skyraidybos Mokymo Centras", "HELIRIM", "Lithuania", + "HRN", "Heron Luftfahrt", "HERONAIR", "Germany", + "HRS", "Pursuit Aviation", "HORSEMAN", "United States", + "HRT", "Chartright Air", "CHARTRIGHT", "Canada", + "HRZ", "Croatian Air Force", "CROATIAN AIRFORCE", "Croatia", + "HSA", "East African Safari Air", "DUMA", "Kenya", + "HSE", "Compania Helicopteros Del Sureste", "HELISURESTE", "Spain", + "HSH", "Hispánica de Aviación", "HASA", "Spain", + "HSI", "Heliswiss", "HELISWISS", "Switzerland", + "HSK", "Sky Europe Airlines", "MATRA", "Slovakia", + "HSM", "Horizon Air for Transport and Training", "ALOFUKAIR", "Libya", + "HSN", "H.S.AVIATION CO. LTD.", "H.S.AVIATION", "Thailand", + "HSO", "Campania Helicopteros De Transporte", "HELIASTURIAS", "Spain", + "HSP", "International Jet Charter", "HOSPITAL", "United States", + "HSR", "Citylink Airlines", "HOOSIER", "United States", + "HSS", "Compañía Transportes Aéreos Del Sur", "TAS HELICOPTEROS", "Spain", + "HSU", "Helisul", "HELIS", "Portugal", + "HSV", "Svenska Direktflyg", "HIGHSWEDE", "Sweden", + "HSY", "Sky Helicopteros", "HELISKY", "Spain", + "HTA", "Helitrans", "SCANBIRD", "Norway", + "HTB", "Helix-Craft Aviation", "HELIX-CRAFT", "Panama", + "HTC", "Haiti Trans Air", "HAITI TRANSAIR", "Haiti", + "HTE", "Midwest Helicopters De Mexico", "HELICOPTERSMEXICO", "Mexico", + "HTG", "Grossmann Air Service", "GROSSMANN", "Austria", + "HTI", "Haiti International Air", "HAITI INTERNATIONAL", "Haiti", + "HTL", "Jet Linx Aviation", "HEARTLAND", "United States", + "HTN", "Haiti North Airline", "", "Haiti", + "HTO", "Tango Bravo", "HELI TANGO", "France", + "HTP", "Heli Trip", "HELI TRIP", "Mexico", + "HTR", "Holstenair Lubeck", "HOLSTEN", "Germany", + "HTS", "Helitrans Air Service", "HELITRANS", "United States", + "HTT", "Air Tchad", "HOTEL TANGO", "Chad", + "HTU", "Hongtu Airlines", "HONGLAND", "China", + "HUB", "Hub Airlines", "HUB", "United States", + "HUC", "Aerolíneas de Techuacán", "LINEAS TEHUACAN", "Mexico", + "HUD", "Horizons Unlimited", "HUD", "United States", + "HUF", "Hungarian Air Force", "HUNGARIAN AIRFORCE", "Hungary", + "HUR", "Miami Air Charter", "HURRICANE CHARTER", "United States", + "HUS", "Huessler Air Service", "HUESSLER", "United States", + "HUT", "Aerotransportes Huitzilin", "AEROHUITZILIN", "Mexico", + "HUV", "Hunair Hungarian Airlines", "SILVER EAGLE", "Hungary", + "HUY", "Aero Transportes Del Humaya", "AERO HUMAYA", "Mexico", + "HVA", "Newair", "HAVEN-AIR", "United States", + "HVK", "Turkish Air Force", "TURKISH AIRFORCE", "Turkey", + "HVL", "Heavylift International", "HEAVYLIFT INTERNATIONAL", "United Arab Emirates", + "HVN", "Vietnam Airlines", "VIET NAM", "Vietnam", + "HVY", "Heavylift Cargo Airlines", "HEAVY CARGO", "Australia", + "HWD", "HPM Investments", "FLITEWISE", "United Kingdom", + "HWY", "Highland Airways", "HIWAY", "United Kingdom", + "HXA", "China Express Airlines", "CHINA EXPRESS", "China", + "HYA", "Hyack Air", "HYACK", "Canada", + "HYC", "Hydro Air Flight Operations", "HYDRO CARGO", "South Africa", + "HYD", "Hydro-Québec", "HYDRO", "Canada", + "HYH", "Heli Hungary", "HELIHUNGARY", "Hungary", + "HYP", "Hyperion Aviation", "HYPERION", "Malta", + "HYR", "Airlink Airways", "HIGHFLYER", "Ireland", + "HZT", "Air Horizon", "HORIZON TOGO", "Togo", + "HZX", "Citic General Aviation", "ZHONGXIN", "China", + "IAA", "Indonesian Airlines", "INDO LINES", "Indonesia", + "IAC", "Interaviation Charter", "INTERCHARTER", "Romania", + "IAD", "Fly Wex", "FLYWEX", "Italy", + "IAF", "Israeli Air Force", "", "Israel", + "IAG", "EPAG", "EPAG", "France", + "IAI", "Israel Aircraft Industries", "ISRAEL AIRCRAFT", "Israel", + "IAJ", "Islandair Jersey", "JARLAND", "United Kingdom", + "IAK", "International Air Cargo Corporation", "AIR CARGO EGYPT", "Egypt", + "IAR", "Iliamna Air Taxi", "ILIAMNA AIR", "United States", + "IAW", "Iraqi Airways", "IRAQI", "Iraq", + "IAX", "International Air Services", "INTERAIR SERVICES", "Liberia", + "IAY", "Deadalos Flugtbetriebs", "IASON", "Austria", + "IBB", "Binter Canarias", "BINTER", "Spain", + "IBC", "Ibicenca Air", "IBICENCA", "Spain", + "IBE", "Iberia Airlines", "IBERIA", "Spain", + "IBG", "Springfield Air", "ICE BRIDGE", "United States", + "IBK", "Norwegian Air International", "NORTRANS", "Ireland", + "IBL", "IBL Aviation", "CATOVAIR", "Mauritius", + "IBR", "Ibertour Servicios Aéreos", "IBERTOUR", "Spain", + "IBS", "Iberia Express", "IBEREXPRESS", "Spain", + "IBT", "Ibertrans Aérea", "IBERTRANS", "Spain", + "IBU", "Indigo Airlines", "INDIGO BLUE", "United States", + "IBX", "Ibex Airlines", "IBEX", "Japan", + "IBY", "International Business Aircraft", "CENTRAL STAGE", "United States", + "IBZ", "International Business Air", "INTERBIZ", "Sweden", + "ICA", "Icaro", "ICARFLY", "Italy", + "ICB", "Icebird Airline", "ICEBIRD", "Iceland", + "ICC", "Institut Cartogràfic de Catalunya", "CARTO", "Spain", + "ICE", "Icelandair", "ICEAIR", "Iceland", + "ICG", "Icelandic Coast Guard", "ICELAND COAST", "Iceland", + "ICJ", "Icejet", "ICEJET", "Iceland", + "ICL", "Challenge Airlines IL", "CAL", "Israel", + "ICM", "Air Inter Cameroun", "INTER-CAMEROUN", "Cameroon", + "ICN", "Inter-Canadian", "INTER-CANADIAN", "Canada", + "ICP", "Intercopters", "CHOPER", "Spain", + "ICR", "Eagle Aero", "ICARUS FLIGHTS", "United States", + "ICS", "International Charter Services", "INTERSERVI", "Mexico", + "ICT", "Intercontinental de Aviación", "CONTAVIA", "Colombia", + "ICV", "Cargolux Italia", "CARGO MED", "Italy", + "IDA", "Indonesia Air Transport", "INTRA", "Indonesia", + "IDG", "IDG Technology Air", "INDIGO", "Czech Republic", + "IDL", "Ildefonso Redriguez", "ILDEFONSO", "Mexico", + "IDP", "Independent Air Freighters", "INDEPENDENT", "Australia", + "IDR", "Indicator Company", "INDICATOR", "Hungary", + "IEP", "Elipiu'", "ELIPIU", "Italy", + "IFA", "FAI Air Service", "RED ANGEL", "Germany", + "IFC", "Indian Air Force", "INDIAN AIRFORCE", "India", + "IFF", "Interfreight Forwarding", "INTERFREIGHT", "Sudan", + "IFI", "Air Lift", "HELLAS LIFT", "Greece", + "IFL", "IFL Group", "EIFEL", "United States", + "IFM", "Ifly", "ICOPTER", "Greece", + "IFS", "Italy First", "RIVIERA", "Italy", + "IFT", "Interflight", "INTERFLIGHT", "United Kingdom", + "IFX", "International Flight Training Academy", "IFTA", "United States", + "IGA", "Skytaxi", "IGUANA", "Poland", + "IGN", "Interguide Air", "DIVINE AIR", "Nigeria", + "IGO", "IndiGo", "IFLY", "India", + "IGS", "Isle Grande Flying School", "ISLA GRANDE", "United States", + "IHE", "Interjet Helicopters", "INTERCOPTER", "Greece", + "IHS", "Thryluthjonustan", "", "Iceland", + "IIC", "Inter Island Air Charter", "", "Bahamas", + "IIG", "International Company for Transport Trade and Public Works", "ALDAWLYH AIR", "Libya", + "IIL", "India International Airways", "INDIA INTER", "India", + "IIN", "Inter Island Airways", "", "Cape Verde", + "IJA", "International Jet Aviation Services", "I-JET", "United States", + "IJE", "Ivoire Jet Express", "IVOIRE JET", "Ivory Coast", + "IJM", "IJM International Jet Management", "JET MANAGEMENT", "Austria", + "IJS", "Silvair", "", "United States", + "IJT", "Interflight (Learjet)", "", "United Kingdom", + "IJW", "Interjet Inc.", "JET WEST", "United States", + "IKA", "Itek Air", "ITEK-AIR", "Kyrgyzstan", + "IKK", "IKI International Airlines", "IKIAIR", "Japan", + "IKM", "Aero Survey", "EASY SHUTTLE", "Ghana", + "IKN", "IKON FTO", "IKON", "Germany", + "IKR", "Ikaros DK", "IKAROS", "Denmark", + "IKY", "Intersky Bulgary", "GENERAL SKY", "Bulgaria", + "ILC", "ILAS Air", "", "Japan", + "ILF", "Island Air Charters", "ISLAND FLIGHT", "United States", + "ILK", "Aero Airline", "ILEK", "Kazakhstan", + "ILL", "Ilyich-Avia", "ILYICHAVIA", "Ukraine", + "ILN", "Interair South Africa", "INLINE", "South Africa", + "ILP", "Ilpo Aruba Cargo", "", "Aruba", + "ILS", "Servicios Aéreos Ilsa", "SERVICIOS ILSA", "Mexico", + "ILV", "Il-Avia", "ILAVIA", "Russia", + "IMA", "Inter-Mountain Airways", "INTER-MOUNTAIN", "United States", + "IMG", "Imperial Cargo Airlines", "IMPERIAL AIRLINES", "Ghana", + "IMN", "Aerotaxis Cimarron", "TAXI CIMARRON", "Mexico", + "IMR", "Imaer", "IMAER", "Portugal", + "IMT", "Imtrec Aviation", "IMTREC", "Cambodia", + "IMX", "Zimex Aviation", "ZIMEX", "Switzerland", + "INA", "Aero Internacional", "AERO-NACIONAL", "Mexico", + "INC", "Insel Air International", "INSELAIR", "Netherlands Antilles", + "IND", "Iona National Airways", "IONA", "Ireland", + "INK", "Sincom-Avia", "SINCOM AVIA", "Ukraine", + "INL", "Intal Avia", "INTAL AVIA", "Kyrgyzstan", + "INO", "Aeroservicios Intergrados de Norte", "INTENOR", "Mexico", + "INP", "Peruvian Navy", "", "Peru", + "INS", "Inflite The Jet Centre", "", "United Kingdom", + "INT", "Intair", "INTAIRCO", "Canada", + "INU", "Flyguppdraget Backamo", "INSTRUCTOR", "Sweden", + "INV", "Inversija", "INVER", "Latvia", + "INX", "Inter Express", "INTER-EURO", "Turkey", + "IOA", "Iowa Airways", "IOWA AIR", "United States", + "IOM", "Island Aviation and Travel", "ISLE AVIA", "United Kingdom", + "IOS", "Isles of Scilly Skybus", "SCILLONIA", "United Kingdom", + "IPA", "IPEC Aviation", "IPEC", "Australia", + "IPL", "Airpull Aviation", "IPULL", "Spain", + "IPM", "IPM Europe", "SHIPEX", "United Kingdom", + "IPN", "Industri Pesawat Terbang Nusantara", "NUSANTARA", "Indonesia", + "IPR", "Independent Carrier (ICAR)", "ICAR", "Ukraine", + "IPT", "Interport Corporation", "INTERPORT", "United States", + "IQQ", "Caribbean Airways", "CARIBJET", "Barbados", + "IRA", "Iran Air", "IRANAIR", "Iran", + "IRB", "Iranair Tours", "", "Iran", + "IRC", "Iran Aseman Airlines", "ASEMAN", "Iran", + "IRD", "Arvand Airlines", "ARVAND", "Iran", + "IRE", "Pariz Air", "PARIZAIR", "Iran", + "IRF", "TA-Air Airline", "TA-AIR", "Iran", + "IRG", "Iranian Naft Airlines", "NAFT", "Iran", + "IRH", "Atlas Aviation Group", "ATLAS AVIA", "Iran", + "IRI", "Navid", "NAVID", "Iran", + "IRJ", "Bonyad Airlines", "BONYAD AIR", "Iran", + "IRK", "Kish Air", "KISHAIR", "Iran", + "IRL", "Irish Air Corps", "IRISH", "Ireland", + "IRM", "Mahan Air", "MAHAN AIR", "Iran", + "IRO", "CSA Air", "IRON AIR", "United States", + "IRP", "Payam Air", "PAYAMAIR", "Iran", + "IRR", "Tara Air Line", "TARAIR", "Iran", + "IRU", "Chabahar Airlines", "CHABAHAR", "Iran", + "IRV", "Safat Airlines", "SAFAT AIR", "Iran", + "IRW", "Aram Airline", "ARAM", "Iran", + "IRX", "Aria Tour", "ARIA", "Iran", + "IRY", "Eram Air", "ERAM AIR", "Iran", + "IRZ", "Saha Airlines Services", "SAHA", "Iran", + "ISA", "Island Airlines", "", "United States", + "ISD", "ISD Avia", "ISDAVIA", "Ukraine", + "ISF", "International Stabilisation Assistance Force", "", "United Kingdom", + "ISG", "Club Air", "CLUBAIR", "Italy", + "ISI", "Island Air", "ISLANDMEX", "Mexico", + "ISL", "Landsflug", "ISLANDIA", "Iceland", + "ISM", "Auo Airclub AIST-M", "STORK", "Russia", + "ISN", "Interisland Airlines", "TRI-BIRD", "Philippines", + "ISR", "Israir", "ISRAIR", "Israel", + "ISS", "Meridiana", "MERIDIANA", "Italy", + "IST", "Istanbul Airlines", "ISTANBUL", "Turkey", + "ISV", "Islena De Inversiones", "", "Honduras", + "ISW", "Islas Airways", "PINTADERA", "Spain", + "ITA", "Inter-Air", "CAFEX", "United States", + "ITC", "International Air Carrier Association", "", "Belgium", + "ITE", "Aerotaxi S.R.O.", "AEROTAXI", "Czech Republic", + "ITF", "Avita-Servicos Aéreos", "AIR AVITA", "Angola", + "ITH", "International Trans-Air", "INTRANS NIGERIA", "Nigeria", + "ITI", "AirSwift", "AIRSWIFT", "Philippines", + "ITK", "Interlink Airlines", "INTERLINK", "South Africa", + "ITN", "Industrias Titan", "TITANLUX", "Spain", + "ITO", "Aero Citro", "AERO CITRO", "Mexico", + "ITR", "Frontier Commuter", "OUT BACK", "United States", + "ITS", "Inter-State Aviation", "INTER-STATE", "United States", + "ITU", "Intervuelos", "INTERLOS", "Mexico", + "ITW", "Inter Air", "INTER WINGS", "Bulgaria", + "ITX", "Imair Airlines", "IMPROTEX", "Azerbaijan", + "ITY", "ITA Airways", "ITARROW", "Italy", + "IUS", "Icarus", "ICARUS", "Italy", + "IVA", "Innotech Aviation", "INNOTECH", "Canada", + "IVE", "Air Executive", "COMPANY EXEC", "Spain", + "IVJ", "Flight International", "INVADER JACK", "United States", + "IVR", "Burundaiavia", "RERUN", "Kazakhstan", + "IVS", "Ivoire Aero Services", "IVOIRE AERO", "Ivory Coast", + "IVT", "Interaviatrans", "INTERAVIA", "Ukraine", + "IVW", "Ivoire Airways", "IVOIRAIRWAYS", "Ivory Coast", + "IWD", "Iberworld", "IBERWORLD", "Spain", + "IWL", "Island Wings", "", "Bahamas", + "IWS", "Aviainvest", "", "Russia", + "IWY", "InterCaribbean Airways", "ISLANDWAYS", "Turks and Caicos Islands", + "IXR", "Ixair", "X-BIRD", "France", + "IXX", "Dolphin Express Airlines", "ISLAND EXPRESS", "United States", + "IYE", "Yemenia", "YEMENI", "Yemen", + "IZA", "Izhavia", "IZHAVIA", "Russia", + "IZG", "Zagros Airlines", "ZAGROS", "Iran", + "IZM", "Izair", "IZMIR", "Turkey", + "JAA", "Jet Asia Airways", "JET ASIA", "Thailand", + "JAB", "Air Bagan", "AIR BAGAN", "Myanmar", + "JAC", "Japan Air Commuter", "COMMUTER", "Japan", + "JAD", "Aerojal", "AEROJAL", "Mexico", + "JAE", "Jade Cargo International", "JADE CARGO", "China", + "JAF", "TUI fly Belgium", "BEAUTY", "Belgium", + "JAG", "Jetalliance", "JETALLIANCE", "Austria", + "JAI", "Jet Airways", "JET AIRWAYS", "India", + "JAK", "Jana-Arka", "YANZAR", "Kazakhstan", + "JAL", "Japan Airlines", "JAPANAIR", "Japan", + "JAM", "Sunline Express", "SUNTRACK", "Kenya", + "JAR", "Airlink", "AIRLINK", "Austria", + "JAS", "Jet Aviation Flight Services", "JET SETTER", "United States", + "JAT", "JetSMART", "ROCKSMART", "Chile", + "JAV", "Jordan Aviation", "JORDAN AVIATION", "Jordan", + "JAW", "Jamahiriya Airways", "JAW", "Libya", + "JAX", "Janair", "JANAIR", "United States", + "JAZ", "JALways", "JALWAYS", "Japan", + "JBA", "Helijet", "HELIJET", "Canada", + "JBR", "Job Air", "JOBAIR", "Czech Republic", + "JBU", "JetBlue Airways", "JETBLUE", "United States", + "JCB", "J C Bamford (Excavators)", "JAYSEEBEE", "United Kingdom", + "JCC", "Jetcraft Aviation", "JETCRAFT", "Australia", + "JCF", "Jet Center Flight Training", "JET CENTER", "Spain", + "JCH", "Trading Air Cargo", "TRADING CARGO", "Mauritania", + "JCI", "Jordan International Air Cargo", "", "Jordan", + "JCK", "Jackson Air Services", "JACKSON", "Canada", + "JCM", "Secure Air Charter", "SECUREAIR", "United States", + "JCR", "Rotterdam Jet Center", "ROTTERDAM JETCENTER", "Netherlands", + "JCS", "Jetclub", "JETCLUB", "Switzerland", + "JCT", "Jet Charter", "JET CHARTER", "United States", + "JDA", "JDAviation", "JAY DEE", "United Kingdom", + "JDC", "Deere and Company", "JOHN DEERE", "United States", + "JDG", "Joanas Avialinijos", "LADYBLUE", "Lithuania", + "JDI", "Jet Story", "JEDI", "Poland", + "JDP", "JDP Lux", "RED PELICAN", "Luxembourg", + "JED", "Jet East International", "JET EAST", "United States", + "JEE", "Ambjek Air Services", "AMBJEK AIR", "Nigeria", + "JEF", "Jetflite", "JETFLITE", "Finland", + "JEI", "Jet Executive International Charter", "JET EXECUTIVE", "Germany", + "JEJ", "Jets Ejecutivos", "MEXJETS", "Mexico", + "JEK", "Jet Link", "JET OPS", "Israel", + "JEL", "Tal Air Charters", "JETEL", "Canada", + "JEM", "Emerald Airways", "GEMSTONE", "United Kingdom", + "JEP", "Jets Personales", "JET PERSONALES", "Spain", + "JES", "JS Aviation", "JAY-ESS AVIATION", "Mexico", + "JET", "Wind Jet", "GHIBLI", "Italy", + "JEV", "Lagun Air", "", "Spain", + "JEX", "JAL Express", "JANEX", "Japan", + "JFA", "Jetfly Aviation", "MOSQUITO", "Luxembourg", + "JFC", "LTV Jet Fleet Corporation", "JET-FLEET", "United States", + "JFK", "Keenair Charter -", "KEENAIR", "United Kingdom", + "JFL", "Jetfly Airlines", "LINEFLYER", "Austria", + "JFS", "Juanda Flying School", "JAEMCO", "Indonesia", + "JFU", "Jet4You", "ARGAN", "Morocco", + "JFY", "Foster Yeoman", "YEOMAN", "United Kingdom", + "JGD", "Jet G&D Aviation", "JET GEE-AND-DEE", "Israel", + "JGJ", "Jinggong Jet", "GLOBAL JINGGONG", "China", + "JHM", "JHM Cargo Expreso", "", "Costa Rica", + "JHN", "Johnson Air", "AIR JOHNSON", "United States", + "JIA", "PSA Airlines", "BLUE STREAK", "United States", + "JIB", "Jibair", "JIBAIRLINE", "Djibouti", + "JIC", "Jetgo International", "JIC-JET", "Thailand", + "JIM", "Sark International Airways", "SARK", "United Kingdom", + "JJA", "Jeju Air", "JEJU AIR", "Republic of Korea", + "JJM", "Regional Geodata Air", "GEODATA", "Spain", + "JJP", "Jetstar Japan", "ORANGE LINER", "Japan", + "JKA", "LeTourneau University", "JACKET", "United States", + "JKH", "JETKONTOR AG", "JETKONTOR", "Germany", + "JKR", "Justice Air Charter", "JOKER", "United States", + "JKT", "Jetstar Hong Kong Airways", "KAITAK", "China", + "JKY", "Helicopter & Aviation Services", "JOCKEY", "United Kingdom", + "JLA", "MIA Airlines", "SALLINE", "Romania", + "JLH", "Centro de Servicio Aeronautico", "CESA", "Mexico", + "JLN", "Eurojet Limited", "JET LINE", "Malta", + "JLX", "Jetlink Express", "KEN JET", "Kenya", + "JMB", "Jambo Africa Airlines", "JAMBOAFRICA", "Democratic Republic of Congo", + "JMC", "JMC Airlines", "JAYEMMSEE", "United Kingdom", + "JMJ", "Johnston Airways", "JOHNSTON", "United States", + "JMM", "Joint Military Commission", "JOICOMAR", "Sudan", + "JMP", "Businesswings", "JUMP RUN", "Germany", + "JMR", "Alexandair", "ALEXANDAIR", "Canada", + "JMT", "Jomartaxi Aereo", "JOMARTAXI", "Mexico", + "JMX", "Air Jamaica Express", "JAMAICA EXPRESS", "Jamaica", + "JNA", "Jin Air", "JIN AIR", "South Korea", + "JNH", "M & N Aviation", "JONAH", "United States", + "JNJ", "Journey Jet", "JOURNEY JET", "Thailand", + "JNL", "JetNetherlands", "JETNETHERLANDS", "Netherlands", + "JNR", "Jet Norte", "JET NORTE", "Mexico", + "JNV", "Jetnova de Aviación Ejecutiva", "JETNOVA", "Spain", + "JNY", "Jenney Beechcraft", "JENAIR", "United States", + "JOA", "Air Swift Aviation", "", "Australia", + "JOB", "Aerojobeni", "JOBENI", "Mexico", + "JOL", "Atyrau Air Ways", "EDIL", "Kazakhstan", + "JON", "Johnsons Air", "JOHNSONSAIR", "Ghana", + "JOS", "DHL de Guatemala", "", "Guatemala", + "JPA", "OSACOM", "J-PAT", "United States", + "JPN", "Jeppesen UK", "JETPLAN", "United Kingdom", + "JPO", "Jetpro", "JETPRO", "Mexico", + "JPQ", "Jett Paqueteria", "JETT PAQUETERIA", "Mexico", + "JRF", "First Air Transport", "", "Japan", + "JRI", "Jetrider International", "JETRIDER", "United Kingdom", + "JRN", "Jet Rent", "JET RENT", "Mexico", + "JSA", "Jetstar Asia Airways", "JETSTAR ASIA", "Singapore", + "JSE", "Jets Y Servicios Ejecutivos", "SERVIJETS", "Mexico", + "JSH", "Jet-stream", "STREAM AIR", "Hungary", + "JSI", "Jet Air Group", "SISTEMA", "Russia", + "JSJ", "JS Air", "JS CHARTER", "Pakistan", + "JSM", "Jet Stream", "JET STREAM", "Moldova", + "JSP", "Palmer Aviation", "PALMER", "United Kingdom", + "JSS", "Jet Stream International", "", "Pakistan", + "JST", "Jetstar", "JETSTAR", "Australia", + "JSV", "Japan Aircraft Service", "", "Japan", + "JSW", "Jigsaw Project", "JIGSAW", "United Kingdom", + "JSX", "JSX (airline)", "BIGSTRIPE", "United States", + "JSY", "Jung Sky", "", "Croatia", + "JTA", "Japan Transocean Air", "JAI OCEAN", "Japan", + "JTC", "Jet Trans Aviation", "JETRANS", "Ghana", + "JTE", "National Jet Express", "JETEX", "Australia", + "JTF", "Jet Time", "JETFIN", "Finland", + "JTG", "Jettime", "JETTIME", "Denmark", + "JTI", "Cirrus Middle East", "", "Lebanon", + "JTL", "Jet Linx Aviation", "JET LINX", "United States", + "JTM", "Exxavia Limited", "SKYMAN", "Ireland", + "JTN", "Jet Test Intl.", "JET TEST", "United States", + "JTR", "Executive Aviation Services", "JESTER", "United Kingdom", + "JTS", "Arrendamiento de Aviones Jets", "AVIONESJETS", "Mexico", + "JTT", "Jet-2000", "MOSCOW JET", "Russia", + "JTU", "Zhetysu", "ZHETYSU", "Kazakhstan", + "JTX", "Jet Aspen Air Lines", "JET ASPEN", "United States", + "JTY", "Jatayu Airlines", "JATAYU", "Indonesia", + "JUA", "Aero Juarez", "JUAREZ", "Mexico", + "JUB", "Jubba Airways", "JUBBA", "Somalia", + "JUC", "Juba Cargo Services & Aviation Company", "JUBA CARGO", "Sudan", + "JUR", "Ju-Air", "JUNKERS", "Switzerland", + "JUS", "USA Jet Airlines", "JET USA", "United States", + "JVK", "Jorvik", "ISLANDIC", "Iceland", + "JWD", "Jayawijaya Dirgantara", "", "Indonesia", + "JWY", "Jetways of Iowa", "JETWAYS", "United States", + "JXA", "Jetex Aviation", "", "Lebanon", + "JXT", "Jetstream Executive Travel", "VANNIN", "United Kingdom", + "JXX", "JetX Airlines", "JETBIRD", "Iceland", + "JZA", "Air Canada Jazz", "JAZZ", "Canada", + "JZR", "Jazeera Airways", "JAZEERA", "Kuwait", + "KAC", "Kuwait Airways", "KUWAITI", "Kuwait", + "KAD", "Air Kirovograd", "AIR KIROVOGRAD", "Ukraine", + "KAE", "Kartika Airlines", "KARTIKA", "Indonesia", + "KAH", "Kent Aviation", "DEKAIR", "Canada", + "KAI", "KaiserAir", "KAISER", "United States", + "KAJ", "Karthago Airlines", "KARTHAGO", "Tunisia", + "KAL", "Korean Air", "KOREANAIR", "South Korea", + "KAM", "Air Mach", "ICO-AIR", "Italy", + "KAO", "Kazan Aviation Production Association", "KAZAVAIA", "Russia", + "KAP", "Cape Air", "CAIR", "United States", + "KAR", "Ikar", "IKAR", "Russian Federation", + "KAS", "Kingston Air Services", "KINGSTON AIR", "Canada", + "KAT", "Kato Airline", "KATO-AIR", "Norway", + "KAV", "Air Kufra", "AIRKUFRA", "Libya", + "KAW", "Kaz Air West", "KAZWEST", "Kazakhstan", + "KAZ", "Comlux Kazakhstan", "KAZLUX", "Kazakhstan", + "KBA", "Kenn Borek Air", "BOREK AIR", "Canada", + "KBN", "Spiracha Aviation", "KABIN", "Thailand", + "KBV", "Kustbevakningen", "SWECOAST", "Sweden", + "KCA", "Trans-Kiev", "TRANS-KIEV", "Ukraine", + "KCE", "Irving Oil", "KACEY", "Canada", + "KCH", "KC International Airlines", "CAM AIR", "Cambodia", + "KCR", "Kolob Canyons Air Services", "KOLOB", "United States", + "KDC", "K D Air Corporation", "KAY DEE", "Canada", + "KDR", "Royal Daisy Airlines", "DARLINES", "Uganda", + "KDZ", "Flightworks", "KUDZU", "United States", + "KEE", "Keystone Air Service", "KEYSTONE", "Canada", + "KEM", "CemAir", "CEMAIR", "South Africa", + "KEN", "Kenmore Air", "KENMORE", "United States", + "KES", "Kallat El Saker Air Company", "KALLAT EL SKER", "Libya", + "KEW", "Keewatin Air", "BLIZZARD", "Canada", + "KEY", "Key Airlines", "KEY AIR", "United States", + "KFA", "Kelowna Flightcraft Air Charter", "FLIGHTCRAFT", "Canada", + "KFC", "Kremenchuk Flight College", "KREMENCHUK", "Ukraine", + "KFE", "SkyFirst LTD", "SKYFIRST", "Malta", + "KFK", "Aero Charter Krifka", "KRIFKA AIR", "Austria", + "KFS", "Kalitta Charters", "KALITTA", "United States", + "KFT", "Air Kraft Mir", "AIR KRAFT MIR", "Uzbekistan", + "KGA", "Kyrgyzstan Airlines", "KYRGYZ", "Kyrgyzstan", + "KGB", "Kyrgz General Aviation", "KEMIN", "Kyrgyzstan", + "KGC", "Peach Air", "GOLDCREST", "United Kingdom", + "KGD", "Air Concorde", "CONCORDE AIR", "Bulgaria", + "KGL", "Kogalymavia Air Company", "KOGALYM", "Russia", + "KGT", "Knights Airlines", "KNIGHT-LINER", "Nigeria", + "KGZ", "Kyrgyz Airlines", "BERMET", "Kyrgyzstan", + "KHB", "Dalavia", "DALAVIA", "Russia", + "KHE", "Kanfey Ha'emek Aviation", "KANFEY HAEMEK", "Israel", + "KHH", "Alexandria Airlines", "", "Egypt", + "KHK", "Kharkiv Airlines", "SUNRAY", "Ukraine", + "KHO", "Khors Aircompany", "AIRCOMPANY KHORS", "Ukraine", + "KHP", "Khoezestan Photros Air Lines", "PHOTROS AIR", "Iran", + "KHR", "Khazar", "KHAZAR", "Turkmenistan", + "KHV", "Cambodia Angkor Air", "ANGKOR AIR", "Cambodia", + "KHX", "Knighthawk Express", "RIZZ", "United States", + "KHY", "Khyber Afghan Airlines", "KHYBER", "Afghanistan", + "KIA", "Kiwi International Air Lines", "KIWI AIR", "United States", + "KIE", "Afit", "TWEETY", "Germany", + "KII", "Kalitta Charters II", "DRAGSTER", "United States", + "KIL", "Kuban Airlines", "AIR KUBAN", "Russia", + "KIN", "RAF Kinloss", "KINLOSS", "United Kingdom", + "KIP", "Kinnarps", "KINNARPS", "Sweden", + "KIS", "Contactair", "CONTACTAIR", "Germany", + "KIW", "Royal New Zealand Air Force", "KIWI", "New Zealand", + "KIZ", "Kanaf-Arkia Airlines", "", "Israel", + "KKA", "Kazavia", "KAKAIR", "Kazakhstan", + "KKB", "Air South", "KHAKI BLUE", "United States", + "KKK", "Atlasjet", "ATLASJET", "Turkey", + "KKS", "Salem", "KOKSHE", "Kazakhstan", + "KLB", "Air Mali International", "TRANS MALI", "Mali", + "KLC", "KLM Cityhopper", "CITY", "Netherlands", + "KLD", "Air Klaipeda", "AIR KLAIPEDA", "Lithuania", + "KLE", "Rusaero", "", "Russia", + "KLH", "KLM Helicopter", "KLM HELI", "Netherlands", + "KLM", "KLM", "KLM", "Netherlands", + "KLO", "Flight-Ops International", "KLONDIKE", "Canada", + "KLR", "Columbus Air Transport", "KAY-LER", "United States", + "KLS", "Kalstar Aviation", "KALSTAR", "Indonesia", + "KLX", "Kelix Air", "KELIX", "Nigeria", + "KLZ", "Aerokaluz", "AEROKALUZ", "Mexico", + "KMA", "Komiaviatrans State Air Enterprise", "KOMI AVIA", "Russia", + "KMB", "Delta Engineering Aviation", "KEMBLEJET", "United Kingdom", + "KMC", "Kahama Mining Corporation", "KAHAMA", "Tanzania", + "KME", "Cambodia Airways", "GIANT IBIS", "Cambodia", + "KMF", "Kam Air", "KAMGAR", "Afghanistan", + "KMG", "Kosmas Air", "KOSMAS CARGO", "Serbia", + "KMI", "K-Mile Air", "KAY-MILE AIR", "Thailand", + "KMP", "Kampuchea Airlines", "KAMPUCHEA", "Cambodia", + "KMR", "Western Pacific Airlines", "KOMSTAR", "United States", + "KMV", "Komiinteravia", "KOMIINTER", "Russia", + "KNA", "Knight Air", "KNIGHTAIR", "Canada", + "KNE", "Flynas", "NAS EXPRESS", "Saudi Arabia", + "KNG", "King Aviation", "KING", "United Kingdom", + "KNI", "KD Avia", "KALININGRAD AIR", "Russia", + "KNM", "GB Helicopters", "KINGDOM", "United Kingdom", + "KOA", "Koanda Avacion", "KOANDA", "Spain", + "KOB", "Koob-Corp - 96 KFT", "AUTOFLEX", "Hungary", + "KOE", "Northland Aviation", "KOKEE", "United States", + "KOK", "Horizon Air Service", "KOKO", "United States", + "KOM", "Kom Activity", "COMJET", "Netherlands", + "KOP", "Servicios Aéreos Copters", "COPTERS", "Chile", + "KOR", "Air Koryo", "AIR KORYO", "Democratic People's Republic of Korea", + "KOS", "Kosova Airlines", "KOSOVA", "Serbia", + "KOV", "Orlan-2000", "ORLAN", "Kazakhstan", + "KOY", "Araiavia", "ALEKS", "Kazakhstan", + "KPA", "Kunpeng Airlines", "KUNPENG", "China", + "KPH", "Kazan Helicopters", "KAMA", "Russia", + "KPM", "Sky Prim Air", "SKY PRIMAIR", "Molodva", + "KQA", "Kenya Airways", "KENYA", "Kenya", + "KRA", "Kiwi Regional Airlines", "REGIONAL", "New Zealand", + "KRB", "Karibu Airways Company", "KARIBU AIR", "Tanzania", + "KRC", "Royal New Zealand Air Force", "KIWI RESCUE", "New Zealand", + "KRE", "AeroSucre", "AEROSUCRE", "Colombia", + "KRF", "United Kingdom Royal VIP Flights", "KITTYHAWK", "United Kingdom", + "KRG", "Krimaviamontag", "AVIAMONTAG", "Ukraine", + "KRH", "United Kingdom Royal VIP Flight", "SPARROWHAWK", "United Kingdom", + "KRI", "Krylo Airlines", "Krylo", "Russia", + "KRM", "Crimea Universal Avia", "TRANS UNIVERSAL", "Ukraine", + "KRN", "Kaz Agros Avia", "ANTOL", "Kazakhstan", + "KRO", "Kroonk Air Agency", "KROONK", "Ukraine", + "KRP", "Carpatair", "CARPATAIR", "Romania", + "KRS", "Rosen Aviation", "", "Japan", + "KRV", "Khoriv-Avia", "KHORIV-AVIA", "Ukraine", + "KSA", "K S Avia", "SKY CAMEL", "Latvia", + "KSI", "Air Kissari", "KISSARI", "Angola", + "KSM", "Kosmos", "KOSMOS", "Russia", + "KSP", "Servicios Aéreos Expecializados En Transportes Petroleros", "SAEP", "Colombia", + "KSS", "Raytheon Travel Air", "KANSAS", "United States", + "KST", "PTL Luftfahrtunternehmen", "KING STAR", "Germany", + "KSU", "Kansas State University", "K-STATE", "United States", + "KTA", "Kirov Air Enterprise", "VYATKA-AVIA", "Russia", + "KTB", "Transaviabaltika", "TRANSBALTIKA", "Lithuania", + "KTC", "Kyrgyz Trans Avia", "DINARA", "Kyrgyzstan", + "KTK", "Katekavia", "KATEKAVIA", "Russia", + "KTL", "P & P Floss Pick Manufacturers", "KNOTTSBERRY", "South Africa", + "KTN", "Aeronavigaciya", "AERONAVIGACIYA", "Ukraine", + "KTR", "HT Helikoptertransport", "COPTER TRANS", "Sweden", + "KTS", "Transair-Gyraintiee", "KOTAIR", "Russia", + "KTV", "Kata Transportation", "KATAVIA", "Sudan", + "KUS", "National Airlines", "KUSWAG", "South Africa", + "KVK", "Olimp Air", "PONTA", "Kazakhstan", + "KVR", "Alliance Avia", "KAVAIR", "Kazakhstan", + "KVS", "Kevis", "KEVIS", "Kazakhstan", + "KVZ", "Z-Aero Airlines", "", "Ukraine", + "KWA", "Vozdushnaya Academy", "VOZAIR", "Kazakhstan", + "KWN", "Kwena Air", "KWENA", "South Africa", + "KWX", "Florida Aerocharter", "KAY DUB", "United States", + "KYC", "Av Atlantic", "DOLPHIN", "United States", + "KYD", "Sky Messaging", "SKYAD", "South Africa", + "KYE", "Sky Lease Cargo", "SKY CUBE", "United States", + "KYM", "Krym", "CRIMEA AIR", "Ukraine", + "KYR", "Sky Aeronautical Services", "SKY AERONAUTICAL", "Mexico", + "KZA", "Kurzemes Avio", "", "Russia", + "KZE", "Euro-Asia Air International", "KAZEUR", "Kazakhstan", + "KZH", "Zhez Air", "", "Kazakhstan", + "KZM", "Phoebus Apolloa Zambia", "CARZAM", "Zambia", + "KZR", "Air Astana", "ASTANALINE", "Kazakhstan", + "KZS", "Kazaviaspas", "SPAKAZ", "Kazakhstan", + "KZU", "Kuzu Airlines Cargo", "KUZU CARGO", "Turkey", + "KZW", "Khalifa Airways", "KHALIFA AIR", "Algeria", + "LAA", "Libyan Arab Airlines", "LIBAIR", "Libya", + "LAB", "L.A.B. Flying Service", "LAB", "United States", + "LAC", "Lockeed Aircraft Corporation", "LOCKHEED", "United States", + "LAD", "Lebanon Airport Development Corporation", "LADCO-AIR", "United States", + "LAF", "Latvian Air Force", "LATVIAN AIRFORCE", "Latvia", + "LAG", "Aviation Legacy", "AVILEG", "Gambia", + "LAH", "L A Helicopter", "STAR SHIP", "United States", + "LAL", "Labrador Airways", "LAB AIR", "Canada", + "LAM", "Linhas Aéreas de Moçambique", "MOZAMBIQUE", "Mozambique", + "LAN", "LATAM Chile", "LAN CHILE", "Chile", + "LAO", "Lao Airlines", "LAO", "Lao People's Democratic Republic", + "LAP", "LATAM Paraguay", "PARAGUAYA", "Paraguay", + "LAQ", "Lebanese Air Transport", "LAT", "Lebanon", + "LAR", "Lawrence Aviation", "LAWRENCE", "United States", + "LAT", "Lebanese Air Transport (charter)", "LEBANESE AIR", "Lebanon", + "LAU", "Líneas Aéreas Suramericanas", "SURAMERICANO", "Colombia", + "LAV", "AlbaStar", "ALBASTAR", "Spain", + "LAW", "Link Airways of Australia", "", "Australia", + "LAX", "Laminar Air", "", "Spain", + "LAY", "Layang-Layang Aerospace", "LAYANG", "Malaysia", + "LBC", "Albanian Airlines", "ALBANIAN", "Albania", + "LBH", "Laker Airways (Bahamas)", "LAKER BAHAMAS", "United States", + "LBI", "Albisa", "ALBISA", "Mexico", + "LBQ", "Quest Diagnostics", "LABQUEST", "United States", + "LBR", "Elbe Air Transport", "MOTION", "Germany", + "LBT", "Nouvel Air Tunisie", "NOUVELAIR", "Tunisia", + "LBW", "Albatros Airways", "ALBANWAYS", "Albania", + "LBY", "Belle Air", "ALBAN-BELLE", "Albania", + "LBZ", "Angkasa Super Service", "", "Indonesia", + "LCA", "Leconte Airlines", "LECONTE", "United States", + "LCB", "LC Busre", "BUSRE", "Peru", + "LCC", "The Lancair Company", "LANCAIR", "United States", + "LCG", "Lignes Aeriennes Congolaises", "CONGOLAISE", "Democratic Republic of the Congo", + "LCH", "Lynch Flying Service", "LYNCH AIR", "United States", + "LCM", "Líneas Aéreas Comerciales", "LINEAS COMERCIALES", "Mexico", + "LCN", "Líneas Aéreas Canedo LAC", "CANEDO", "Bolivia", + "LCO", "LATAM Cargo Chile", "LAN CARGO", "Chile", + "LCR", "Libyan Arab Air Cargo", "LIBAC", "Libya", + "LCS", "RAF Leuchars", "LEUCHARS", "United Kingdom", + "LCT", "Compañía De Actividades Y Servicios De Aviación", "STELLAIR", "Spain", + "LCY", "London City Airport Jet Centre", "LONDON CITY", "United Kingdom", + "LDA", "Lauda Air", "LAUDA AIR", "Austria", + "LDE", "LADE - Líneas Aéreas Del Estado", "LADE", "Argentina", + "LDG", "Aerolíneas Aéreas Ejecutivas De Durango", "DURANGO", "Mexico", + "LDI", "Lauda Air Italy", "LAUDA ITALY", "Italy", + "LDL", "Aerologic", "", "Russia", + "LDM", "LaudaMotion", "LAUDA MOTION", "Austria", + "LDN", "Al-Donas Airlines", "ALDONAS AIR", "Nigeria", + "LDR", "Aero Lider", "AEROLIDER", "Mexico", + "LEA", "Unijet", "LEADAIR", "France", + "LEB", "Lebap", "LEBAP", "Turkmenistan", + "LEC", "Linex", "LECA", "Central African Republic", + "LED", "Blom Geomatics", "SWEEPER", "Norway", + "LEE", "RAF Leeming", "JAVELIN", "United Kingdom", + "LEG", "The Army Aviation Heritage Foundation", "LEGACY", "United States", + "LEJ", "FSH Luftfahrtunternehmen", "LEIPZIG FAIR", "Germany", + "LEL", "Leonsa De Aviación", "LEONAVIA", "Spain", + "LEM", "Aleem", "", "Egypt", + "LEN", "Lentini Aviation", "LENTINI", "United States", + "LEP", "Laughlin Express", "LAUGHLIN EXPRESS", "United States", + "LER", "Línea Aérea de Servicio Ejecutivo Regional", "LASER", "Venezuela", + "LET", "Aerolíneas Ejecutivas", "MEXEJECUTIV", "Mexico", + "LEU", "Lions-Air", "LIONSAIR", "Switzerland", + "LEX", "L'Express Airlines", "LEX", "United States", + "LFA", "Air Alfa", "AIR ALFA", "Turkey", + "LFE", "Luxflight Executive", "LUX EXPRESS", "Luxembourg", + "LFI", "National Airways Corporation", "AEROMED", "South Africa", + "LFL", "Executive Air", "LIFE FLIGHT", "Zimbabwe", + "LFO", "Deutsches Zentrum fur Luft-und Raumfahrt EV", "LUFO", "Germany", + "LFP", "Alfa Aerospace", "ALFA-SPACE", "Australia", + "LFR", "Atlantic Airfreight Aviation", "LANFREIGHT", "São Tomé and Príncipe", + "LGA", "Logistic Air", "LOGAIR", "Malaysia", + "LGC", "Legacy Air", "LEGACY AIR", "Thailand", + "LGD", "Legend Airlines", "LEGENDARY", "United States", + "LGL", "Luxair", "LUXAIR", "Luxembourg", + "LGN", "Aerolaguna", "AEROLAGUNA", "Mexico", + "LGT", "Longtail Aviation", "LONGTAIL", "Bermuda", + "LGU", "Servicios Aéreos Ejecutivos De La Laguna", "LAGUNA", "Mexico", + "LGW", "German Airways", "WALTER", "Germany", + "LHB", "Liebherr Geschaeftreiseflugzeug", "FAMILY", "Germany", + "LHC", "London Helicopter Centres", "MUSTANG", "United Kingdom", + "LHR", "Al Ahram Aviation", "AL AHRAM", "Egypt", + "LHS", "Luhansk", "ENTERPRISE LUHANSK", "Ukraine", + "LHT", "Lufthansa Technik", "LUFTHANSA TECHNIK", "Germany", + "LHX", "City Airlines", "CITYAIR", "Germany", + "LIA", "Leeward Islands Air Transport", "LIAT", "Antigua and Barbuda", + "LIB", "Polizeihubschrauberstaffel Hamburg", "LIBELLE", "Germany", + "LID", "Alidaunia", "ALIDA", "Italy", + "LIE", "Al-Dawood Air", "AL-DAWOOD AIR", "Nigeria", + "LIF", "Rocky Mountain Holdings", "LIFECARE", "United States", + "LIJ", "Líneas Aéreas San Jose", "LINEAS JOSE", "Mexico", + "LIN", "Aerolimousine", "AEROLIMOUSINE", "Russia", + "LIQ", "Lid Air", "", "Sweden", + "LIR", "Minsk Aircraft Overhaul Plant", "LISLINE", "Belarus", + "LIS", "Eastern Express", "LARISA", "Kazakhstan", + "LJY", "L J Aviation", "ELJAY", "United States", + "LKA", "Lao Capricorn Air", "NAKLAO", "Lao People's Democratic Republic", + "LKD", "Lignes Aeriennes Du Tchad", "LATCHAD", "Chad", + "LKE", "Lucky Air", "LUCKY AIR", "China", + "LKL", "Lakeland Aviation", "LAKELAND", "United States", + "LKN", "Lankair", "Lankair", "Sri Lanka", + "LKR", "Laker Airways", "LAKER", "United States", + "LKS", "Airlink Solutions", "AIRLIN", "Spain", + "LKW", "Top Sky International", "TOPINTER", "Indonesia", + "LKY", "Air Solutions", "LUCKY", "United States", + "LLA", "Servico Leo Lopex", "LEO LOPOZ", "Mexico", + "LLB", "Lloyd Aéreo Boliviano", "LLOYDAEREO", "Bolivia", + "LLD", "Lloyd Aviation", "", "Venezuela", + "LLL", "Lao Skyway", "LAVIE", "Lao People's Democratic Republic", + "LLM", "Yamal Airlines", "YAMAL", "Russia", + "LLO", "Operation Enduring Freedom", "APOLLO", "Canada", + "LLR", "Air India Regional", "ALLIED", "India", + "LLS", "Servicios Aéreos Estrella", "SERVIESTRELLA", "Mexico", + "LMA", "Aerolima", "AEROLIMA", "Mexico", + "LMC", "Línea Aérea Mexicana de Carga", "LINEAS DECARGA", "Mexico", + "LME", "Lignes Mauritaniennes Air Express", "LIMAIR EXPRESS", "Mauritania", + "LMG", "South African Air Force", "SOUTH AFRICAN", "South Africa", + "LMJ", "Masterjet", "MASTERJET", "Portugal", + "LMK", "Grantex Aviation", "LANDMARK", "United Kingdom", + "LMN", "Líneas Aéreas Monarca", "LINEAS MONARCA", "Mexico", + "LMO", "Sky One Holdings as Privaira", "SKY HOLDINGS", "United States", + "LMP", "Air Plus Argentina", "AIR FLIGHT", "Argentina", + "LMR", "Lamra", "LAMAIR", "Sudan", + "LMS", "Lomas Helicopters", "LOMAS", "United Kingdom", + "LMT", "Almaty Aviation", "ALMATY", "Kazakhstan", + "LMU", "AlMasria Universal Airlines", "ALMASRIA", "Egypt", + "LMX", "Aerolíneas Mexicanas J S", "LINEAS MEXICANAS", "Mexico", + "LMY", "Air Almaty", "AGLEB", "Kazakhstan", + "LMZ", "Air Almaty ZK", "ALUNK", "Kazakhstan", + "LNA", "Lnair Air Services", "ELNAIR", "Spain", + "LNC", "LAN Dominicana", "LANCANA", "Dominican Republic", + "LNE", "Aerolane", "AEROLANE", "Ecuador", + "LNG", "Lockheed Martin Aeronautics Company", "LIGHTNING", "United States", + "LNI", "Lion Air", "LION INTER", "Indonesia", + "LNK", "Airlink", "LINK", "South Africa", + "LNP", "Línea Aérea SAPSA", "SAPSA", "Chile", + "LNT", "Aerolíneas Internacionales", "LINEAINT", "Mexico", + "LNX", "London Executive Aviation", "LONEX", "United Kingdom", + "LOD", "Malmoe Air Taxi", "LOGIC", "Sweden", + "LOF", "Trans States Airlines", "WATERSKI", "United States", + "LOG", "Loganair", "LOGAN", "United Kingdom", + "LOK", "Alok Air", "ALOK AIR", "Sudan", + "LOP", "RAF Linton-on-Ouse", "LINTON ON OUSE", "United Kingdom", + "LOR", "Leo-Air", "LEO CHARTER", "South Africa", + "LOS", "RAF Lossiemouth", "LOSSIE", "United Kingdom", + "LOT", "LOT Polish Airlines", "LOT", "Poland", + "LOU", "Air Saint Louis", "AIR SAINTLOUIS", "Senegal", + "LOV", "London Flight Centre (Stansted)", "LOVEAIR", "United Kingdom", + "LPA", "Pal Aerolíneas", "LINEASPAL", "Mexico", + "LPC", "Alpine Aviation", "NETSTAR", "South Africa", + "LPD", "UK Royal/HRH Duke of York", "LEOPARD", "United Kingdom", + "LPE", "LATAM Peru", "LANPERU", "Peru", + "LPL", "Lease-a-Plane International", "LEASE-A-PLANE", "United States", + "LPN", "Laoag International Airlines", "LAOAG AIR", "Philippines", + "LPV", "Air Alps Aviation", "ALPAV", "Austria", + "LRA", "Little Red Air Service", "LITTLE RED", "Canada", + "LRB", "L R Airlines", "LADY RACINE", "Czech Republic", + "LRC", "LACSA", "LACSA", "Costa Rica", + "LRD", "Laredo Air", "LAREDO AIR", "United States", + "LRO", "Alrosa-Avia", "ALROSA", "Russia", + "LRR", "Lorraine Aviation", "LORRAINE", "France", + "LRS", "Sansa", "", "Costa Rica", + "LRT", "Lincoln Airlines", "", "Australia", + "LRW", "Al Rida Airways", "AL RIDA", "Mauritania", + "LSA", "LANSA", "INTERNACIONAL", "Dominican Republic", + "LSC", "Los Cedros Aviación", "CEDROS", "Chile", + "LSE", "Línea De Aeroservicios", "", "Chile", + "LSK", "Aurela", "AURELA", "Lithuania", + "LSP", "Spectrum Aviation Incorporated", "AIR TONY", "United Kingdom", + "LSR", "Alsair", "ALSAIR", "France", + "LSS", "Lone Star Airlines", "LONE STAR", "United States", + "LSU", "Laus", "LAUS AIR", "Croatia", + "LSV", "Slovenian Ministry of Defence", "", "Slovenia", + "LSY", "Lindsay Aviation", "LINDSAY AIR", "United States", + "LTA", "LIFT Academy", "LIFT", "United States", + "LTD", "Executive Express Aviation/JA Air Charter", "LIGHT SPEED", "United States", + "LTE", "LTE International Airways", "FUN JET", "Spain", + "LTF", "Lufttaxi Fluggesellschaft", "Garfield", "Germany", + "LTI", "Aerotaxis Latinoamericanos", "LATINO", "Mexico", + "LTL", "Benin Littoral Airways", "LITTORAL", "Benin", + "LTO", "LTU Austria", "BILLA TRANSPORT", "Austria", + "LTR", "Lufttransport", "LUFT TRANSPORT", "Norway", + "LTS", "Flight Inspections and Systems", "SPECAIR", "Russia", + "LTU", "LTU International", "LTU", "Germany", + "LTW", "Luchtvaartmaatschappij Twente", "TWENTAIR", "Netherlands", + "LTY", "Liberty Air", "SKYDECK", "Netherlands", + "LUC", "Albinati Aeronautics", "ALBINATI", "Switzerland", + "LUE", "Aeroclub de Alicante", "", "Spain", + "LUK", "LUKoil-Avia", "LUKOIL", "Russia", + "LUT", "Luft Carago", "LUGO", "South Africa", + "LUV", "Luxembourg Air Rescue", "LUX RESCUE", "Luxembourg", + "LUZ", "Luzair", "LISBON JET", "Portugal", + "LVB", "IRS Airlines", "SILVERBIRD", "Nigeria", + "LVD", "Luftfahrt-Vermietungs-Dienst", "AIR SANTE", "Austria", + "LVL", "Level", "LEVEL", "Spain and France", + "LVN", "Aliven", "ALIVEN", "Italy", + "LVR", "Aviavilsa", "AVIAVILSA", "Lithuania", + "LVT", "La Valenciana Taxi Aéreo", "TAXIVALENCIANA", "Mexico", + "LWA", "Libyan Wings", "LIBYAN WINGS", "Libya", + "LWD", "Leisure Air", "LEISURE WORLD", "United States", + "LWG", "Luxwing", "LUXWING", "Malta", + "LWL", "Lowlevel", "CUB DRIVER", "Portugal", + "LXA", "Luxaviation", "RED LION", "Luxembourg", + "LXF", "Lynx Air International", "LYNX FLIGHT", "United States", + "LXG", "Air Luxor GB", "LUXOR GOLF", "Guinea-Bissau", + "LXJ", "Bombardier Business Jet Solutions", "FLEXJET", "United States", + "LXO", "Luxor Air", "", "Egypt", + "LXP", "LATAM Express", "LANEX", "Chile", + "LYB", "Lynden Air Cargo", "HIGHLANDS", "Papua New Guinea", + "LYC", "Lynden Air Cargo", "LYNDEN", "United States", + "LYD", "Lydd Air", "LYDDAIR", "United Kingdom", + "LYF", "Lithuanian Air Force", "LITHUANIAN AIRFORCE", "Lithuania", + "LYH", "Yankee Lima Helicopteres", "HELIGUYANE", "France", + "LYM", "Key Lime Air", "KEY LIME", "United States", + "LYN", "Air Kyrgyzstan", "ALTYN AVIA", "Kyrgyzstan", + "LYT", "Apatas Air", "APATAS", "Lithuania", + "LYW", "Libyan Airlines", "LIBYAN AIRWAYS", "Libya", + "LYX", "Lynx Aviation", "LYNX AIR", "Pakistan", + "LZA", "Lanza Air", "AEROLANZA", "Spain", + "LZB", "Bulgaria Air", "FLYING BULGARIA", "Bulgaria", + "LZF", "Lease Fly", "SKYLEASE", "Portugal", + "LZP", "Air Ban", "DOC AIR", "Bulgaria", + "LZR", "Air Lazur", "LAZUR BEE-GEE", "Bulgaria", + "LZT", "Lanzarote Aerocargo", "BARAKA", "Spain", + "MAA", "MasAir", "MAS CARGA", "Mexico", + "MAB", "Millardair", "MILLARDAIR", "Canada", + "MAC", "Air Arabia Maroc", "ARABIA MAROC", "Morocco", + "MAD", "Maple Air Services", "MAPLE AIR", "Canada", + "MAE", "Mali Air", "MALI AIREXPRESS", "Austria", + "MAF", "Mission Aviation Fellowship", "MISSI", "Indonesia", + "MAI", "Max Avia", "MAX AVIA", "Kyrgyzstan", + "MAJ", "Majestic Airlines", "MAGIC AIR", "United States", + "MAL", "Morningstar Air Express", "MORNINGSTAR", "Canada", + "MAM", "Aeródromo De La Mancha", "AEROMAN", "Spain", + "MAN", "Mannion Air Charter", "MANNION", "United States", + "MAQ", "Mac Aviation", "MAC AVIATION", "Spain", + "MAR", "March Helicopters", "MARCH", "United Kingdom", + "MAS", "Malaysia Airlines", "MALAYSIAN", "Malaysia", + "MAT", "Maine Aviation", "MAINE-AV", "United States", + "MAU", "Air Mauritius", "AIRMAURITIUS", "Mauritius", + "MAV", "Minoan Air", "MINOAN", "Greece", + "MAW", "Mustique Airways", "MUSTIQUE", "Barbados", + "MAX", "Max-Aviation", "MAX AVIATION", "Canada", + "MAY", "Malta Air", "", "Malta", + "MAZ", "Mines Air Services Zambia", "MINES", "Zambia", + "MBA", "Avag Air", "AVAG AIR", "Austria", + "MBB", "Air Manas", "AIR MANAS", "Kyrgyzstan", + "MBC", "Airjet Exploracao Aerea de Carga", "MABECO", "Angola", + "MBE", "Martin-Baker", "MARTIN", "United Kingdom", + "MBG", "Zephyr Aviation", "CHALGROVE", "United Kingdom", + "MBI", "Mountain Bird", "MOUNTAIN BIRD", "United States", + "MBL", "First City Air", "FIRST CITY", "United Kingdom", + "MBN", "Zambian Airways", "ZAMBIANA", "Zambia", + "MBO", "Mobil Oil", "MOBIL", "Canada", + "MBR", "Brazilian Navy Aviation", "BRAZILIAN NAVY", "Brazil", + "MBS", "Mbach Air", "MBACHI AIR", "Malawi", + "MCB", "Air Mercia", "WESTMID", "United Kingdom", + "MCC", "MCC Aviation", "DISCOVERY", "South Africa", + "MCD", "Air Medical", "AIR MED", "United Kingdom", + "MCE", "Marshall Aerospace", "MARSHALL", "United Kingdom", + "MCF", "MAC Fotografica", "MAC FOTO", "Spain", + "MCG", "SOS Helikoptern Gotland", "MEDICOPTER", "Sweden", + "MCH", "McAlpine Helicopters", "MACLINE", "United Kingdom", + "MCJ", "Avianca Argentina", "JETMAC", "Argentina", + "MCK", "Macair Airlines", "", "Australia", + "MCL", "Medical Aviation Services", "MEDIC", "United Kingdom", + "MCM", "Heli-Air-Monaco", "HELI AIR", "Monaco", + "MCN", "Mac Dan Aviation Corporation", "MAC DAN", "United States", + "MCO", "Aerolíneas Marcos", "MARCOS", "Mexico", + "MCP", "Marcopolo Airways", "MARCOPOLO", "Afghanistan", + "MCS", "Macedonian Airlines", "MACAIR", "Greece", + "MCT", "Transportación Aérea Del Mar De Cortés", "TRANS CORTES", "Mexico", + "MCV", "MTC Aviación", "MTC AVIACION", "Mexico", + "MCX", "Cargo Express", "MAURICARGO", "Mauritania", + "MCY", "Ambulance Air Africa", "MERCY", "South Africa", + "MDA", "Mandarin Airlines", "MANDARIN", "Taiwan", + "MDB", "Monde Air Charters", "MONDEAIR CARGO", "United Kingdom", + "MDC", "Atlantic Aero and Mid-Atlantic Freight", "NIGHT SHIP", "United States", + "MDE", "Mauritanienne De Transport Aerien", "MAURI-TRANS", "Mauritania", + "MDF", "Mediterranean Air Freight", "MED-FREIGHT", "Greece", + "MDG", "Air Madagascar", "AIR MADAGASCAR", "Madagascar", + "MDH", "Madina Air", "MADINA AIR", "Libya", + "MDJ", "Jetran Air", "JETRAN AIR", "Romania", + "MDL", "Mandala Airlines", "MANDALA", "Indonesia", + "MDM", "Medavia", "MEDAVIA", "Malta", + "MDN", "Mudan Airlines", "", "Somali Republic", + "MDR", "Compania Mexicana De Aeroplanos", "AEROPLANOS", "Mexico", + "MDS", "McNeely Charter Services", "MID-SOUTH", "United States", + "MDT", "Sundt Air", "MIDNIGHT", "Norway", + "MDV", "Moldavian Airlines", "MOLDAVIAN", "Moldova", + "MDX", "Aerosud Charter", "MEDAIR", "South Africa", + "MDY", "Mediterranean Airways", "", "Egypt", + "MEA", "Middle East Airlines", "CEDAR JET", "Lebanon", + "MEC", "Mercury Aircourier Service", "MERCAIR", "United States", + "MED", "Ontario Ministry of Health", "MEDICAL", "Canada", + "MEE", "Elimediterranea", "ELIMEDITERRANEA", "Italy", + "MEF", "Air Meridan", "EMPENNAGE", "Nigeria", + "MEI", "Merlin Airways", "AVALON", "United States", + "MEJ", "Medjet International", "MEDJET", "United States", + "MEK", "Med-Trans of Florida", "MED-TRANS", "United States", + "MEL", "Mega Linhas Aéreas", "MEGA AIR", "Brazil", + "MEM", "Meridian Limited", "MERIDIAN CHERRY", "Ukraine", + "MEP", "Midwest Airlines", "MIDEX", "United States", + "MER", "Methow Aviation", "METHOW", "United States", + "MES", "Mesaba Airlines", "MESABA", "United States", + "MET", "Meteorological Research Flight", "METMAN", "United Kingdom", + "MEX", "Metro Express", "EAGLE EXPRESS", "United States", + "MEY", "Justair Scandinavia", "MELODY", "Sweden", + "MFA", "Martyn Fiddler Associates", "SEAHORSE", "United Kingdom", + "MFB", "Mountain Flyers 80", "MOUNTAINHELI", "Switzerland", + "MFC", "Moncton Flying Club", "EAST WIND", "Canada", + "MFL", "Aero McFly", "MCFLY", "Mexico", + "MFR", "Midline Air Freight", "MIDLINE FREIGHT", "United States", + "MFS", "Miller Flying Services", "MILLER TIME", "United States", + "MFT", "Multiflight", "YORKAIR", "", + "MFZ", "Mofaz Air", "MOFAZ AIR", "Malaysia", + "MGA", "MG Aviación", "MAG AVACION", "Spain", + "MGB", "Coulson Flying Service", "MOCKINGBIRD", "United Kingdom", + "MGE", "Asia Pacific Airlines", "MAGELLAN", "United States", + "MGG", "Elmagal Aviation Services", "ELMAGAL", "Sudan", + "MGI", "Marghi Air", "MARGHI", "Nigeria", + "MGK", "Mega", "MEGLA", "Kazakhstan", + "MGL", "MIAT Mongolian Airlines", "MONGOL AIR", "Mongolia", + "MGM", "Transporte Aero MGM", "AERO EMM-GEE-EMM", "Mexico", + "MGO", "Punto Fa", "MANGO", "Spain", + "MGR", "Magna Air", "MAGNA AIR", "Austria", + "MGS", "Aeromagar", "AEROMAGAR", "Mexico", + "MGX", "Montenegro Airlines", "MONTENEGRO", "Montenegro", + "MHA", "Mountain High Aviation", "MOUNTAIN HIGH", "United States", + "MHC", "Aero Jomacha", "AERO JOMACHA", "Mexico", + "MHD", "Yas Air Kish", "YAS AIR", "Iran", + "MHF", "Maritime Helicopters", "AIR MARITIME", "United States", + "MHL", "Meridian Airlines", "HASSIMAIR", "Nigeria", + "MHN", "Manhattan Air", "MANHATTAN", "United Kingdom", + "MHQ", "Skargardshavets Helikoptertjanst", "HELICARE", "Finland", + "MHU", "Air Memphis", "MEPHIS UGANDA", "Uganda", + "MHV", "MHS Aviation GmbH", "SNOWCAP", "Germany", + "MIA", "Mauria", "MAURIA", "Mauritania", + "MIC", "Mint Airways", "MINT AIRWAYS", "Spain", + "MID", "Midland Airport Services", "", "United Kingdom", + "MIE", "Aero Premier De Mexico", "AEROPREMIER", "Mexico", + "MIF", "Miras", "MIRAS", "Kazakhstan", + "MIG", "Russian Aircraft Corporation-MiG", "MIG AVIA", "Russia", + "MIM", "Mimino", "MIMINO", "Russia", + "MIR", "Miramichi Air Service", "MIRAMICHI", "Canada", + "MIS", "Midstate Airlines", "MIDSTATE", "United States", + "MIT", "Flight Line", "MATCO", "United States", + "MIZ", "Desarrollo Milaz", "MILAZ", "Mexico", + "MJB", "Magic Blue Airlines", "MAGIC BLUE", "Netherlands", + "MJC", "Mandarin Air", "AIR MANDA", "China", + "MJF", "MJET", "EM-EXPRESS", "Austria", + "MJL", "Jet Line International", "MOLDJET", "Moldova", + "MJM", "Eti 2000", "ELCO ETI", "Italy", + "MJN", "Royal Air Force of Oman", "MAJAN", "Oman", + "MJR", "Midamerica Jet", "MAJOR", "United States", + "MJT", "Mex-Jet", "MEJETS", "Mexico", + "MKA", "MK Airline", "KRUGER-AIR", "Ghana", + "MKH", "Air Marrakech Service", "AIR MARRAKECH", "Morocco", + "MKK", "Malaya Aviatsia Dona", "AEROKEY", "Russia", + "MKL", "McCall Aviation", "MCCALL", "United States", + "MKO", "Markoss Aviation", "GOSHAWK", "United Kingdom", + "MKS", "Pimichikamac Air", "MIKISEW", "Canada", + "MKY", "Monky Aerotaxis", "MONKY", "Mexico", + "MLA", "40-Mile Air", "MILE-AIR", "United States", + "MLB", "Manaf International Airways", "MANAF", "Burundi", + "MLC", "Malila Airlift", "MALILA", "Democratic Republic of the Congo", + "MLD", "Air Moldova", "AIR MOLDOVA", "Moldova", + "MLE", "Moldaeroservice", "MOLDAERO", "Moldova", + "MLF", "Amal Airlines", "AMAL", "Djibouti", + "MLG", "Malagasy Airlines", "", "Madagascar", + "MLH", "Mahalo Air", "MAHALO", "United States", + "MLK", "Millennium Air", "NIGERJET", "Nigeria", + "MLL", "Aeroclub de Mallorca", "MALLORCA", "Spain", + "MLM", "Comlux Malta", "LUXMALTA", "Malta", + "MLN", "Air Madeleine", "AIR MADELEINE", "Canada", + "MLO", "Servicios Aéreos Milenio", "MILENIO", "Mexico", + "MLR", "Mihin Lanka", "MIHIN LANKA", "Sri Lanka", + "MLS", "Mall Airways", "MALL-AIRWAYS", "United States", + "MLT", "Maleth-Aero", "", "Malta", + "MLU", "Zracno Pristaniste Mali Losinj", "MALI LOSINJ", "Croatia", + "MLV", "Multiservicios Aereos Del Valle", "MULTI VALLE", "Mexico", + "MLX", "Malawi Express", "MALAWI EXPRESS", "Malawi", + "MMA", "Myanmar Airways International", "MYANMAR", "Myanmar", + "MMC", "Aermarche", "AERMARCHE", "Italy", + "MMD", "Air Alsie", "MERMAID", "Denmark", + "MMG", "Aereo Ruta Maya", "RUTA MAYA", "Guatemala", + "MMH", "McMahon Helicopter", "NIGHT RIDER", "United States", + "MMJ", "Macau Jet International", "MACAUJET", "China", + "MML", "Hunnu Air", "TRANS MONGOLIA", "Mongolia", + "MMM", "Aviation Company Meridian", "AVIAMERIDIAN", "Russia", + "MMP", "AMP Incorporated", "AMP-INC", "United States", + "MMR", "Musrata Air Transport", "MUSRATA AIR", "Libya", + "MMS", "SAAD (A320) Limited", "MUSAAD AIR", "Cayman Islands", + "MMX", "Airmax", "PERUMAX", "Peru", + "MMZ", "EuroAtlantic Airways", "EUROATLANTIC", "Portugal", + "MNA", "Merpati Nusantara Airlines", "MERPATI", "Indonesia", + "MNB", "MNG Airlines", "BLACK SEA", "Turkey", + "MNC", "MIT Airlines", "MUNCIE", "Canada", + "MNE", "Air Montenegro", "MOUNT EAGLE", "Montenegro", + "MNG", "Aero Mongolia", "AERO MONGOLIA", "Mongolia", + "MNH", "Monarch Airlines", "MONARCH AIR", "United States", + "MNI", "Aeromilenio", "AEROMIL", "Mexico", + "MNJ", "Menajet", "MENAJET", "Lebanon", + "MNL", "Miniliner", "MINILINER", "Italy", + "MNO", "Mango", "TULCA", "South Africa", + "MNR", "Mann Air", "TEEMOL", "United Kingdom", + "MNS", "Ministic Air", "MINISTIC", "Canada", + "MNT", "Montserrat Airways", "MONTSERRAT", "Montserrat", + "MNU", "Elite Airways", "MAINER", "United States", + "MNV", "Mauritanienne Aerienne Et Navale", "NAVALE", "Mauritania", + "MNX", "Manx Airlines", "MANX", "United Kingdom", + "MNY", "Mooney Aircraft Corporation", "MOONEY FLIGHT", "United States", + "MNZ", "Murmansk Aircompany", "MURMAN AIR", "Russia", + "MOC", "Air Monarch Cargo", "MONARCH CARGO", "Mexico", + "MOH", "Tigerfly", "MOTH", "United Kingdom", + "MOP", "Aeropublicitaria De Angola", "PUBLICITARIA", "Angola", + "MOR", "Aerolíneas De Morelia", "AEROMORELIA", "Mexico", + "MOS", "Misr Overseas Airways", "", "Egypt", + "MOV", "VIM Airlines", "MOV AIR", "Russia", + "MOW", "Mohawk Airlines", "MOHAWK AIR", "United States", + "MPA", "Mid-Pacific Airlines", "MID PAC", "United States", + "MPC", "Mountain Pacific Air", "MOUNTAIN PACIFIC", "Canada", + "MPD", "Air Plus Comet", "RED COMET", "Spain", + "MPH", "Martinair", "MARTINAIR", "Netherlands", + "MPI", "Mosphil Aero", "MOSPHIL", "Philippines", + "MPJ", "MAP-Management and Planung", "MAPJET", "Austria", + "MPL", "Master Planner", "", "United States", + "MPO", "Transportes Aéreos Amparo", "AMPARO", "Mexico", + "MPR", "Empire Aviation Services", "", "Nigeria", + "MPS", "Metropolis", "METRO REGIONAL", "Netherlands", + "MPT", "Miapet-Avia", "MIAPET", "Armenia", + "MPX", "Aeromexpress", "AEROMEXPRESS", "Mexico", + "MQT", "Air ITM", "MUSKETEER", "France", + "MRA", "Martinaire", "MARTEX", "United States", + "MRD", "Meridian Air Cargo", "MERIDIAN", "United States", + "MRE", "Namibia Commercial Aviation", "MED RESCUE", "Namibia", + "MRF", "Mauritanienne Air Fret", "MAUR-FRET", "Mauritania", + "MRG", "MANAG'AIR", "MANAG'AIR", "France", + "MRH", "RAF Marham", "MARHAM", "United Kingdom", + "MRI", "Servicios Aéreos Moritani", "MORITANI", "Mexico", + "MRK", "Markair", "MARKAIR", "United States", + "MRL", "Aeromorelos", "AEROMORELOS", "Mexico", + "MRM", "Aerocharter", "MARITIME", "Canada", + "MRN", "Missions Gouvernemtales Francaises", "MARIANNE", "France", + "MRO", "Morrison Flying Service", "MORRISON", "United States", + "MRP", "Abas", "ABAS", "Czech Republic", + "MRR", "San Juan Airlines", "MARINER", "United States", + "MRT", "Air Mauritanie", "MIKE ROMEO", "Mauritania", + "MRW", "Mars RK", "AVIAMARS", "Ukraine", + "MRX", "Herman's Markair Express", "SPEEDMARK", "United States", + "MRY", "Air Marine", "AIR MARINE", "France", + "MRZ", "Medical Air Rescue Services", "MARS", "Zimbabwe", + "MSA", "Poste Air Cargo", "AIRMERCI", "Italy", + "MSC", "Air Cairo", "", "Egypt", + "MSF", "Minsheng International Jet", "MEINSHENG", "China", + "MSG", "Servico Aéreo Regional", "SAR-REGIONAL", "Mozambique", + "MSH", "US Marshals Service", "MARSHALAIR", "United States", + "MSI", "Motor Sich Airlines", "MOTOR SICH", "Ukraine", + "MSJ", "Magnum Air", "MAGNUM AIR", "Philippines", + "MSK", "Air Sport", "AIR SPORT", "Bulgaria", + "MSL", "Marsland Aviation", "MARSLANDAIR", "Sudan", + "MSM", "Aeromas", "AEROMAS EXPRESS", "Uruguay", + "MSN", "Missionair", "MISIONAIR", "Spain", + "MSO", "Aerolíneas Mesoamericanas", "MESO AMERICANAS", "Mexico", + "MSP", "Servicio De Vigilancia Aérea Del Ministerio De Seguridad Pública", "SEGURIDAD", "Costa Rica", + "MSQ", "Meta Linhas Aéreas", "META", "Brazil", + "MSR", "Egyptair", "EGYPTAIR", "Egypt", + "MSS", "Morris Air Service", "WASATCH", "United States", + "MSV", "Aero-Kamov", "AERAFKAM", "Russia", + "MSW", "Master Airways", "MASTER AIRWAYS", "Serbia", + "MSX", "Egyptair Cargo", "EGYPTAIR CARGO", "Egypt", + "MSY", "Massey University School of Aviation", "MASSEY", "New Zealand", + "MTA", "GAK/Mitchell Aero", "GAK AVIATION", "United States", + "MTB", "Aerotaxis Metropolitanos", "AEROMETROPOLIS", "Mexico", + "MTC", "Mountain Air Company", "MOUNTAIN LEONE", "Sierra Leone", + "MTD", "MacKnight Airlines", "", "Australia", + "MTG", "Servicios Aéreos MTT", "", "Mexico", + "MTH", "Massachusetts Institute of Technology", "RESEARCH", "United States", + "MTI", "Monerrey Air Taxi", "MONTERREY AIR", "Mexico", + "MTJ", "Metrojet", "METROJET", "Hong Kong", + "MTK", "Air Metack", "AIRMETACK", "Angola", + "MTL", "RAF-Avia", "MITAVIA", "Latvia", + "MTN", "Mountain Air Cargo", "MOUNTAIN", "United States", + "MTO", "Marathon Airlines", "MARATHON", "Greece", + "MTP", "Island Helicopters", "METROCOPTER", "United States", + "MTR", "Metroflight", "METRO", "United States", + "MTS", "Mantrust Asahi Airways", "MANTRUST", "Indonesia", + "MTU", "Middle Tennessee State University", "BLUE RAIDER", "United States", + "MTV", "Mountain Valley Air Service", "MOUNTAIN VALLEY", "United States", + "MTX", "Multi Taxi", "MULTITAXI", "Mexico", + "MTZ", "Mali Airways", "MALI AIRWAYS", "Mali", + "MUA", "Murray Air", "MURRAY AIR", "United States", + "MUI", "Trans Air", "MAUI", "United States", + "MUL", "Mokulele Airlines", "MUKULELE", "United States", + "MUN", "Aeromundo Ejecutivo", "AEROMUNDO", "Mexico", + "MUR", "Aerolínea Muri", "MURI", "Mexico", + "MVA", "Mississippi Valley Airways", "VALAIR", "United States", + "MVD", "Kavminvodyavia", "AIR MINVODY", "Russia", + "MVG", "Moldova", "MOLDOVA-STATE", "Moldova", + "MVI", "Metro Business Aviation", "", "United Kingdom", + "MVK", "Helicopter Training & Hire", "MAVRIK", "United Kingdom", + "MVL", "Mavial Magadan Airlines", "Mavial", "Russia", + "MVM", "Air Cargo America", "PEGASUS", "United States", + "MVN", "Marvin Limited", "MARVIN", "United Kingdom", + "MVR", "Maverick Airways", "MAV-AIR", "United States", + "MVY", "VIM-Aviaservice", "", "Russia", + "MWA", "Midwest Airlines (Egypt)", "", "Egypt", + "MWG", "MASwings", "MASWINGS", "Malaysia", + "MWI", "Malawian Airlines 2014", "MALAWIAN", "Malawi", + "MWM", "Modern Transporte Aereo De Carga", "MODERNAIR", "Brazil", + "MWR", "Raslan Air Service", "RASLAN", "Egypt", + "MWS", "Malta Wings", "MALTA WINGS", "Malta", + "MWT", "Midwest Aviation Division", "MIDWEST", "United States", + "MWY", "Mauritanienne Airways", "MAURITANIENNE", "Mauritania", + "MXA", "Mexicana de Aviación", "MEXICANA", "Mexico", + "MXB", "Mex Blue", "MEX BLUE", "Mexico", + "MXD", "Malindo Airways", "MALINDO EXPRESS", "Malaysia", + "MXE", "Mocambique Expresso", "MOZAMBIQUE EXPRESS", "Mozambique", + "MXF", "Maximum Flight Advantages", "MAXFLIGHT", "United States", + "MXL", "Maxair", "MAXAIR", "Sweden", + "MXO", "Aerotaxi Mexicano", "MAXAERO", "Mexico", + "MXP", "May Air Xpress", "BEECHNUT", "United States", + "MXQ", "Transportes Aéreos Mexiquenses", "MEXIQUENSES", "Mexico", + "MXS", "Millon Express", "MILLON EXPRESS", "United States", + "MXT", "México Transportes Aéreos", "TRANSMEX", "Mexico", + "MXU", "Maximus Air Cargo", "CARGO MAX", "United Arab Emirates", + "MXX", "Merchant Express Aviation", "MERCHANT", "Nigeria", + "MXY", "Breeze Airways", "MOXY", "United States", + "MYA", "Myflug", "MYFLUG", "Iceland", + "MYD", "Maya Island Air", "MYLAND", "Belize", + "MYI", "Mayair", "MAYAIR", "Mexico", + "MYM", "MYAirline", "MYAIR", "Malaysia", + "MYO", "Dominguez Toledo (Grupo Mayoral)", "MAYORAL", "Spain", + "MYP", "Mann Yadanarpon Airlines", "MANN ROYAL", "Myanmar", + "MYS", "Aero Yaqui Mayo", "AERO YAQUI", "Mexico", + "MYW", "MyWay Airlines", "MYSKY", "Georgia", + "MYX", "Smartlynx Airlines Estonia", "TALLINN CAT", "Estonia", + "MZA", "Irtysh Air", "IRTYSH AIRLINES", "Kazakhstan", + "MZE", "Zenith Aviation (Malta)", "", "Malta", + "MZK", "AVC Airlines", "", "Japan", + "MZL", "Aerovías Montes Azules", "MONTES AZULES", "Mexico", + "MZS", "Mahfooz Aviation", "MAHFOOZ", "Gambia", + "N/A", "North Coast Air Services Ltd", "NORTH COAST", "Canada", + "NAA", "Norwegian Air Argentina", "NORUEGA", "Argentina", + "NAB", "Mina Airline Company", "", "Egypt", + "NAC", "Northern Air Cargo", "YUKON", "United States", + "NAD", "Seulawah Nad Air", "SEULAWAH", "Indonesia", + "NAE", "Nations Air Express Inc", "NATIONS EXPRESS", "United States", + "NAF", "Royal Netherlands Air Force", "NETHERLANDS AIR FORCE", "Netherlands", + "NAH", "Nahanni Air Services Ltd", "NAHANNI", "Canada", + "NAI", "North Adria Aviation", "NORTH-ADRIA", "Croatia", + "NAJ", "North American Jet Charter Group", "JET GROUP", "United States", + "NAK", "École Nationale de l'Aviation Civile", "ENAC SCHOOL", "France", + "NAL", "Northway Aviation Ltd", "NORTHWAY", "Canada", + "NAM", "Nortland Air Manitoba", "MANITOBA", "Canada", + "NAN", "Norwegian Air Norway", "NORSHIP", "Norway", + "NAO", "North American Airlines", "NORTH AMERICAN", "United States", + "NAP", "Napier Air Service Inc", "NAPIER", "United States", + "NAR", "Air Continental Inc", "NIGHT AIR", "United States", + "NAS", "Nasair", "NASAIRWAYS", "Eritrea", + "NAT", "North Atlantic Air Inc", "MASS AIR", "United States", + "NAU", "Antanik-Air", "ANTANIK", "Ukraine", + "NAV", "Nav Flight Planning", "NAV DISPATCH", "Czech Republic", + "NAY", "Navegación Servicios Aéreos Canarios S.A.", "NAYSA", "Spain", + "NAZ", "Servicios Aéreos del Nazas S.A. de C.V.", "NAZAS", "Mexico", + "NBE", "Novosibirsk Aviaenterprise", "NAKAIR", "Russia", + "NBK", "Albarka Air", "AL-AIR", "Nigeria", + "NBL", "Nobil Air", "NOBIL AIR", "Moldova", + "NBN", "North British Airlines", "TEESAIR", "United Kingdom", + "NBR", "Haughey Air", "NORBROOK", "United Kingdom", + "NBS", "Nimbus Aviation", "NIMBUS", "United Kingdom", + "NBT", "Norse Atlantic Airways", "LONGSHIP", "Norway", + "NCA", "Nippon Cargo Airlines", "NIPPON CARGO", "Japan", + "NCB", "North Caribou Flying Service Ltd", "NORTH CARIBOU", "Canada", + "NCC", "T3 Aviation Inc.", "STARFLEET", "United States", + "NCE", "Northcoast Executive Airlines", "TOP HAT", "United States", + "NCF", "Norfolk County Flight College", "COUNTY", "United Kingdom", + "NCG", "Nederlandse Kustwacht", "NETHERLANDS COASTGUARD", "Netherlands", + "NCH", "Chanchangi Airlines", "CHANCHANGI", "Nigeria", + "NCM", "Nas Air", "AIR BANE", "Angola", + "NCN", "National Airlines", "", "Chile", + "NCO", "Natalco Air Lines", "NATALCO", "São Tomé and Príncipe", + "NCP", "Capital Airlines Limited", "CAPITAL SHUTTLE", "Nigeria", + "NCR", "National Air Cargo dba National Airlines", "NATIONAL CARGO", "United States", + "NCS", "Simpson Air Ltd", "COMMUTER-CANADA", "Canada", + "NCT", "NokScoot", "BIG BIRD", "Thailand", + "NDA", "Northern Airways", "NORTHERN DAKOTA", "United States", + "NDF", "Namibian Defence Force", "NAMIBIAN AIR FORCE", "Namibia", + "NDS", "Nordstree (Australia)", "", "Australia", + "NDU", "University of North Dakota", "SIOUX", "United States", + "NEA", "New England Airlines", "NEW ENGLAND", "United States", + "NEB", "State of Nebraska", "NEBRASKA", "United States", + "NEC", "Necon Air", "NECON AIR", "Nepal", + "NEE", "Northeast Airlines", "NORTHEAST", "United States", + "NEF", "Nord-Flyg", "NORDEX", "Sweden", + "NEG", "Línea Aérea de Fumig Aguas Negras", "AGUAS NEGRAS", "Chile", + "NEJ", "Netjets Business Aviation", "NET BUSINESS", "China", + "NEL", "Aero Servicios de Nuevo Laredo", "AEROLAREDO", "Mexico", + "NEN", "North-East Airlines", "NORTHEAST SWAN", "Nigeria", + "NER", "Air Newark", "NEWAIR", "United States", + "NES", "Nordeste Linhas Aéreas Regionais", "NORDESTE", "Brazil", + "NET", "Network Aviation Services", "NETWORK", "Nigeria", + "NEW", "Northeastern Aviation", "MEADOW FLIGHT", "United States", + "NEX", "Northern Executive Aviation", "NEATAX", "United Kingdom", + "NEZ", "New England Air Express", "ENGAIR", "United States", + "NFA", "North Flying", "NORTH FLYING", "Denmark", + "NFC", "North Atlantic Cargo", "NORTH ATLANTIC", "Norway", + "NFL", "Northaire Freight Lines", "GREAT LAKES", "United States", + "NFT", "Nefteyugansk Aviation Division", "NEFTEAVIA", "Russia", + "NGA", "Nigeria Airways", "NIGERIA", "Nigeria", + "NGC", "Angoservice", "ANGOSERVICE", "Angola", + "NGF", "Angel Flight America", "ANGEL FLIGHT", "United States", + "NGK", "Oriental Air Bridge", "ORIENTAL BRIDGE", "Japan", + "NGO", "Air-Angol", "AIR ANGOL", "Angola", + "NGR", "Nigerian Air Force", "NIGERIAN AIRFORCE", "Nigeria", + "NGV", "Angoavia", "ANGOAVIA", "Angola", + "NGX", "Nigerian Global", "AIR GLOBAL", "Nigeria", + "NHC", "Northern Helicopter", "NORTHERN", "Germany", + "NHG", "NHT Linhas Aéreas", "HELGA", "Brazil", + "NHK", "Federal Aviation Administration", "NIGHTHAWK", "United States", + "NHL", "Northumbria Helicopters", "NORTHUMBRIA", "United Kingdom", + "NHR", "Nuevo Horizonte Internacional", "NUEVO HORIZONTE", "Mexico", + "NHT", "New Heights 291", "NEWHEIGHTS", "South Africa", + "NHV", "NHV Aviation", "", "Ghana", + "NHZ", "Nada Air Service", "NADA AIR", "Chad", + "NIC", "Northern Illinois Commuter", "ILLINOIS COMMUTER", "United States", + "NID", "Aeroni", "AERONI", "Mexico", + "NIE", "Aeroejecutiva Nieto", "AERONIETO", "Mexico", + "NIG", "Aero Contractors", "AEROLINE", "Nigeria", + "NIH", "NAM Air", "NAM", "Indonesia", + "NIN", "Niger Airlines", "NIGER AIRLINES", "Niger", + "NIR", "Norsk Flytjeneste", "NORSEMAN", "Norway", + "NIS", "Nicaragüense de Aviación", "NICA", "Nicaragua", + "NIT", "Midwest Aviation", "NIGHTTRAIN", "United States", + "NJA", "New Japan Aviation", "SHIN NIHON", "Japan", + "NJC", "Nashville Jet Charters", "NASHVILLE JET", "United States", + "NJE", "NetJets Europe", "FRACTION", "Portugal", + "NJS", "National Jet Systems", "NATIONAL JET", "Australia", + "NKF", "Barents AirLink", "NORDFLIGHT", "Sweden", + "NKL", "Nakheel Aviation", "NAKHEEL", "United Arab Emirates", + "NKP", "Abakan Air", "ABAKAN AIR", "Russia", + "NKS", "Spirit Airlines", "SPIRIT WINGS", "United States", + "NKV", "Nikolaev-Air", "AIR NIKOLAEV", "Ukraine", + "NKY", "Aeromonkey", "AEROMON", "Mexico", + "NKZ", "Aerokuzbass", "NOVOKUZNETSK", "Russia", + "NLA", "Neiltown Air", "NEILTOWN AIR", "Canada", + "NLC", "Nelair Charters", "NELAIR", "South Africa", + "NLG", "NEL Cargo", "NELCARGO", "Ivory Coast", + "NLH", "Norwegian Long Haul", "NORSTAR", "Norway", + "NLK", "Elbrus-Avia Air Enterprise", "ELAVIA", "Russia", + "NLL", "Northafrican Air Transport", "NORTHAFRICAN AIR", "Libya", + "NLS", "Nationale Luchtvaartschool", "PANDER", "Netherlands", + "NLT", "Newfoundland Labrador Air Transport", "NALAIR", "Canada", + "NLW", "Nile Wings Aviation Services", "NILE WINGS", "Sudan", + "NLY", "Niki", "FLYNIKI", "Austria", + "NMB", "Air Namibia", "NAMIBIA", "Namibia", + "NMD", "Nomad Aviation", "NOMAD AIR", "Namibia", + "NMI", "Pacific Wings", "TSUNAMI", "United States", + "NML", "Gambia New Millennium Air", "NEWMILL", "Gambia", + "NOA", "Norontair", "NORONTAIR", "Canada", + "NOC", "Norcopter", "NORCOPTER", "Norway", + "NOF", "Fonnafly", "FONNA", "Norway", + "NOH", "No. 32 (The Royal) Squadron", "NORTHOLT", "United Kingdom", + "NOK", "Nok Air", "NOK AIR", "Thailand", + "NOL", "National Overseas Airlines Company", "NAT AIRLINE", "Egypt", + "NON", "Servicios Aéreos Latinoamericanos", "SERVICIOS LATINO", "Mexico", + "NOR", "Norsk Helikopter", "NORSKE", "Norway", + "NOS", "Neos", "MOONFLOWER", "Italy", + "NOT", "Línea Aérea Costa Norte", "COSTA NORTE", "Chile", + "NOV", "Nova Airline", "NOVANILE", "Sudan", + "NOW", "Royal Norwegian Air Force", "NORWEGIAN", "Norway", + "NOY", "Noy Aviation", "NOY AVIATION", "Israel", + "NOZ", "Norwegian Air Shuttle", "NORDIC", "Norway", + "NPC", "Western Air Couriers", "NORPAC", "United States", + "NPO", "Novosibirsk Aviation Production Association", "NOVSIB", "Russia", + "NPT", "West Atlantic UK", "NEPTUNE", "United Kingdom", + "NPX", "Northeast Aviation", "NORTHEAST EXPRESS", "United States", + "NRC", "North Sea Airways", "NORTH SEA", "Netherlands", + "NRD", "Nordic Regional", "NORTH RIDER", "Sweden", + "NRE", "Aviones Are", "AVIONES ARE", "Mexico", + "NRG", "Ross Aviation", "ENERGY", "United States", + "NRK", "Naturelink Charter", "NATURELINK", "South Africa", + "NRL", "Nolinor Aviation", "NOLINOR", "Canada", + "NRN", "Royal Netherland Navy", "NETHERLANDS NAVY", "Netherlands", + "NRP", "Aeronord-Grup", "AERONORD", "Moldova", + "NRR", "Natureair", "NATUREAIR", "Costa Rica", + "NRS", "Norwegian Air UK", "REDNOSE", "United Kingdom", + "NRT", "Norestair", "NORESTAIR", "Spain", + "NRV", "North Vancouver Airlines", "NORVAN", "Canada", + "NRW", "Polizeifliegerstaffel Nordrhein-Westfalen", "HUMMEL", "Germany", + "NRX", "Norse Air Charter", "NORSE AIR", "South Africa", + "NRZ", "Servicios Aéreos Monarrez", "MONARREZ", "Mexico", + "NSA", "Nile Safaris Aviation", "NILE SAFARIS", "Sudan", + "NSC", "Societe De Transport Aerien De Mauritanie", "TRANS-SOCIETE", "Mauritania", + "NSE", "SATENA", "SATENA", "Colombia", + "NSF", "Northamptonshire School of Flying", "NORTON", "United Kingdom", + "NSK", "Air Intersalonika", "INTERSALONIKA", "Greece", + "NSL", "Neric", "NERICAIR", "United Kingdom", + "NSM", "Global Jet Corporation", "THUNDERCLOUD", "United States", + "NSO", "Aerolíneas Sosa", "SOSA", "Honduras", + "NSP", "Novosibirsk Aircraft Repairing Plant", "NARPAIR", "Russia", + "NSR", "National Air Charter", "NASAIR", "Indonesia", + "NSS", "Northstar Aviation", "NORTHSTAR", "United States", + "NSW", "Country Connection Airlines", "", "Australia", + "NSZ", "Norwegian Air Sweden", "REDNOSE", "Sweden", + "NTA", "Northern Thunderbird Air", "THUNDERBIRD", "Canada", + "NTB", "Servicios Aéreos Del Norte", "SERVINORTE", "Mexico", + "NTC", "Gibson Aviation", "NIGHT CHASE", "United States", + "NTD", "Aero Norte", "", "Mexico", + "NTE", "Interaire", "INTERMEX", "Mexico", + "NTG", "Servicios Integrales De Aviación", "INTEGRALES", "Mexico", + "NTH", "Hokkaido Air System", "NORTH AIR", "Japan", + "NTJ", "NextJet", "NEXTJET", "Sweden", + "NTK", "National Air Traffic Controllers Association", "NATCA", "United States", + "NTR", "TNT International Aviation", "NITRO", "United Kingdom", + "NTS", "Cirrus Air", "NITE STAR", "United States", + "NTT", "Inter Tropic Airlines", "INTER-TROPIC", "Sierra Leone", + "NTV", "Air Inter Ivoire", "INTER-IVOIRE", "Ivory Coast", + "NTW", "Nationwide Airlines", "NATIONWIDE", "South Africa", + "NTX", "Northern Jet Management", "NORTAX", "United States", + "NUB", "Nomad Aviation", "VALLETTA", "Malta", + "NUL", "Aeroservicios De Nuevo Leon", "SERVICIOS NUEVOLEON", "Mexico", + "NUN", "Nunasi-Central Airlines", "NUNASI", "Canada", + "NVA", "Aeroclub de Albacete", "", "Spain", + "NVC", "Nav Canada", "NAV CAN", "Canada", + "NVD", "Avion Express", "NORDVIND", "Lithuania", + "NVG", "Novgorod Air Enterprise", "SADKO AVIA", "Russia", + "NVI", "Avial NV Aviation Company", "NEW AVIAL", "Russia", + "NVJ", "Fly International Airways", "NOUVINTER", "Tunisia", + "NVK", "Nizhnevartovskavia", "VARTOSKAVIA", "Russia", + "NVL", "Navigator Airlines", "NAVLINES", "Armenia", + "NVM", "Naviera Mexicana", "NAVIERA", "Mexico", + "NVP", "Navegacao Aérea De Portugal", "", "Portugal", + "NVQ", "Novo Air", "NOVO AIR", "Bangladesh", + "NVR", "Novair", "NAVIGATOR", "Sweden", + "NVY", "Royal Navy", "NAVY", "United Kingdom", + "NWD", "New World Jet Corporation", "NEW WORLD", "United States", + "NWE", "Northwest Aero Associates", "", "United States", + "NWG", "Airwing", "NORWING", "Norway", + "NWL", "North-Wright Airways", "NORTHWRIGHT", "Canada", + "NWN", "Northwinds Northern", "NORTHWINDS", "Canada", + "NWO", "RAF Northwood", "", "United Kingdom", + "NWR", "Northwest Regional Airlines", "", "Australia", + "NWS", "Nordwind Airlines", "NORDLAND", "Russia", + "NWT", "Northwest Territorial Airways", "TERRITORIAL", "Canada", + "NWW", "North West Airlines", "HALANT", "Australia", + "NWZ", "Nationwide Airlines (Zambia)", "ZAMNAT", "Zambia", + "NXA", "Air Next", "BLUE-DOLPHIN", "Japan", + "NXF", "Nextflight Aviation", "NEXTFLIGHT", "United States", + "NXS", "Nexus Aviation", "NEXUS AVIATION", "Nigeria", + "NXT", "National Express", "NATIONAL FREIGHT", "United States", + "NYA", "Nanyah Aviation", "NANYAH", "Israel", + "NYB", "Belgian Navy", "BELGIAN NAVY", "Belgium", + "NYH", "New York Helicopter", "NEW YORK", "United States", + "NYL", "Mid Airlines", "NILE", "Sudan", + "NYS", "Nyasa Express", "NYASA", "Malawi", + "NYT", "Yeti Airlines", "YETI AIRLINES", "Nepal", + "NZA", "Nayzak Air Transport", "", "Libya", + "NZM", "Mount Cook Airline", "MOUNTCOOK", "New Zealand", + "OAA", "Oxley Aviation", "", "Australia", + "OAC", "Oriental Airlines", "ORIENTAL AIR", "Nigeria", + "OAD", "Orscom Tourist Installations Company", "ORSCOM", "Egypt", + "OAE", "Omni Air International", "OMNI-EXPRESS", "United States", + "OAL", "Olympic Air", "OLYMPIC", "Greece", + "OAO", "Arkhangelsk 2 Aviation Division", "DVINA", "Russia", + "OAP", "COAPA AIR", "COAPA", "Mexico", + "OAR", "ONE AIR", "BOSS AIR", "Spain", + "OAV", "Omni - Aviacao e Tecnologia", "OMNI", "Portugal", + "OAW", "Helvetic Airways", "HELVETIC", "Switzerland", + "OAX", "Operational Aviation Services", "", "Australia", + "OBA", "Aerobanana", "AEROBANANA", "Mexico", + "OBK", "Amako Airlines", "AMAKO AIR", "Nigeria", + "OCA", "Aserca Airlines", "AROSCA", "Venezuela", + "OCE", "Heliocean", "HELIOCEAN", "France", + "OCN", "Eurowings Discover", "OCEAN", "Germany", + "OCO", "Ostend Air College", "AIR COLLEGE", "Belgium", + "OCS", "Ocean Sky UK", "OCEANSKY", "United Kingdom", + "ODI", "Jonsson H Air Taxi", "ODINN", "Iceland", + "ODM", "Pan African Airways", "", "Kenya", + "ODS", "Odessa Airlines", "ODESSA AIR", "Ukraine", + "ODY", "Odyssey International", "ODYSSEY", "Canada", + "OEA", "Orient Thai Airlines", "ORIENT THAI", "Thailand", + "OEC", "Christophorus Flugrettungsverein", "CHRISTOPHORUS", "Austria", + "OED", "Orion Air Charter", "ORION CHARTER", "South Africa", + "OES", "ART Aviation", "ART AUSTRIA", "Austria", + "OFA", "Helitaxi Ofavi", "OFAVI", "Mexico", + "OFF", "Challenge International Airlines", "CHALLENGE AIR", "United States", + "OGI", "Aerogisa", "AEROGISA", "Mexico", + "OGJ", "Bakoji Airlines Services", "BAKO AIR", "Nigeria", + "OGN", "Origin Pacific Airways", "ORIGIN", "New Zealand", + "OHY", "Onur Air", "ONUR AIR", "Turkey", + "OIC", "Iwamoto Crane Co Ltd", "", "Japan", + "OIX", "Orion-x", "ORIONIX", "Russia", + "OJY", "Florida Air", "OHJAY", "United States", + "OKA", "Okay Airways", "OKAYJET", "China", + "OKJ", "Okada Airlines", "OKADA AIR", "Nigeria", + "OKL", "Oklahoma Department of Public Safety", "OKLAHOMA", "United States", + "OKP", "Okapi Airways", "OKAPI", "Democratic Republic of Congo", + "OKS", "Slok Air Gambia", "SLOK GAMBIA", "Gambia", + "OKT", "Soko Aviation", "SOKO AIR", "Spain", + "OLA", "Overland Airways", "OVERLAND", "Nigeria", + "OLC", "Solar Cargo", "SOLARCARGO", "Venezuela", + "OLE", "Operadora de Lineas Ejecutivas", "OPERADORA", "Mexico", + "OLO", "Soloflight", "SOLO", "United Kingdom", + "OLR", "Colaéreos", "COLAEREOS", "Ecuador", + "OLT", "OLT Express Germany", "OLTRA", "Germany", + "OLV", "Aerolíneas Olve", "OLVE", "Mexico", + "OLX", "Olimex Aerotaxi", "OLIMEX", "Czech Republic", + "OLY", "Olympic Aviation", "OLAVIA", "Greece", + "OMA", "Oman Air", "OMAN AIR", "Oman", + "OMD", "Nomadic Aviation Group LLC", "NOMADIC", "United States", + "OMF", "Omniflys", "OMNIFLYS", "Mexico", + "OMG", "Aeromega", "OMEGA", "United Kingdom", + "OML", "Organizacoes Mambra", "MAMBRA", "Angola", + "OMN", "Servicios Aereos Ominia", "SERVIOMNIA", "Mexico", + "OMR", "Minair", "ORMINE", "Central African Republic", + "OMS", "SalamAir", "MAZOON", "Oman", + "ONE", "Avianca Brasil", "OCEAN AIR", "Brazil", + "ONG", "Sonnig SA", "SONNIG", "Switzerland", + "ONI", "OMNI AVIATION TRAINING CENTER", "OMNI TRAINING", "Portugal", + "ONM", "Presidencia de La Republica de Guinea Ecuatorial", "", "Equatorial Guinea", + "ONS", "One Airlines", "AIR DREAMS", "Chile", + "ONT", "Air Ontario", "ONTARIO", "Canada", + "OOT", "Out Of The Blue Air Safaris", "OOTBAS", "South Africa", + "OPA", "Opal Air", "", "Australia", + "OPC", "Krystel Air Charter", "OPTIC", "United Kingdom", + "OPS", "Jet-Ops", "OPS-JET", "United Arab Emirates", + "OPT", "Flight Options", "OPTIONS", "United States", + "OPV", "Operadora de Veulos Ejectutivos", "OPERADORA DE VUELOS", "Mexico", + "ORA", "Long Island Airlines", "LONG ISLAND", "United States", + "ORB", "Orenburg Airlines", "ORENBURG", "Russia", + "ORD", "Orange Air Services", "ORANGE SERVICES", "Sierra Leone", + "ORE", "Orange Aviation", "ORANGE AVIATION", "Israel", + "ORF", "Oman Royal Flight", "OMAN", "Oman", + "ORJ", "Orange Air Sierra Leone", "ORANGE SIERRA", "Sierra Leone", + "ORK", "Orca Air", "ORCA TAXI", "Egypt", + "ORL", "On Air Limited", "ON AIR", "Canada", + "ORM", "Orel State Air Enterprise", "ORPRISE", "Russia", + "ORN", "Orange Air", "ORANGE JET", "United States", + "ORO", "C N Air", "CAPRI", "Spain", + "ORP", "Aerocorp", "CORPSA", "Mexico", + "ORR", "Operadora Turistica Aurora", "TURISTICA AURORA", "Mexico", + "ORX", "Orbit Express Airlines", "OREX", "Turkey", + "ORZ", "Zorex", "ZOREX", "Spain", + "OSA", "Open Sky Aviation", "", "Lebanon", + "OSH", "Osh Avia", "OSH AVIA", "Kyrgyzstan", + "OSL", "Sosoliso Airlines", "SOSOLISO", "Nigeria", + "OSN", "Aerosan", "AEROSAN", "Mexico", + "OSO", "Aviapartner Limited Company", "", "Russia", + "OSS", "Servicios Aéreos Noticiosos", "NOTICIOSOS", "Mexico", + "OST", "Airline Alania", "ALANIA", "Russia", + "OSU", "Ohio State University", "SCARLET", "United States", + "OSW", "JetAfrica Eswatini", "BEVO", "Eswatini", + "OSY", "Open Skies Consultative Commission", "OPEN SKIES", "United States", + "OTA", "Business Aviators", "OUTLAW", "United States", + "OTG", "One Two Go Airlines", "THAI EXPRESS", "Thailand", + "OTL", "South Airlines", "SOUTHLINE", "Ukraine", + "OTM", "Onetime Airlines Zambia", "ZEDTIME", "Zambia", + "OTN", "LASTP", "LASTP", "São Tomé and Príncipe", + "OTP", "Operadora de Transportes Aéreos", "OPERADORA AEREO", "Mexico", + "OTR", "Orient Airlines", "ORIENTROC", "Sudan", + "OUF", "Beijing Eofa International Jet", "ELEMENT", "China", + "OVA", "Aero Nova", "AERONOVA", "Spain", + "OVC", "Aerovic", "", "Ecuador", + "OVE", "Aeromover", "AEROMOVER", "Mexico", + "OVI", "Aerovías Ejecutivas", "VIAS EJECUTIVAS", "Mexico", + "OVV", "Orient Air", "ORIENTSYR", "Syrian Arab Republic", + "OWE", "Owenair", "OWENAIR", "South Africa", + "OWL", "Miami Valley Aviation", "NIGHT OWL", "United States", + "OWN", "Aero Owen", "AERO OWEN", "Mexico", + "OXE", "Oxaero", "OXOE", "United Kingdom", + "OXO", "Million Air", "MILL AIR", "United States", + "OYE", "Koda International", "KODA AIR", "Nigeria", + "OZJ", "OzJet", "AUSJET", "Australia", + "OZU", "Hozu-Avia", "HOZAVIA", "Kazakhstan", + "OZW", "Virgin Australia Regional Airlines", "VELOCITY", "Australia", + "PAA", "Pan American World Airways", "CLIPPER", "United States", + "PAB", "Pacific Air Boats", "AIR BOATS", "Canada", + "PAC", "Polar Air Cargo", "POLAR", "United States", + "PAD", "Professional Express Courier Service", "AIR PROFESSIONAL", "United States", + "PAE", "Paisajes Españoles", "PAISAJES", "Spain", + "PAG", "Perimeter Aviation", "PERIMETER", "Canada", + "PAH", "Panorama Air Tour", "LANI", "United States", + "PAI", "Paradise Airways", "SEA RAY", "United States", + "PAJ", "Aliparma", "ALIPARMA", "Italy", + "PAK", "Pacific Alaska Airlines", "PACIFIC ALASKA", "United States", + "PAL", "Philippine Airlines", "PHILIPPINE", "Philippines", + "PAM", "Phoenix Air", "PHOENIX", "Germany", + "PAO", "Polynesian Airlines", "POLYNESIAN", "Samoa", + "PAP", "Langtry Flying Group", "PROFLIGHT", "United Kingdom", + "PAQ", "Pacific Air Express", "SOLPAC", "Solomon Islands", + "PAR", "Pacific Rim Airways", "PACRIM", "Australia", + "PAS", "Pelita Air Service", "PELITA", "Indonesia", + "PAT", "Priority Air Transport", "PAT", "United States", + "PAV", "VIP Avia", "NICOL", "Kazakhstan", + "PAW", "Pointair Burkina", "POINTAIR BURKINA", "Burkina Faso", + "PAX", "Pan Air", "PANNEX", "United States", + "PAZ", "Point Afrique Niger", "POINTAIR NIGER", "Niger", + "PBA", "PB Air", "PEEBEE AIR", "Thailand", + "PBB", "Polizeihubschrauberstaffel Brandenburg", "ADEBAR", "Germany", + "PBN", "Pacific Blue", "BLUEBIRD", "New Zealand", + "PBR", "Fast Air", "POLAR BEAR", "Canada", + "PBT", "Air Parabet", "PARABET", "Bangladesh", + "PBU", "Air Burundi", "AIR-BURUNDI", "Burundi", + "PBW", "Polizeihubschrauberstaffel Baden-Württemberg", "BUSSARD", "Germany", + "PBY", "Pearl Air Services", "PEARL SERVICES", "Uganda", + "PCA", "Penya De L'Aire", "PENA DEL AIRE", "Spain", + "PCC", "Perforadora Central", "PERFORADORA CENTRAL", "Mexico", + "PCE", "Pace Airlines", "PACE", "United States", + "PCG", "Aeropostal Cargo de Mexico", "POSTAL CARGO", "Mexico", + "PCH", "Pilatus Flugzeugwerke", "PILATUS WINGS", "Switzerland", + "PCJ", "Pacific Jet", "PACIFIC JET", "United States", + "PCK", "Air Pack Express", "AIRPACK EXPRESS", "Spain", + "PCL", "Pinnacle Air Group", "PINNACLE GROUP", "United States", + "PCM", "Westair Industries", "PAC VALLEY", "United States", + "PCN", "Princeton Aviation Corporation", "PRINCETON", "United States", + "PCO", "Pacific Coastal Airlines", "PASCO", "Canada", + "PCP", "Aerolínea Principal Chile", "PRINCIPAL", "Chile", + "PCR", "PAC Air", "PACAIR", "United States", + "PCS", "Air Palace", "AIR PALACE", "Mexico", + "PCV", "Pacific Aviation (Australia)", "PACAV", "Australia", + "PCW", "Trans-Pacific Orient Airways", "PACIFIC ORIENT", "Philippines", + "PCX", "Pacific Aviation (United States)", "", "United States", + "PDA", "Podilia-Avia", "PODILIA", "Ukraine", + "PDC", "Potomac Air", "DISTRICT", "United States", + "PDD", "Phillips Alaska", "PADA", "United States", + "PDF", "Pelican Air Services", "PELICAN AIRWAYS", "South Africa", + "PDG", "PLM Dollar Group", "OSPREY", "United Kingdom", + "PDI", "Paradise Island Airways", "PARADISE ISLAND", "United States", + "PDQ", "PDQ Air Charter", "DISPATCH", "United States", + "PDR", "COMAV", "SPEEDSTER", "Namibia", + "PDT", "Piedmont Airlines", "PIEDMONT", "United States", + "PDV", "Elicar", "ELICAR", "Italy", + "PDY", "Pen-Avia", "PENDLEY", "United Kingdom", + "PEA", "Pan Europeenne Air Service", "", "France", + "PEB", "Benders Air", "PALEMA", "Sweden", + "PEC", "Pacific East Asia Cargo Airlines", "PAC-EAST CARGO", "Philippines", + "PEI", "Panamedia", "PANAMEDIA", "Spain", + "PEL", "Aeropelican Air Services", "PELICAN", "Australia", + "PEM", "Pem-Air", "PEM-AIR", "Canada", + "PEN", "Peninsula Airways", "PENINSULA", "United States", + "PEO", "Petro Air", "PETRO AIR", "Libya", + "PER", "Pioneers Limited", "", "Pakistan", + "PET", "Aerotransporte Petrolero", "AEROPETRO", "Colombia", + "PEV", "Peoples Vienna Line", "PEOPLES", "Austria", + "PEX", "Pelican Express", "PELICAN EXPRESS", "United States", + "PFA", "Pacific Flight Services", "PACIFIC SING", "Singapore", + "PFI", "Aerolíneas Chihuahua", "PACIFICO CHIHUAHUA", "Mexico", + "PFN", "Pan African Air Services", "PANAFRICAN", "Sierra Leone", + "PFR", "Pacificair Airlines", "PACIFIC WEST", "United States", + "PFS", "Prairie Flying Service", "PRAIRIE", "United States", + "PFT", "Air Cargo Express International", "PROFREIGHT", "United States", + "PFY", "Pel-Air Aviation", "PELFLIGHT", "Australia", + "PFZ", "Proflight Zambia", "PROFLIGHT-ZAMBIA", "Zambia", + "PGA", "Portugalia", "PORTUGALIA", "Portugal", + "PGF", "Paragon Global Flight Support", "", "United Kingdom", + "PGH", "Pulkovo Aircraft Services", "", "Russia", + "PGI", "Panagra Airways", "PANAGRA", "United States", + "PGL", "Premiair Aviation Services", "PREMIERE", "United Kingdom", + "PGP", "Perm Airlines", "PERM AIR", "Russia", + "PGS", "Tauranga Aer Club", "", "New Zealand", + "PGT", "Pegasus Airlines", "SUNTURK", "Turkey", + "PGX", "Paragon Air Express", "PARAGON EXPRESS", "United States", + "PHA", "Phoenix Air Group", "GRAY BIRD", "United States", + "PHB", "Phoebus Apollo Aviation", "PHOEBUS", "South Africa", + "PHC", "Petroleum Helicopters de Colombia", "HELICOPTERS", "Colombia", + "PHD", "Duncan Aviation", "PANHANDLE", "United States", + "PHE", "Pawan Hans", "PAWAN HANS", "India", + "PHG", "Phoenix Aviation", "PHOENIX GROUP", "Kyrgyzstan", + "PHH", "Polizeihubschrauberstaffel Hessen", "IBIS", "Germany", + "PHI", "Philips Aviation Services", "PHILAIR", "Netherlands", + "PHL", "Phillips Michigan City Flying Service", "PHILLIPS", "United States", + "PHM", "Petroleum Helicopters", "PETROLEUM", "United States", + "PHN", "Phoenix Air Lines", "PHOENIX BRASIL", "Brazil", + "PHO", "L&L Flygbildteknik", "PHOTOFLIGHT", "Sweden", + "PHR", "Al Farana Airline", "PHARAOH", "Egypt", + "PHS", "Polizeihubschrauberstaffel Sachsen", "PASSAT", "Germany", + "PHT", "Pan Havacilik Ve Ticaret", "PANANK", "Turkey", + "PHU", "Pannon Air Service", "PANNON", "Hungary", + "PHV", "Phenix Aviation", "NEW BIRD", "France", + "PHY", "Phoenix Avia", "PHOENIX ARMENIA", "Armenia", + "PIA", "Pakistan International Airlines", "PAKISTAN", "Pakistan", + "PIC", "Pacific Airlines", "PACIFIC AIRLINES", "Vietnam", + "PIE", "Air South West", "PIRATE", "United Kingdom", + "PIF", "Aeroservicios California Pacifico", "AEROCALPA", "Mexico", + "PIK", "Polizeihubschrauberstaffel Sachsen-Anhalt", "POLICE IKARUS", "Germany", + "PIL", "Canada Jet Charters", "PINNACLE", "Canada", + "PIM", "Pinframat", "PINFRAMAT", "Angola", + "PIN", "Pacific International Airlines", "ROAD RUNNERS", "United States", + "PIO", "Pioneer Airlines", "PIONEER", "United States", + "PIP", "Pilot Flight Academy", "PILOT", "Norway", + "PIR", "Pamir Airways", "PAMIR", "Afghanistan", + "PIU", "43 Air School", "PRIMA", "South Africa", + "PIV", "Sokol", "AEROSOKOL", "Russia", + "PIX", "Metropix UK", "METROPIX", "United Kingdom", + "PJA", "Private Jet Management", "PRIVATE FLIGHT", "United States", + "PJE", "Private Jet Expeditions", "PEE JAY", "United States", + "PJP", "Princely Jets", "PRINCELY JETS", "Pakistan", + "PJS", "Jet Aviation", "JETAVIATION", "Switzerland", + "PKA", "AST Pakistan Airways", "PAKISTAN AIRWAY", "Pakistan", + "PKP", "Mountain Air Express", "PIKES PEAK", "United States", + "PKR", "Pakker Avio", "PAKKER AVIO", "Estonia", + "PKW", "Pak West Airlines", "PLATINUM WEST", "United States", + "PKZ", "Prime Aviation", "PRAVI", "Kazakhstan", + "PLA", "Polynesian Air-Ways", "POLYAIR", "United States", + "PLB", "Polynesian Blue", "POLYBLUE", "New Zealand", + "PLC", "Police Aviation Services", "SPECIAL", "United Kingdom", + "PLF", "Polish Air Force", "POLISH AIRFORCE", "Poland", + "PLG", "MBK-S", "PILGRIM", "Russia", + "PLL", "Air Pal", "AIRPAL", "Spain", + "PLM", "Air Pullmantur", "PULLMANTUR", "Spain", + "PLN", "Planar", "PLANAR", "Angola", + "PLR", "Northwestern Air", "POLARIS", "Canada", + "PLS", "Aeroflot-Plus", "AEROPLUS", "Russia", + "PLT", "South Carolina Aeronautics Commission", "PALMETTO", "United States", + "PLU", "Pilatus PC-12 Center De Mexico", "PILATUS MEXICO", "Mexico", + "PLX", "Pool Aviation", "POOLEX", "United Kingdom", + "PLY", "Puma Linhas Aéreas", "PUMA BRASIL", "Brazil", + "PLZ", "Planet Airways", "PLANET", "United States", + "PMA", "Pan Malaysian Air Transport", "PAN MALAYSIA", "Malaysia", + "PMC", "Primas Courier", "PRIMAC", "United States", + "PME", "Premiair Fliyng Club", "ADUR", "United Kingdom", + "PMI", "Primero Transportes Aereos", "AEROEPRIM", "Mexico", + "PMM", "Paradigm Air Operators", "PARADIGM", "United States", + "PMO", "Polar Airlines de Mexico", "POLAR MEXICO", "Mexico", + "PMR", "Servicios Aéreos Premier", "SERVICIOS PREMIER", "Mexico", + "PMS", "Planemaster Services", "PLANEMASTER", "United States", + "PMT", "PMTair", "MULTITRADE", "Cambodia", + "PMU", "Premium Aviation", "PREMIUM", "Germany", + "PMV", "Polizeihubschrauberstaffel Mecklenburg-Vorpommern", "POLICE MERLIN", "Germany", + "PMW", "Paramount Airways", "PARAWAY", "India", + "PMX", "Petroleos Mexicanos", "PEMEX", "Mexico", + "PMY", "Phetchabun Airline", "PHETCHABUN AIR", "Thailand", + "PNA", "Universal Airlines", "PACIFIC NORTHERN", "United States", + "PNC", "Pan-Air", "PANAIRSA", "Mexico", + "PND", "Pond Air Express", "POND AIR", "United States", + "PNE", "Peninter Aérea", "PENINTER", "Mexico", + "PNG", "Puerto Rico National Guard", "", "United States", + "PNH", "Panh", "KUBAN LIK", "Russia", + "PNK", "Air Pink", "AIRPINK", "Serbia", + "PNL", "Aero Personal", "AEROPERSONAL", "Mexico", + "PNM", "Panorama", "PANORAMA", "Spain", + "PNP", "Pineapple Air", "PINEAPPLE AIR", "Bahamas", + "PNR", "PAN Air", "SKYJET", "Spain", + "PNS", "Survey Udara (Penas)", "PENAS", "Indonesia", + "PNT", "Balmoral Central Contracts", "PORTNET", "South Africa", + "PNU", "Aero Servicios Platinum", "AERO PLATINUM", "Mexico", + "PNW", "Palestinian Airlines", "PALESTINIAN", "Egypt", + "PNX", "AIS Airlines", "SPINNER", "Netherlands", + "PNY", "Polish Navy", "POLISH NAVY", "Poland", + "POA", "Portuguese Army", "PORTUGUESE ARMY", "Portugal", + "POB", "Servicios Aéreos Poblanos", "POBLANOS", "Mexico", + "POC", "Pocono Air Lines", "POCONO", "United States", + "POE", "Porter Airlines", "PORTER", "Canada", + "POF", "Police Aux Frontières", "AIRPOL", "France", + "POI", "BGB Air", "BOJBAN", "Kazakhstan", + "POL", "Rikspolisstyrelsen", "", "Sweden", + "PON", "Portuguese Navy", "PORTUGUESE NAVY", "Portugal", + "POR", "Porteadora De Cosola", "PORTEADORA", "Mexico", + "POT", "Polet Airlines", "POLET", "Russia", + "POV", "Meridian", "AIR POLTAVA", "Ukraine", + "POW", "Hagondale Limited", "AIRNET", "United Kingdom", + "POY", "Apoyo Aéreo", "APOYO AEREO", "Mexico", + "PPA", "Propheter Aviation", "AIR PROP", "United States", + "PPC", "Palau Asia Pacific Airlines", "PALAU ASIAPAC", "Palau", + "PPG", "Phoenix Air Transport", "PAPAGO", "United States", + "PPH", "Polizeihubschrauberstaffel Niedersachsen", "POLICE PHOENIX", "Germany", + "PPK", "Ramp 66", "PELICAN", "United States", + "PPM", "Pacific Pearl Airways", "PACIFIC PEARL", "Philippines", + "PPQ", "Personas Y Pasquetes Por Air", "PERSONSPAQ", "Mexico", + "PPS", "Butte Aviation", "PIPESTONE", "United States", + "PPW", "Royal Phnom Penh Airways", "PHNOM-PENH AIR", "Cambodia", + "PQA", "Pacific Coast Airlines", "SAGE BRUSH", "United States", + "PRA", "Pars Aviation Service", "PARSAVIA", "Iran", + "PRC", "Pacific Air Charter", "PACIFIC CHARTER", "United States", + "PRD", "Presidential Aviation", "PRESIDENTIAL", "United States", + "PRE", "Precision Airlines", "PRECISION", "United States", + "PRF", "Precision Air", "PRECISION AIR", "Tanzania", + "PRG", "Empresa Aero-Servicios Parrague", "ASPAR", "Chile", + "PRH", "Pro Air", "PROHAWK", "United States", + "PRI", "Primera Air Scandinavia", "PRIMERA", "Denmark", + "PRL", "Pearl Air", "PEARL LINE", "Pakistan", + "PRM", "Prime Airlines", "PRIME AIR", "United States", + "PRN", "Pirinair Express", "PRINAIR EXPRESS", "Spain", + "PRO", "Propair", "PROPAIR", "Canada", + "PRP", "PRT Aviation", "PRONTO", "Spain", + "PRR", "Paramount Airlines", "PARAMOUNT", "Sierra Leone", + "PRT", "Atlantic Coast Jet", "PATRIOT", "United States", + "PRV", "Provincial Express", "PROVINCIAL", "Canada", + "PRW", "Primera Air Nordic", "JETBIRD", "Latvia", + "PRX", "VIP Avia", "PAREX", "Latvia", + "PRY", "Priority Air Charter", "PRIORITY AIR", "United States", + "PSA", "Pacific Island Aviation", "PACIFIC ISLE", "United States", + "PSC", "Pascan Aviation", "PASCAN", "Canada", + "PSD", "President Airlines", "", "Cambodia", + "PSE", "Aeroservicio Sipse", "SIPSE", "Mexico", + "PSF", "Plymouth School of Flying", "LIZARD", "United Kingdom", + "PSG", "Aviones Para Servirle", "SERVIAVIONES", "Mexico", + "PSH", "Red Aviation", "PASSION", "United Kingdom", + "PSL", "Aeroservicios Corporativos De San Luis", "CORSAN", "Mexico", + "PSN", "Potosina Del Aire", "POTOSINA", "Mexico", + "PSO", "Aerotaxis Pegaso", "AEROPEGASO", "Mexico", + "PSP", "Publiservicios Aéreos", "PUBLISERVICIOS", "Mexico", + "PSR", "Polestar Aviation", "POLESTAR", "United Kingdom", + "PSS", "TSSKB-Progress", "PROGRESS", "Russia", + "PST", "Parsa", "TURISMO REGIONAL", "Panama", + "PSV", "Servicios Aéreos Profesionales", "PROSERVICIOS", "Dominican Republic", + "PSW", "Pskovavia", "PSKOVAVIA", "Russia", + "PSZ", "Pro Air Service", "POP-AIR", "United States", + "PTA", "Ptarmigan Airways", "PTARMIGAN", "Canada", + "PTB", "Passaredo Transportes Aéreos", "PASSAREDO", "Brazil", + "PTC", "Patria Cargas Aéreas", "PATRIA", "Argentina", + "PTD", "Aero Servicio Pity", "PITY", "Mexico", + "PTE", "Aero Copter", "AERO-COP", "Mexico", + "PTH", "Proteus Helicopteres", "PROTEUS", "France", + "PTI", "Privatair", "PRIVATAIR", "Switzerland", + "PTK", "Petropavlovsk-Kamchatsk Air Enterprise", "PETROKAM", "Russia", + "PTL", "Providence Airline", "PLANTATION", "United States", + "PTM", "Southeastern Airways", "POSTMAN", "United States", + "PTN", "Pantanal Linhas Aéreas", "PANTANAL", "Brazil", + "PTO", "North West Geomatics", "PHOTO", "Canada", + "PTP", "Palau Trans Pacific Airlines", "TRANS PACIFIC", "Palau", + "PTQ", "Port Townsend Airways", "TOWNSEND", "United States", + "PTR", "Nova Scotia Department of Lands and Forests", "PATROL", "Canada", + "PTS", "Points of Call Airlines", "POINTSCALL", "Canada", + "PTT", "Promotora Industria Totolapa", "TOTOLAPA", "Mexico", + "PTV", "Puntavia Air Services", "PUNTAVIA", "Djibouti", + "PTY", "Petty Transport", "PETTY", "United States", + "PUA", "PLUNA", "PLUNA", "Uruguay", + "PUE", "Aeropuelche", "PUELCHE", "Chile", + "PUR", "Spurwing Airlines", "SPURWING", "South Africa", + "PUV", "Publivoo", "PUBLIVOO", "Portugal", + "PVA", "Aerotransportes Privados", "TRANSPRIVADO", "Mexico", + "PVG", "Privilege Style Líneas Aéreas", "PRIVILEGE", "Spain", + "PVI", "Panavia", "", "Panama", + "PVL", "Professione VOlare", "VOLARE", "Italy", + "PVN", "Peruvian Airlines", "", "Peru", + "PVO", "Bearing Supplies Limited", "PROVOST", "United Kingdom", + "PVU", "Peau Vavau", "PEAU", "Tonga", + "PVV", "Fly Pro", "Sunday", "Moldova", + "PWA", "Priester Aviation", "PRIESTER", "United States", + "PWC", "Pratt and Whitney Canada", "PRATT", "Canada", + "PWD", "CARIBAIR", "CARIBAIR", "Dominican Republic", + "PWF", "Private Wings Flugcharter", "PRIVATE WINGS", "Germany", + "PWL", "Powell Air", "POWELL AIR", "Canada", + "PXA", "Pecotox Air", "PECOTOX", "Moldova", + "PXG", "Aitheras Aviation Group", "", "United States", + "PXP", "Pacific Air Transport", "PAK EXPRESS", "United States", + "PXR", "Pixair Survey", "PIXAIR", "France", + "PXT", "Pacific Coast Jet", "PACK COAST", "United States", + "PXX", "Aroostook Aviation", "PINE STATE", "United States", + "PYC", "Aeropycsa", "AEROPYCSA", "Mexico", + "PYN", "Haverfordwest Air Charter Services", "POYSTON", "United Kingdom", + "PYR", "Pyramid Air Lines", "PYAIR", "Egypt", + "PYZ", "Players Air", "PLAYERS AIR", "United States", + "PZA", "Aéreo Taxi Paraza", "AEREO PARAZA", "Mexico", + "PZR", "Sky Trek International Airlines", "PHAZER", "United States", + "PZY", "Zapolyarye Airline Company", "ZAPOLYARYE", "Russia", + "QAC", "Qatar Air Cargo", "QATAR CARGO", "Qatar", + "QAF", "Qatar Amiri Flight", "AMIRI", "Qatar", + "QAH", "Quick Airways Holland", "QUICK", "Netherlands", + "QAI", "Conquest Air", "CHICKPEA", "United States", + "QAJ", "Quick Air Jet Charter", "DAGOBERT", "Germany", + "QAQ", "Qurinea Air Service", "QURINEA AIR", "Libya", + "QAS", "Quisqueya Airlines", "QUISQUEYA", "Haiti", + "QCC", "Qwest Commuter Corporation", "QWEST AIR", "United States", + "QCL", "Air Class Líneas Aéreas", "ACLA", "Uruguay", + "QEA", "Aviation Consultancy Office", "", "Australia", + "QFA", "Qantas", "QANTAS", "Australia", + "QGA", "Windrose Air", "QUADRIGA", "Germany", + "QID", "USAF 100th Air Refueling Wing", "QUID", "United States", + "QJE", "QantasLink", "QJET", "Australia", + "QKC", "Aero Taxi Aviation", "QUAKER CITY", "United States", + "QLA", "Aviation Quebec Labrador", "QUEBEC LABRADOR", "Canada", + "QLK", "QantasLink", "QLINK", "Australia", + "QNA", "Queen Air", "QUEEN AIR", "Dominican Republic", + "QNK", "Kabo Air", "KABO", "Nigeria", + "QNT", "Qanot Sharq", "QANAT SHARQ", "Uzbekistan", + "QNZ", "Jetconnect", "QANTAS JETCONNECT", "New Zealand", + "QQE", "Qatar Executive", "", "Qatar", + "QSC", "African Safari Airways", "ZEBRA", "Kenya", + "QSM", "Qeshm Air", "QESHM AIR", "Iran", + "QSR", "SR Jet", "SPARKLE ROLL", "China", + "QTR", "Qatar Airways", "QATARI", "Qatar", + "QTX", "Quantex Environmental", "AIR QUANTEX", "Canada", + "QUE", "Quebec Government Air Service", "QUEBEC", "Canada", + "QUI", "Aero Quimmco", "QUIMMCO", "Mexico", + "QVR", "Kvadro Aero", "PEGASO", "Kyrgyzstan", + "QWA", "Qwestair", "", "Australia", + "QWL", "Qwila Air", "Q-CHARTER", "South Africa", + "QXE", "Horizon Air", "HORIZON AIR", "United States", + "RAA", "Rynes Aviation", "RYNES AVIATION", "United States", + "RAC", "Icar Air", "TUZLA AIR", "Bosnia and Herzegovina", + "RAD", "Alada", "AIR ALADA", "Angola", + "RAE", "Régional", "REGIONAL EUROPE", "France", + "RAF", "Farnas Aviation Services", "FARNAS", "Sudan", + "RAG", "Regio Air", "GERMAN LINK", "Germany", + "RAH", "Regent Air", "REGENT", "Canada", + "RAI", "Aerotur Air", "DIASA", "Kazakhstan", + "RAJ", "Raji Airlines", "RAJI", "Pakistan", + "RAK", "Riga Airclub", "SPORT CLUB", "Latvia", + "RAL", "Roswell Airlines", "ROSWELL", "United States", + "RAM", "Royal Air Maroc", "ROYALAIR MAROC", "Morocco", + "RAN", "Renan", "RENAN", "Moldova", + "RAP", "Air Center Helicopters", "RAPTOR", "United States", + "RAQ", "Rath Aviation", "RATH AVIATION", "Austria", + "RAR", "Air Rarotonga", "AIR RAROTONGA", "Cook Islands", + "RAS", "Jim Ratliff Air Service", "SHANHIL", "United States", + "RAU", "Uganda Royal Airways", "UGANDA ROYAL", "Uganda", + "RAV", "Reed Aviation", "REED AVIATION", "United Kingdom", + "RAX", "Royal Air Freight", "AIR ROYAL", "United States", + "RAZ", "Rijnmond Air Services", "RIJNMOND", "Netherlands", + "RBA", "Royal Brunei Airlines", "BRUNEI", "Brunei", + "RBB", "Rabbit-Air", "RABBIT", "Switzerland", + "RBC", "Republicair", "REPUBLICAIR", "Mexico", + "RBD", "Trans World Express", "RED BIRD", "United States", + "RBE", "Aur Rum Benin", "RUM BENIN", "Benin", + "RBG", "Air Arabia Egypt", "ARABIA EGYPT", "Egypt", + "RBH", "Regal Bahamas International Airways", "CALYPSO", "Bahamas", + "RBJ", "Aeroserivios Del Bajio", "AEROBAJIO", "Mexico", + "RBN", "Red Baron Aviation", "RED BARON", "United States", + "RBR", "Siam Airnet", "SIAM AIRNET", "Thailand", + "RBT", "Robinton Aero", "ROBIN", "Dominican Republic", + "RBU", "Airbus France", "AIRBUS FRANCE", "France", + "RBV", "Air Roberval", "AIR ROBERVAL", "Canada", + "RBW", "Shandong Airlines Rainbow Jet", "CAI HONG", "China", + "RBY", "Vision Airlines", "RUBY", "United States", + "RCA", "Richland Aviation", "RICHLAND", "United States", + "RCB", "Real Aero Club De Baleares", "BALEARES", "Spain", + "RCD", "Real Aeroclub De Tenerife", "AEROCLUB", "Spain", + "RCE", "Aerocer", "AEROCER", "Mexico", + "RCF", "Aeroflot-Cargo", "AEROFLOT-CARGO", "Russia", + "RCG", "Royal Air Cargo", "ROYAL CARGO", "South Africa", + "RCH", "Air Mobility Command", "REACH", "United States", + "RCI", "Air Cassai", "AIR CASSAI", "Angola", + "RCJ", "Raytheon Corporate Jets", "NEWPIN", "United Kingdom", + "RCK", "FaroeJet", "ROCKROSE", "Faroe Islands", + "RCO", "Aero Renta De Coahuila", "AEROCOAHUILA", "Mexico", + "RCP", "Aerocorp", "AEROCORPSA", "Mexico", + "RCQ", "Aerolíneas Regionales", "REGIONAL CARGO", "Mexico", + "RCT", "Ryan Air Services", "ARCTIC TRANSPORT", "United States", + "RCU", "Atlantic S.L.", "AIR COURIER", "Spain", + "RCX", "Air Service Center", "SERVICE CENTER", "Italy", + "RCY", "Package Express", "RACE CITY", "United States", + "RDE", "II Lione Alato Arl", "FLIGHT RED", "United Kingdom", + "RDK", "Irish Air Transport", "IRISH TRANS", "Ireland", + "RDL", "Roadair Lines", "ROADAIR", "Canada", + "RDM", "Air Ada", "AIR ADA", "Mauritania", + "RDP", "Eurojet Romania", "JET-ARROW", "Romania", + "RDR", "Goodridge (UK) Limited", "RED STAR", "United Kingdom", + "RDS", "Rhoades Aviation", "RHOADES EXPRESS", "United States", + "RDV", "Red Sea Aviation", "RED AVIATION", "Egypt", + "RDW", "Valair AG (Helicoptere)", "ROADWATCH", "Switzerland", + "RDZ", "Rodze Air", "RODZE AIR", "Nigeria", + "REA", "Aer Arann", "AER ARANN", "Ireland", + "REB", "Rebus", "REBUS", "Bulgaria", + "REC", "Trans Reco", "TRANS-RECO", "Mauritania", + "RED", "International Committee of the Red Cross", "RED CROSS", "Switzerland", + "REF", "Reef Air", "REEF AIR", "New Zealand", + "REG", "Regional Air Services", "REGIONAL SERVICES", "Tanzania", + "REI", "Ray Aviation", "RAY AVIATION", "Israel", + "REJ", "SA Airlink Regional", "REGIONAL LINK", "South Africa", + "REK", "Reem Air", "REEM AIR", "Kyrgyzstan", + "REL", "Reliance Aviation", "RELIANCE AIR", "United States", + "REN", "Aero-Rent", "AERORENT", "Mexico", + "REO", "Rio Airways", "RIO", "United States", + "RER", "Servicio Aéreo Regional Regair", "REGAIR", "Ecuador", + "RES", "Australian Maritime Safety Authority", "RESCUE", "Australia", + "REU", "Air Austral", "REUNION", "France", + "REV", "RVL Group", "ENDURANCE", "United Kingdom", + "REW", "Regional Air Express", "REGIONAL WINGS", "Germany", + "REX", "Ram Air Freight", "RAM EXPRESS", "United States", + "REY", "Aero-Rey", "AEROREY", "Mexico", + "REZ", "Cal-West Aviation", "CAL AIR", "United States", + "RFA", "Raleigh Flying Service", "RALEIGH SERVICE", "United States", + "RFC", "Aero Africa", "AERO AFRICA", "Eswatini", + "RFD", "Aerotransportes Rafilher", "RAFHILER", "Mexico", + "RFF", "Russian Federation Air Force", "RUSSIAN AIRFORCE", "Russia", + "RFI", "National Air Traffic Services", "SHERLOCK", "United Kingdom", + "RFL", "Interfly", "INFLY", "Italy", + "RFR", "Royal Air Force", "RAFAIR", "United Kingdom", + "RFS", "Rossair", "", "Australia", + "RFT", "Scoala Superioara De Aviatie Civila", "ROMANIAN ACADEMY", "Romania", + "RFX", "J P Hunt Air Carriers", "REFLEX", "United States", + "RGA", "Royal Ghanaian Airlines", "ROYAL GHANA", "Ghana", + "RGC", "Servicios Aéreos Regiomontanos", "REGIOMONTANO", "Mexico", + "RGE", "Regent Airways", "REGENT", "Bangladesh", + "RGL", "Regional Air Lines", "MAROC REGIONAL", "Morocco", + "RGM", "Rangemile Limited", "RANGEMILE", "United Kingdom", + "RGN", "Cygnus Air", "CYGNUS AIR", "Spain", + "RGO", "Argo", "ARGOS", "Dominican Republic", + "RGP", "River State Government of Nigeria", "GARDEN CITY", "Nigeria", + "RGR", "Avior Regional", "AVIOR REGIONAL", "Venezuela", + "RGS", "Renown Aviation", "RENOWN", "United States", + "RGV", "RG Aviation", "", "Venezuela", + "RGY", "Regency Airlines", "REGENCY", "United States", + "RHC", "Redhill Aviation", "REDAIR", "United Kingdom", + "RHD", "Bond Air Services", "RED HEAD", "United Kingdom", + "RHL", "Air Archipels", "ARCHIPELS", "France", + "RIA", "Rich International Airways", "RICHAIR", "United States", + "RIC", "Richardson's Airway", "RICHARDSON", "United States", + "RID", "Ridder Avia", "AKRID", "Kazakhstan", + "RIF", "Aviation Ministry of the Interior of the Russian Federation", "INTERMIN AVIA", "Russian Federation", + "RIL", "Regional Air", "", "Mauritania", + "RIM", "Rimrock Airlines", "RIMROCK", "United States", + "RIO", "Rio Linhas Aéreas", "RIO", "Brazil", + "RIS", "Aeris Gestión", "AERIS", "Spain", + "RIT", "Asian Spirit", "ASIAN SPIRIT", "Philippines", + "RIU", "Riau Airlines", "RIAU AIR", "Indonesia", + "RIV", "APG Airlines", "RIVERA", "France", + "RIX", "Rectrix Aviation", "RECTRIX", "United States", + "RJA", "Royal Jordanian", "JORDANIAN", "Jordan", + "RJM", "Millen Corporation", "MILLEN", "United Kingdom", + "RJS", "Aeroservicios Jet", "ASERJET", "Mexico", + "RJT", "RA Jet Aeroservicios", "RA JET", "Mexico", + "RJZ", "Royal Jordanian Air Force", "JORDAN AIR FORCE", "Jordan", + "RKA", "Air Afrique", "AIRAFRIC", "Ivory Coast", + "RKC", "DAS Airlines", "DAS CONGO", "Democratic Republic of the Congo", + "RKH", "Royal Khmer Airlines", "KHMER AIR", "Cambodia", + "RKM", "RAK Airways", "RAKAIR", "United Arab Emirates", + "RKT", "Rotormotion", "ROCKET", "United Kingdom", + "RKW", "Rockwell Collins Avionics", "ROCKWELL", "United States", + "RLA", "Airlinair", "AIRLINAIR", "France", + "RLE", "Rico Linhas Aéreas", "RICO", "Brazil", + "RLH", "Ruili Airlines", "SENDI", "China", + "RLI", "Reliant Air", "RELIANT", "United States", + "RLL", "Air Leone", "AEROLEONE", "Sierra Leone", + "RLM", "Royal American Airways", "ROYAL AMERICAN", "United States", + "RLN", "Aero Lanka", "AERO LANKA", "Sri Lanka", + "RLR", "Business Airfreight", "RATTLER", "United States", + "RLS", "S-Air", "S-AIRLINES", "Russia", + "RLU", "Rusline", "RUSLINE AIR", "Russia", + "RLV", "Real Aviation", "REAL", "Ghana", + "RLX", "Go2Sky", "RELAX", "Slovakia", + "RLZ", "Air Alize", "ALIZE", "France", + "RMA", "Rocky Mountain Airways", "ROCKY MOUNTAIN", "United States", + "RMD", "Air Amder", "AIR AMDER", "Mauritania", + "RMF", "Royal Malaysian Air Force", "ANGKASA", "Malaysia", + "RMG", "Rumugu Air & Space Nigeria", "RUMUGU AIR", "Nigeria", + "RMI", "Point Airlines", "POINT AIRLINE", "Nigeria", + "RML", "Air Mediterranean", "HELLASMED", "Greece", + "RMO", "Arm-Aero", "ARM-AERO", "Armenia", + "RMP", "Servicios De Rampa Y Mostrador", "SERAMSA", "Mexico", + "RMS", "Tas Aviation", "TASS AIR", "United States", + "RMT", "Ram Aircraft Corporation", "RAM FLIGHT", "United States", + "RMU", "C.S.P. Societe", "AIR-MAUR", "Mauritania", + "RMV", "Romavia", "AEROMAVIA", "Romania", + "RMX", "Air Max", "AEROMAX", "Bulgaria", + "RMY", "Raya Airways", "TRANSMILE", "Malaysia", + "RNA", "Nepal Airlines", "ROYAL NEPAL", "Nepal", + "RNB", "Rosneft-Baltika", "ROSBALT", "Russia", + "RND", "Rutland Aviation", "RUTLAND", "United Kingdom", + "RNE", "Air Salone", "AIR SALONE", "Sierra Leone", + "RNG", "Orange Aircraft Leasing", "ORANGE", "Netherlands", + "RNM", "Aeronem Air Cargo", "AEROMNEM", "Ecuador", + "RNR", "Air Cargo Masters", "RUNNER", "United States", + "RNS", "Ronso", "RONSO", "Mexico", + "ROC", "Rocky Mountain Airlines", "", "Canada", + "ROD", "Aerodan", "AERODAN", "Mexico", + "ROE", "Aeroeste", "ESTE-BOLIVIA", "Bolivia", + "ROF", "Romanian Air Force", "ROMAF", "Romania", + "ROG", "Fundació Rego", "REGO", "Spain", + "ROH", "Aero Gen", "AEROGEN", "Mexico", + "ROI", "Avior Airlines", "AVIOR", "Venezuela", + "ROJ", "Royal Jet", "ROYALJET", "United Arab Emirates", + "RON", "Our Airline", "OUR AIRLINE", "Nauru", + "ROO", "Aero Roa", "AERO ROA", "Mexico", + "ROP", "Royal Oman Police", "", "Oman", + "ROR", "Roraima Airways", "RORAIMA", "Guyana", + "ROS", "Rossair Europe", "CATCHER", "Netherlands", + "ROT", "Tarom", "TAROM", "Romania", + "ROU", "Air Canada Rouge", "ROUGE", "Canada", + "ROV", "Rover Airways International", "ROVERAIR", "United States", + "ROW", "Nor Aviation", "ROTORWING", "Norway", + "ROX", "Roblex Aviation", "ROBLEX", "United States", + "ROY", "Conifair Aviation", "", "Canada", + "RPA", "Republic Airways", "BRICKYARD", "United States", + "RPB", "AeroRepública", "AEROREPUBLICA", "Colombia", + "RPC", "Aerolíneas Del Pacífico", "AEROPACSA", "Ecuador", + "RPH", "Republic Express Airlines", "PUBLIC EXPRESS", "Indonesia", + "RPK", "Royal Airlines", "ROYAL PAKISTAN", "Pakistan", + "RPS", "Global Air Charter", "RESPONSE", "United States", + "RPX", "HD Air Ltd", "RAPEX", "United Kingdom", + "RRA", "Royal Rwanda Airlines", "ROYAL RWANDA", "Rwanda", + "RRB", "Aero Club de Castellon", "", "Spain", + "RRC", "Aero Roca", "AEROROCA", "Mexico", + "RRE", "Aerotransportes Internacionales De Torreon", "AERO TORREON", "Mexico", + "RRF", "Royal Air Force", "KITTY", "United Kingdom", + "RRL", "Rolls-Royce Limited", "MERLIN", "United Kingdom", + "RRO", "Aerocredo", "", "Russia", + "RRR", "Royal Air Force", "ASCOT", "United Kingdom", + "RRS", "Boscombe Down DERA (Formation)", "BLACKBOX", "United Kingdom", + "RRT", "Transportes Aéreos Sierra", "SIERRA ALTA", "Mexico", + "RRV", "Mombasa Air Safari", "SKYROVER", "Kenya", + "RRY", "Tbilisi Aviation University", "AIRFERRY", "Georgia", + "RRZ", "Rollright Aviation", "ROLLRIGHT", "United Kingdom", + "RSA", "Elisra Airlines", "ESRA", "Sudan", + "RSB", "Rubystar", "RUBYSTAR", "Belarus", + "RSC", "Aerolíneas Ejecutivas Tarascas", "TARASCAS", "Mexico", + "RSE", "SNAS Aviation", "RED SEA", "Saudi Arabia", + "RSF", "Royal Saudi Air Force", "ARSAF", "Saudi Arabia", + "RSH", "Air Sahara", "SAHARA", "India", + "RSI", "Air Sunshine", "AIR SUNSHINE", "United States", + "RSK", "DSWA", "REDSKIN", "United States", + "RSL", "Panama Aircraft Rental and Sales", "PANAMA RENTAL", "Panama", + "RSM", "Air Somalia", "AIR SOMALIA", "Somali Republic", + "RSN", "Royal Swazi National Airways", "SWAZI NATIONAL", "Eswatini", + "RSP", "JetSuite", "REDSTRIPE", "United States", + "RSQ", "International SOS Windhoek", "SKYMEDIC", "Namibia", + "RSR", "Aero-Service", "CONGOSERV", "Republic of the Congo", + "RSS", "Rossair", "ROSS CHARTER", "South Africa", + "RST", "Resort Air", "RESORT AIR", "United States", + "RSU", "AeroSur", "AEROSUR", "Bolivia", + "RSV", "Red Sky Ventures", "RED SKY", "Namibia", + "RTH", "Artis", "ARTHELICO", "France", + "RTM", "Trans Am Compania", "AERO TRANSAM", "Ecuador", + "RTN", "Raytheon Aircraft Company", "RAYTHEON", "United States", + "RTO", "Rectimo Air Transports", "RACCOON", "France", + "RTQ", "Air Turquoise", "TURQUOISE", "France", + "RTR", "Rotatur", "ROTATUR", "Brazil", + "RTS", "Relief Transport Services", "RELIEF", "United Kingdom", + "RTU", "Aerotucan", "AEROTUCAN", "Mexico", + "RTV", "Nortavia", "TIC-TAC", "Portugal", + "RUA", "Rwanda Airlines", "", "Rwanda", + "RUC", "Rutas Aéreas", "RUTACA", "Venezuela", + "RUD", "Air Anastasia", "ANASTASIA", "Ukraine", + "RUH", "Rusich-T", "", "Russia", + "RUK", "Ryanair UK", "BLUEMAX", "United Kingdom", + "RUM", "Air Rum", "AIR RUM", "Sierra Leone", + "RUN", "Air ACT", "CARGO TURK", "Turkey", + "RUR", "Rusaero", "", "Russia", + "RUT", "Reut Airways", "YADID", "Israel", + "RUZ", "Rusuertol", "ROSTUERTOL", "Russia", + "RVC", "Richards Aviation", "RIVER CITY", "United States", + "RVE", "Airventure", "AIRVENTURE", "Belgium", + "RVF", "Ravn Alaska", "RAVEN FLIGHT", "United States", + "RVI", "Aero Servicios", "AERO SERVICIOS", "Mexico", + "RVL", "Airvallee", "AIR VALLEE", "Italy", + "RVM", "River Ministries Air Charter", "RIVER", "South Africa", + "RVN", "Raven Air", "RAVEN U-S", "United States", + "RVP", "Air VIP", "AEROVIP", "Portugal", + "RVQ", "Aero Jet International", "REVA AIR", "United States", + "RVR", "Raven Air", "RAVEN", "United Kingdom", + "RVT", "Aircompany Veteran", "AIR-VET", "Armenia", + "RWA", "Rwanda Airways", "", "Rwanda", + "RWB", "Alliance Express Rwanda", "", "Rwanda", + "RWC", "Arrow Ecuador Arrowec", "ARROWEC", "Ecuador", + "RWD", "Rwandair Express", "RWANDAIR", "Rwanda", + "RWE", "Royal West Airlines", "ROYAL WEST", "United States", + "RWG", "C&M Airways", "RED WING", "United States", + "RWL", "RWL Luftfahrtgesellschaft", "RHEINTRAINER", "Germany", + "RWS", "Air Whitsunday", "", "Australia", + "RXA", "Regional Express", "REX", "Australia", + "RXP", "Royal Aviation Express", "ROY EXPRESS", "Canada", + "RXT", "Aeroxtra", "AERO-EXTRA", "Mexico", + "RYA", "Ryan Air Services", "RYAN AIR", "United States", + "RYB", "Royal Bahrain Airlines", "ROYAL BAHRAIN", "Bahrain", + "RYL", "Royal Aruban Airlines", "ROYAL ARUBAN", "Aruba", + "RYN", "Ryan International Airlines", "RYAN INTERNATIONAL", "United States", + "RYR", "Ryanair", "RYANAIR", "Ireland", + "RYS", "Buzz (Ryanair)", "MAGIC SUN", "Poland", + "RYT", "Raya Jet", "", "Jordan", + "RYZ", "Ryazan State Air Enterprise", "RYAZAN AIR", "Russia", + "RZ", "Superna Airlines", "YANGTZE RIVER", "China", + "RZA", "Jet Fighter Flights", "RAZOR", "Australia", + "RZL", "Aero Zambia", "AERO ZAMBIA", "Zambia", + "RZN", "Aero Zano", "ZANO", "Mexico", + "RZO", "SATA International", "AIR AZORES", "Portugal", + "RZR", "Zephyr Express", "RECOVERY", "United States", + "RZU", "Zhersu Avia", "ZHERSU AVIA", "Kazakhstan", + "RZZ", "Anoka Air Charter", "RED ZONE", "United States", + "SAA", "South African Airways", "SPRINGBOK", "South Africa", + "SAB", "Sky Way Air", "SKY WORKER", "Kyrgyzstan", + "SAC", "SASCO Airlines", "SASCO", "Sudan", + "SAF", "Singapore Air Force", "SINGA", "Singapore", + "SAG", "SOS Flygambulans", "MEDICAL AIR", "Sweden", + "SAH", "Sayakhat Airlines", "SAYAKHAT", "Kazakhstan", + "SAI", "Shaheen Air", "SHAHEEN AIR", "Pakistan", + "SAM", "SAM Colombia", "SAM", "Colombia", + "SAO", "Sahel Aviation Service", "SAVSER", "Mali", + "SAP", "Avia Jaynar", "TOBOL", "Kazakhstan", + "SAQ", "Springbank Aviation", "SPRINGBANK", "Canada", + "SAS", "Scandinavian Airlines", "SCANDINAVIAN", "Sweden Denmark and Norway", + "SAT", "SATA Air Acores", "SATA", "Portugal", + "SAU", "United Aviation Services", "UNISERVE", "Spain", + "SAV", "Samal Air", "", "Kazakhstan", + "SAW", "Sham Wing Airlines", "SHAMWING", "Syrian Arab Republic", + "SAX", "Sabah Air", "SABAH AIR", "Malaysia", + "SAY", "ScotAirways", "SUCKLING", "United Kingdom", + "SAZ", "Swiss Air-Ambulance", "SWISS AMBULANCE", "Switzerland", + "SBA", "SOL Linhas Aéreas", "SOL", "Brazil", + "SBB", "Steinman Aviation", "SABER EXPRESS", "United States", + "SBC", "Emoyeni Air Charter", "SABIAN AIR", "South Africa", + "SBD", "SIBIA Aircompany Ltd", "SIBIA", "Russia", + "SBF", "Seven Bar Flying Service", "SEVENAIR", "Tunisia", + "SBG", "Sabre Incorporated", "", "United States", + "SBH", "Aerosaab", "AEROSAAB", "Mexico", + "SBI", "S7 Airlines", "SIBERIAN AIRLINES", "Russia", + "SBJ", "Trans Sahara Air", "TRANS SAHARA", "Nigeria", + "SBL", "Sobel Airlines of Ghana", "SOBGHANA", "Ghana", + "SBM", "SkyBahamas", "SKY BAHAMAS", "Bahamas", + "SBO", "Stabo Air", "STABAIR", "Zambia", + "SBQ", "SmithKline Beecham Clinical Labs", "SKIBBLE", "United States", + "SBR", "Saber Aviation", "FREIGHTER", "United States", + "SBS", "Seaborne Airlines", "SEABORNE", "United States", + "SBT", "Taftan Airlines", "TAFTAN", "Iran", + "SBU", "St Barth Commuter", "BLACK FIN", "France", + "SBW", "Snowbird Airlines", "SNOWMAN", "Finland", + "SBX", "North Star Air Cargo", "SKY BOX", "United States", + "SBZ", "Scibe Airlift", "SCIBE AIRLIFT", "Democratic Republic of the Congo", + "SCA", "South Central Air", "SOUTH CENTRAL", "United States", + "SCB", "Saigon Capital Aircraft Management", "SAIGON", "Netherlands", + "SCC", "Seacoast Airlines", "SEA-COASTER", "United States", + "SCD", "Associated Aviation", "ASSOCIATED", "Nigeria", + "SCE", "Scenic Airlines", "SCENIC", "United States", + "SCF", "Socofer", "SOCOFER", "Angola", + "SCH", "Seychelles Airlines", "OCEAN BIRD", "Seychelles", + "SCI", "Servicios Aéreos San Cristóbal", "SAN CRISTOBAL", "Mexico", + "SCJ", "Siamjet Aviation", "SIAMJET", "Thailand", + "SCK", "Sky Cam", "SKYCAM", "France", + "SCL", "Switfair Cargo", "SWIFTAIR", "Canada", + "SCM", "American Jet International", "SCREAMER", "United States", + "SCN", "South American Airlines", "SOUTH AMERICAN", "Peru", + "SCO", "Helikopterservice Euro Air", "SWEDCOPTER", "Sweden", + "SCP", "Scorpio Aviation", "SCORPIO", "Egypt", + "SCQ", "OSM Aviation Academy", "SCAVAC", "Norway Sweden and San Diego", + "SCR", "Silver Cloud Air", "SILVER CLOUD", "Germany", + "SCS", "South African Non Scheduled Airways", "SOUTHERN CHARTERS", "South Africa", + "SCT", "SAAB-Aircraft", "SAAB-CRAFT", "Sweden", + "SCU", "Air Scorpio", "SCORPIO UNIVERS", "Bulgaria", + "SCV", "Servicios Aéreos Del Centro", "SACSA", "Mexico", + "SCW", "Malmö Aviation", "SCANWING", "Sweden", + "SCX", "Sun Country Airlines", "SUN COUNTRY", "United States", + "SDA", "St. Andrews Airways", "SAINT ANDREWS", "Canada", + "SDB", "Sukhoi Design Bureau Company", "SU-CRAFT", "Russia", + "SDC", "Sunrise Airlines", "SUNDANCE", "United States", + "SDD", "Skymaster Air Taxi", "SKY DANCE", "United States", + "SDE", "Air Partners Corp.", "STAMPEDE", "Canada", + "SDF", "Sundorph Aeronautical Corporation", "SUNDORPH", "United States", + "SDG", "Star Air (India)", "HISTAR", "India", + "SDH", "Servicio De Helicopteros", "ARCOS", "Spain", + "SDJ", "Club 328", "SPACEJET", "United Kingdom", + "SDK", "SADELCA - Sociedad Aérea Del Caquetá", "SADELCA", "Colombia", + "SDL", "Skydrift", "SKYDRIFT", "United Kingdom", + "SDM", "Rossiya", "RUSSIA", "Russia", + "SDN", "Spirit of Africa Airlines", "BLUE NILE", "Sudan", + "SDO", "Air Santo Domingo", "AERO DOMINGO", "Dominican Republic", + "SDP", "Aero Sudpacifico", "SUDPACIFICO", "Mexico", + "SDR", "City Airline", "SWEDESTAR", "Sweden", + "SDS", "United Kingdom Civil Aviation Authority", "STANDARDS", "United Kingdom", + "SDU", "Sud Airlines", "SUD LINES", "France", + "SDV", "Servicios Aéreos Del Vaupes", "SELVA", "Colombia", + "SDX", "Servicio Tecnico Aero De Mexico", "SERVICIO TECNICO", "Mexico", + "SDY", "Island Express", "SANDY ISLE", "United States", + "SDZ", "Sudan Pezetel for Aviation", "SUDANA", "Sudan", + "SEA", "Southeast Air", "SOUTHEAST AIR", "United States", + "SEB", "Servicios Aéreos Luce", "SERVILUCE", "Mexico", + "SEC", "3D Aviation", "SECUREX", "United States", + "SED", "Sedona Air Center", "SEDONA AIR", "United States", + "SEE", "Shaheen Air Cargo", "SHAHEEN CARGO", "Pakistan", + "SEF", "Aero Servicios Ejecutivas Del Pacifico", "SERVIPACIFICO", "Mexico", + "SEG", "Eagle International", "SEN-EAGLE", "Senegal", + "SEH", "Sky Express", "AIR CRETE", "Greece", + "SEI", "Transportes Aéreos Sierra Madre", "TRANSPORTE SIERRA", "Mexico", + "SEJ", "Spicejet", "SPICEJET", "India", + "SEK", "Star East Airline", "EAST RIDER", "Romania", + "SEL", "Sentel Corporation", "SENTEL", "United States", + "SEM", "Cape Central Airways", "SEMO", "United States", + "SEN", "Servicios de Aviacion Sierra", "SERVISIERRA", "Mexico", + "TUX", "Tunisair Express", "TUNEXPRESS", "Tunisia", + "SEO", "Selcon Airlines", "SELCON AIR", "Nigeria", + "SEQ", "Sky Eyes", "SKY EYES", "Thailand", + "SER", "Aero California", "AEROCALIFORNIA", "Mexico", + "SES", "Servicio Aéreo Saltillo", "SERVISAL", "Mexico", + "SET", "SAETA", "SAETA", "Ecuador", + "SEU", "XL Airways France", "STARWAY", "France", + "SEV", "Serair Transworld Press", "CARGOPRESS", "Spain", + "SEY", "Air Seychelles", "SEYCHELLES", "Seychelles", + "SFC", "Shuswap Flight Centre", "SHUSWAP", "Canada", + "SFE", "Sefofane Air Charters", "SEFOFANE", "Botswana", + "SFF", "Safewings Aviation Company", "SWIFTWING", "United States", + "SFG", "Sun Freight Logistics", "AERO GULF", "Thailand", + "SFJ", "Star Flyer", "STARFLYER", "Japan", + "SFL", "Southflight Aviation", "SOUTHFLIGHT", "New Zealand", + "SFM", "A-Safar Air Services", "AIR SAFAR", "Nigeria", + "SFN", "Safiran Airlines", "SAFIRAN", "Iran", + "SFP", "Safe Air", "SAFE AIR", "Pakistan", + "SFR", "Safair", "CARGO", "South Africa", + "SFS", "Southern Frontier Air Transport", "SOUTHERN FRONTIER", "Canada", + "SFT", "Skyfreight", "SKYFREIGHT", "United States", + "SFU", "Solent Flight", "SAINTS", "United Kingdom", + "SFX", "S.K. Logistics", "SWAMP FOX", "United States", + "SFY", "Gulf Flite Center", "SKY FLITE", "United States", + "SGA", "Air Saigon", "AIR SAIGON", "Vietnam", + "SGB", "Sky King Inc.", "SONGBIRD", "United States", + "SGC", "Southern Right Air Charter", "SOUTHERNRIGHT", "South Africa", + "SGD", "Sky Gate International Aviation", "AIR BISHKEK", "Kyrgyzstan", + "SGF", "STAC Swiss Government Flights", "STAC", "Switzerland", + "SGH", "Servisair", "SERVISAIR", "United Kingdom", + "SGI", "Servicios Aéreos Agrícolas", "SERAGRI", "Chile", + "SGK", "Skyward Aviation", "SKYWARD", "Canada", + "SGM", "Sky Aircraft Service", "SIGMA", "Netherlands", + "SGN", "Siam GA", "SIAM", "Thailand", + "SGP", "Sagolair Transportes Ejecutivos", "SAGOLAIR", "Spain", + "SGS", "Saskatchewan Government Executive Air Service", "SASKATCHEWAN", "Canada", + "SGT", "Skygate", "SKYGATE", "Netherlands", + "SGU", "Sol del Paraguay", "SOLPARAGUAYO", "Paraguay", + "SGV", "Aerosegovia", "SEGOVIA", "Nicaragua", + "SGX", "Saga Airlines", "", "Turkey", + "SGY", "Skagway Air Service", "SKAGWAY AIR", "United States", + "SHA", "Shree Airlines", "SHREEAIR", "Nepal", + "SHB", "Shabair", "SHABAIR", "Democratic Republic of the Congo", + "SHC", "Sky Harbor Air Service", "SKY HARBOR CHEYENNE", "United States", + "SHD", "Sahara Airlines", "", "Algeria", + "SHE", "Shell Aircraft", "SHELL", "United Kingdom", + "SHF", "Royal Air Force", "VORTEX", "United Kingdom", + "SHG", "Shoprite Group", "SHOP AIR", "United Kingdom", + "SHJ", "Sharjah Ruler's Flight", "SHARJAH", "United Arab Emirates", + "SHK", "Shorouk Air", "", "Egypt", + "SHL", "Samson Aviation", "SAMSON", "United Kingdom", + "SHM", "Sheltam Aviation", "SHELTAM", "South Africa", + "SHN", "Shaheen Airport Services", "SUGAR ALFA", "Pakistan", + "SHO", "Sheremetyevo-Cargo", "", "Russia", + "SHP", "Service Aerien Francais", "SAF", "France", + "SHQ", "Shanghai Airlines Cargo", "SHANGHAI CARGO", "China", + "SHR", "Shooter Air Courier", "SHOOTER", "Canada", + "SHS", "Shura Air Transport Services", "SHURA AIR", "Ethiopia", + "SHT", "British Airways Shuttle", "SHUTTLE", "United Kingdom", + "SHU", "Sakhalinskie Aviatrassy (SAT)", "SATAIR", "Russia", + "SHV", "Shavano Air", "SHAVANO", "United States", + "SHW", "Shawnee Airline", "SHAWNEE", "United States", + "SHX", "Slim Aviation Services", "SLIM AIR", "Nigeria", + "SHY", "Sky Airlines", "ANTALYA BIRD", "Turkey", + "SIA", "Singapore Airlines", "SINGAPORE", "Singapore", + "SIB", "Sibaviatrans", "SIBAVIA", "Russia", + "SIC", "SFS Aviation", "SICHART", "Thailand", + "SIE", "Sierra Express", "SEREX", "United States", + "SIH", "Skynet Airlines", "BLUEJET", "Ireland", + "SII", "Aero Servicios Ejecutivos Internacionales", "ASEISA", "Mexico", + "SIJ", "Seco International", "", "Japan", + "SIL", "Silver Airways", "SILVER WINGS", "United States", + "SIM", "Star Air", "", "Sierra Leone", + "SIO", "Sirio (airline)", "SIRIO", "Italy", + "SIP", "Air Spirit", "AIR SPIRIT", "United States", + "SIQ", "National Center for Atmospheric Research", "SCIENCE QUEST", "United States", + "SIR", "Salair", "SALAIR", "United States", + "SIS", "Saber Airlines", "", "Egypt", + "SIT", "SITA", "", "Belgium", + "SIV", "Slovenian Armed Forces", "SLOVENIAN", "Slovenia", + "SIW", "Sirio Executive", "SIRIO EXECUTIVE", "Italy", + "SIX", "Sixt Rent A Car", "DRIVE ORANGE", "United States", + "SIY", "Aerosiyusa", "SIYUSA", "Mexico", + "SIZ", "Aero Silza", "AEROSILZA", "Mexico", + "SJA", "Servicios Aéreos Especiales De Jalisco", "SERVICIOJAL", "Mexico", + "SJB", "SKS Airways", "SOUTHER TIGER", "Malaysia", + "SJC", "Servicios Ejecutivos Continental", "SERVIEJECUTIVO", "Mexico", + "SJE", "Sunair 2001", "SUNBIZ", "South Africa", + "SJJ", "Spirit Aviation", "SPIRIT JET", "United States", + "SJL", "Servicios Especiales Del Pacifico Jalisco", "SERVICIOS JALISCO", "Mexico", + "SJM", "Sino Jet Management", "SINO SKY", "China", + "SJN", "Air San Juan", "SAN JUAN", "United States", + "SJO", "Spring Airlines Japan", "JEY SPRING", "Japan", + "SJT", "Swiss Jet", "SWISS JET", "Switzerland", + "SJX", "Starlux Airlines", "STARWALKER", "Taiwan", + "SJY", "Sriwijaya Air", "SRIWIJAYA", "Indonesia", + "SKA", "Rio Air Express", "RIO EXPRESS", "Brazil", + "SKC", "Skymaster Airlines", "SKYMASTER AIR", "Brazil", + "SKD", "Sky Harbor Air Service", "SKY DAWG", "United States", + "SKE", "Sky Tours", "SKYISLE", "United States", + "SKF", "Skycraft", "SKYCRAFT", "United States", + "SKG", "Skycraft Air Transport", "SKYCRAFT-CANADA", "Canada", + "SKH", "British Sky Broadcasting", "SKYNEWS", "United Kingdom", + "SKI", "SkyKing Turks and Caicos Airways", "SKYKING", "Turks and Caicos Islands", + "SKK", "Skylink Aviation", "SKYLINK", "Canada", + "SKL", "Skycharter (Malton)", "SKYCHARTER", "Canada", + "SKM", "Fayetteville Flying Service and Scheduled Skyways System", "SKYTEM", "United States", + "SKN", "Skyline Aviation Services", "SKYLINER", "United States", + "SKO", "Scottish Airways Flyers", "SKYWORK", "United Kingdom", + "SKQ", "Labcorp", "SKYLAB", "United States", + "SKR", "Skyscapes Air Charters", "SKYSCAPES", "South Africa", + "SKS", "Sky Link Aviation", "", "Pakistan", + "SKT", "SkyStar Airways", "SKY YOU", "Thailand", + "SKU", "Sky Airline", "AEROSKY", "Chile", + "SKV", "Sky Regional Airlines", "MAPLE", "Canada", + "SKW", "SkyWest Airlines", "SKYWEST", "United States", + "SKY", "Skymark Airlines", "SKYMARK", "Japan", + "SKZ", "Skyway Enterprises", "SKYWAY-INC", "United States", + "SLA", "Sierra National Airlines", "SELAIR", "Sierra Leone", + "SLB", "Slok Air", "SLOK AIR", "Nigeria", + "SLD", "Silver Air", "SILVERLINE", "Czech Republic", + "SLE", "Streamline", "SLIPSTREAM", "South Africa", + "SLF", "Starfly", "ELISTARFLY", "Italy", + "SLG", "Saskatchewan Government", "LIFEGUARD", "Canada", + "SLH", "Silverhawk Aviation", "SILVERHAWK", "United States", + "SLI", "Aeroméxico Connect", "COSTERA", "Mexico", + "SLK", "SilkAir", "SILKAIR", "Singapore", + "SLL", "Slovak Airlines", "SLOV LINE", "Slovakia", + "SLM", "Surinam Airways", "SURINAM", "Suriname", + "SLN", "Sloane Aviation", "SLOANE", "United Kingdom", + "SLO", "Edgartown Air", "SLOW", "United States", + "SLP", "Salpa Aviation", "SALPA", "Sudan", + "SLS", "Servicios Aéreos Slainte", "SERVICIOS SLAINTE", "Mexico", + "SLU", "Avio Sluzba", "AVIO SLUZBA", "Serbia", + "SLV", "Stella Aviation", "AVISTELLA", "Mauritania", + "SLW", "Salama Airlines Nigeria", "SALMA AIR", "Nigeria", + "SLX", "Sete Linhas Aéreas", "SETE", "Brazil", + "SLY", "Sky Line for Air Services", "SKYCO", "Sudan", + "SLZ", "Super Luza", "LUZA", "Angola", + "SMA", "SMA Airlines", "SESAME", "Nigeria", + "SMC", "Sabang Merauke Raya Air Charter", "SAMER", "Indonesia", + "SMD", "Servicios Aéreos La Marquesa", "SERVICIOS MARQUESA", "Mexico", + "SME", "Semos", "SEMICH", "Kazakhstan", + "SMF", "Smalandsflyg", "GORDON", "Sweden", + "SMG", "RAF St Mawgan Search and Rescue", "", "United Kingdom", + "SMH", "Smithair", "SMITHAIR", "United States", + "SMI", "Aero Sami", "SAMI", "Mexico", + "SMJ", "Avient Aviation", "AVAVIA", "Zimbabwe", + "SMK", "Semeyavia", "ERTIS", "Kazakhstan", + "SML", "Smith Air (1976)", "SMITH AIR", "Canada", + "SMM", "Summit Air", "SUMMIT", "Canada", + "SMQ", "Samar Air", "SAMAR AIR", "Tajikistan", + "SMR", "Somon Air", "SOMON AIR", "Tajikistan", + "SMS", "Linhas Aéreas Santomenses", "SANTOMENSES", "São Tomé and Príncipe", + "SMT", "Skyline", "SKYLIMIT", "Nigeria", + "SMU", "Sanborn Map Company", "SPRINGER", "United States", + "SMW", "Carpatair Flight Service", "SMART WINGS", "Romania", + "SMX", "Alitalia Express", "ALIEXPRESS", "Italy", + "SMY", "Sama Airlines", "NAJIM", "Saudi Arabia", + "SMZ", "RAF Scampton", "SCAMPTON", "United Kingdom", + "SNA", "Senator Aviation Charter", "SENATOR", "Germany", + "SNB", "Sterling Airlines", "STERLING", "Denmark", + "SNC", "Air Cargo Carriers", "NIGHT CARGO", "United States", + "SND", "Air Samarkand", "ARSAM", "Uzbekistan", + "SNE", "Servicios Aéreos De Nicaragua (SANSA)", "SANSA", "Nicaragua", + "SNF", "Shans Air", "SHANS AIR", "Russia", + "SNG", "LongJiang Airlines", "SNOW EAGLE", "China", + "SNH", "Senair Services", "SENSERVICE", "Senegal", + "SNI", "Savanah Airlines", "SAVANAHLINE", "Nigeria", + "SNJ", "Solaseed Air", "NEWSKY", "Japan", + "SNK", "Southeast Airlines (Sun Jet International)", "SUN KING", "United States", + "SNL", "Soonair Lines", "SOONAIR", "United States", + "SNM", "Servizi Aerei", "SERVIZI AEREI", "Italy", + "SNO", "Delta Air Charter", "SNOWBALL", "Canada", + "SNP", "Sun Pacific International", "SUN PACIFIC", "United States", + "SNQ", "Sun Quest Executive Air Charter", "EXECU-QUEST", "United States", + "SNS", "Societe Centrafricaine De Transport Aerien", "", "Central African Republic", + "SNT", "Suncoast Aviation", "SUNCOAST", "United States", + "SNU", "Snunit Aviation", "", "Israel", + "SNV", "Sudanese States Aviation", "SUDANESE", "Sudan", + "SNW", "Sun West Airlines", "SUN WEST", "United States", + "SNX", "Sun Air Aviation Services", "SUNEX", "Canada", + "SNY", "Air Sandy", "AIR SANDY", "Canada", + "SOB", "Stabo Freight", "STABO", "Zambia", + "SOC", "Southern Cargo Air Lines", "", "Russia", + "SOD", "Aerolíneas Sol", "ALSOL", "Mexico", + "SOE", "Air Soleil", "AIR SOLEIL", "Mauritania", + "SOG", "Solenta Aviation Ghana", "", "Ghana", + "SOH", "Southern Ohio Aviation Sales", "SOUTHERN OHIO", "United States", + "SOI", "Southern Aviation", "SOAVAIR", "Zambia", + "SOL", "Solomon Airlines", "SOLOMON", "Solomon Islands", + "SOM", "Somali Airlines", "SOMALAIR", "Somali Republic", + "SON", "Sunshine Air Tours", "SUNSHINE TOURS", "United States", + "SOO", "Southern Air", "SOUTHERN AIR", "United States", + "SOP", "Solinair", "SOLINAIR", "Slovenia", + "SOR", "Sonair Servico Aéreo", "SONAIR", "Angola", + "SOT", "Southeast Correct Craft", "SOUTH COURIER", "United States", + "SOU", "Southern Airways", "SOUTHERN EXPRESS", "United States", + "SOV", "Saratov Airlines Joint Stock Company", "SARATOV AIR", "Russia", + "SOW", "White Sparrow GmbH", "SPARROW", "Austria", + "SOX", "Solid Air", "SOLIDAIR", "Netherlands", + "SOY", "Island Aviation", "SORIANO", "Philippines", + "SOZ", "Sat Airlines", "SATCO", "Kazakhstan", + "SPA", "Sierra Pacific Airlines", "SIERRA PACIFIC", "United States", + "SPB", "Springbok Classic Air", "SPRING CLASSIC", "South Africa", + "SPC", "Spark Air Cargo", "Spark Cargo", "United States", + "SPE", "Sprague Electric Company", "SPRAGUE", "United States", + "SPF", "Space World Airline", "SPACE WORLD", "Nigeria", + "SPG", "Springdale Air Service", "SPRING AIR", "United States", + "SPH", "Sapphire Executive Air", "SAPPHIRE-CHARTER", "South Africa", + "SPI", "South Pacific Island Airways", "SOUTH PACIFIC", "United States", + "SPJ", "Air Service", "AIR SKOPJE", "Macedonia", + "SPK", "Diamond Aviation", "SPARKLE", "United States", + "SPL", "Servicios Corporativos Aéreos De La Laguna", "CORPORATIVOS LAGUNA", "Mexico", + "SPM", "Air Saint Pierre", "SAINT-PIERRE", "France", + "SPN", "Skorpion Air", "AIR SKORPIO", "Bulgaria", + "SPO", "Aeroservicios Ejecutivos Del Pacifico", "EJECTUIV PACIFICO", "Mexico", + "SPP", "Sapphire Aviation", "SAPPHIRE", "United States", + "SPQ", "Servicios Aéreos Palenque", "SERVICOS PALENQUE", "Mexico", + "SPR", "Provincial Airlines", "SPEEDAIR", "Canada", + "SPS", "Spark Shuttle", "Spark Shuttle", "United States", + "SPT", "Speed Aviation", "SPEED AVIATION", "Bangladesh", + "SPU", "Southeast Airmotive", "SPUTTER", "United States", + "SPV", "Servicios Privados De Aviación", "SERVICIOS PRIVADOS", "Mexico", + "SPW", "Speedwings", "SPEEDWING", "Switzerland", + "SPX", "Spark Express (Glow)", "Glow|United States", "", + "SPY", "Asian Aerospace Service", "THAI SPACE", "Thailand", + "SPZ", "Airworld", "SPEED SERVICE", "South Africa", + "SQA", "Slovak National Aeroclub", "SLOVAK AEROCLUB", "Slovakia", + "SQC", "Singapore Airlines Cargo", "SINGCARGO", "Singapore", + "SQF", "Slovak Air Force", "SLOVAK AIRFORCE", "Slovakia", + "SQH", "SeaPort Airlines", "SASQUATCH", "United States", + "SQL", "Servicos De Alquiler", "ALQUILER", "Mexico", + "SQP", "SkyUp", "SKYUP", "Ukraine", + "SRA", "Sair Aviation", "SAIR", "Canada", + "SRC", "Searca", "SEARCA", "Colombia", + "SRD", "HM Coastguard", "COASTGUARD", "United Kingdom", + "SRE", "Fly Jetstream Aviation", "STREAMJET", "South Africa", + "SRF", "Transportes Aéreos San Rafael", "SAN RAFEAL", "Chile", + "SRG", "HM Coastguard", "RESCUE", "United Kingdom", + "SRH", "Siem Reap Airways", "SIEMREAP AIR", "Cambodia", + "SRI", "Air Safaris and Services", "AIRSAFARI", "New Zealand", + "SRJ", "C Air Jet Airlines", "SYRJET", "Syria", + "SRK", "Sky Work Airlines", "SKYFOX", "Switzerland", + "SRL", "Servicios Aeronáuticos Aero Personal", "SERVICIOS PERSONAL", "Mexico", + "SRN", "Sirair", "SIRAIR", "Russia", + "SRO", "Servicios Aéreos Ejecutivos Saereo", "SAEREO", "Ecuador", + "SRP", "Polizeihubschauberstaffel Rheinland-Pfalz", "SPERBER", "Germany", + "SRQ", "Cebgo", "BLUE JAY", "Philippines", + "SRR", "Star Air", "WHITESTAR", "Denmark", + "SRS", "Selkirk Remote Sensing", "PHOTO CHARLIE", "Canada", + "SRT", "Trans Asian Airlines", "TRASER", "Kazakhstan", + "SRU", "Star Up", "STAR-UP", "Peru", + "SRV", "Aero Servicio Corporativo", "SERVICORP", "Mexico", + "SRW", "Sarit Airlines", "SARIA", "Sudan", + "SRX", "Sierra Expressway Airlines", "SIERRA EX", "United States", + "SRY", "As-Aero", "", "Armenia", + "SRZ", "Strato Air Services", "STRATO", "South Africa", + "SSB", "Sasair", "SASIR", "Canada", + "SSC", "Southern Seaplane", "SOUTHERN SKIES", "United States", + "SSD", "Star Service International", "STAR SERVICE", "France", + "SSE", "Servicios Aéreos Sunset", "SUNSET", "Mexico", + "SSF", "Severstal Air Company", "SEVERSTAL", "Russia", + "SSG", "Slovak Government Flying Service", "SLOVAK GOVERNMENT", "Slovakia", + "SSH", "Heritage Flight (Valley Air Services)", "SNOWSHOE", "United States", + "SSI", "Vision Airlines", "SUPER JET", "Nigeria", + "SSK", "Skystar International", "SKYSTAR", "United States", + "SSL", "Air Sultan", "SIERRA SULTAN", "Sierra Leone", + "SSO", "Special Scope Limited", "DOPE", "United Kingdom", + "SSP", "Starspeed", "STARSPEED", "United Kingdom", + "SSQ", "Sunstate Airlines", "SUNSTATE", "Australia", + "SSR", "Sardinian Sky Service", "SARDINIAN", "Italy", + "SSS", "SAESA", "SAESA", "Spain", + "SST", "Sunwest Airlines", "SUNFLIGHT", "Canada", + "SSU", "SASCA", "SASCA", "Venezuela", + "SSW", "Streamline Aviation", "STREAMLINE", "United Kingdom", + "SSX", "Lynx Aviation", "SHASTA", "United States", + "SSY", "Sky Aviation", "SIERRA SKY", "Sierra Leone", + "SSZ", "Specsavers Aviation", "SPECSAVERS", "United Kingdom", + "STA", "Star Aviation", "STAR", "United Kingdom", + "STB", "Status-Alpha Airline", "STATUS-ALPHA", "Ukraine", + "STC", "Stadium City Limited", "STADIUM", "United Kingdom", + "STD", "Servicios De Aerotransportacion De Aguascalientes", "AERO AGUASCALINETES", "Mexico", + "STE", "Semitool Europe", "SEMITRANS", "United Kingdom", + "STF", "SFT-Sudanese Flight", "", "Sudan", + "STG", "Sedalia Marshall Boonville Stage Line", "STAGE", "United States", + "STH", "South-Airlines", "", "Armenia", + "STI", "Sontair", "SONTAIR", "Canada", + "STJ", "Sella Aviation", "STELLAVIA", "Netherlands", + "STK", "Aeropac", "SAT PAK", "United States", + "STL", "Stapleford Flight Centre", "STAPLEFORD", "United Kingdom", + "STN", "RAF St Athan", "SAINT ATHAN", "United Kingdom", + "STO", "Streamline Ops", "SLOPS", "Russia", + "STQ", "Star Air", "STERA", "Indonesia", + "STR", "Red Star", "STARLINE", "United Arab Emirates", + "STT", "Western Aircraft Inc", "SAWTOOTH", "United States", + "STU", "Star African Air", "STARSOM", "Somali Republic", + "STV", "Saturn Aviation", "SATURN", "United States", + "STW", "South West Air Corporation", "SIERRA WHISKEY", "Philippines", + "STX", "Stars Away Aviation", "STARSAWAY", "South Africa", + "STY", "Styrian Airways", "STYRIAN", "Austria", + "SUA", "Silesia Air", "AIR SILESIA", "Czech Republic", + "SUB", "Suburban Air Freight", "SUB AIR", "United States", + "SUD", "Sudan Airways", "SUDANAIR", "Sudan", + "SUE", "Aerolíneas Del Sureste", "AEROSURESTE", "Mexico", + "SUF", "Sun Air (Fiji)", "SUNFLOWER", "Fiji", + "SUG", "Sunu Air", "SUNU AIR", "Senegal", + "SUH", "Sun Light", "LIGHT AIR", "Kyrgyzstan", + "SUI", "Swiss Air Force", "SWISS AIR FORCE", "Switzerland", + "SUK", "Superior Aviation Services", "SKYCARGO", "Kenya", + "SUM", "State Unitary Air Enterprise", "SUMES", "Russia", + "SUN", "Antillana De Navegación Aérea", "", "Dominican Republic", + "SUO", "Aeroservicios De San Luis", "SERVICIO SANLUIS", "Mexico", + "SUP", "Aeronautical Charters", "SUN SPEED", "United States", + "SUR", "Sun Air", "", "Egypt", + "SUS", "Sun Air of Scandinavia", "SUNSCAN", "Denmark", + "SUT", "Sistemas Aeronauuticos 2000", "SISTEMAS AERONAUTICOS", "Mexico", + "SUU", "Star West Aviation", "SUNSTAR", "United States", + "SUV", "Sundance Air", "DANCEAIR", "Venezuela", + "SUW", "Interavia Airlines", "ASTAIR", "Russia", + "SUY", "Aerial Surveys (1980) Limited", "SURVEY", "New Zealand", + "SVA", "Saudia", "SAUDIA", "Saudi Arabia", + "SVB", "Siavia", "SIAVIA", "Slovenia", + "SVD", "St. Vincent Grenadines Air (1990)", "GRENADINES", "Saint Vincent and the Grenadines", + "SVE", "Aero Servicios Expecializados", "AEROESPECIAL", "Mexico", + "SVF", "Swedish Armed Forces", "SWEDEFORCE", "Sweden", + "SVH", "Sterling Helicopters", "SILVER", "United Kingdom", + "SVI", "Servicios De Transporte Aéreo", "SETRA", "Mexico", + "SVJ", "Silver Air", "", "Djibouti", + "SVL", "Sevastopol-Avia", "SEVAVIA", "Ukraine", + "SVM", "Aeroservicios Monterrey", "SERVIMONTE", "Mexico", + "SVN", "Savanair (Angola)", "SAVANAIR", "Angola", + "SVO", "Servicios Aeronáuticos De Oriente", "SERVIORIENTE", "Mexico", + "SVR", "Ural Airlines", "SVERDLOVSK AIR", "Russia", + "SVS", "Servicios Aéreos Saar", "AEREOS SAAR", "Mexico", + "SVT", "Seven Four Eight Air Services", "SIERRA SERVICES", "Luxembourg", + "SVV", "SALTAVIATION", "SALT", "Poland", + "SVW", "Global Jet Luxembourg", "SILVER ARROWS", "Luxembourg", + "SVX", "Security Aviation", "SECURITY AIR", "United States", + "SVY", "Cooper Aerial Surveys", "SURVEYOR", "United Kingdom", + "SWA", "Southwest Airlines", "SOUTHWEST", "United States", + "SWB", "Swissboogie Parapro", "SWISSBOOGIE", "Switzerland", + "SWC", "South West Air", "SAINT CLAIR", "Canada", + "SWD", "Trifly", "SAWBLADE", "United States", + "SWE", "Swedeways", "SWEDELINE", "Sweden", + "SWF", "Galair International", "GALAIR", "United Kingdom", + "SWG", "Sunwing Airlines", "SUNWING", "Canada", + "SWH", "Adler Aviation", "SHOCKWAVE", "Canada", + "SWI", "Sunworld Airlines", "SUNWORLD", "United States", + "SWJ", "StatesWest Airlines", "STATES", "United States", + "SWK", "General Aerospace", "SKYWALKER", "Canada", + "SWL", "Trans Jet Airways", "TRANSJET", "Sweden", + "SWN", "West Air Sweden", "AIR SWEDEN", "Sweden", + "SWP", "Star Work Sky", "STAR WORK", "Italy", + "SWQ", "Swift Air (Interstate Equipment Leasing)", "SWIFTFLIGHT", "United States", + "SWR", "Swiss International Air Lines", "SWISS", "Switzerland", + "SWS", "Sunwest Aviation (Lindquist Investment)", "SUNNY WEST", "United States", + "SWT", "Swiftair", "SWIFT", "Spain", + "SWU", "Swiss Global Air Lines", "EUROSWISS", "Switzerland", + "SWV", "Swe Fly", "FLYING SWEDE", "Sweden", + "SWW", "Shovkoviy Shlyah", "WAY AERO", "Ukraine", + "SWX", "Swazi Express Airways", "SWAZI EXPRESS", "Eswatini", + "SWY", "Sky Jet", "SWISSLINK", "Switzerland", + "SWZ", "Skywise Airline", "SKYWISE", "South Africa", + "SXA", "Southern Cross Aviation", "FERRY", "United States", + "SXC", "Sky Exec Aviation Services", "SKY EXEC", "Nigeria", + "SXD", "Sunexpress Deutschland", "SUNRISE", "Germany", + "SXE", "Southeast Express Airlines", "DOGWOOD EXPRESS", "United States", + "SXM", "Servicios Aéreos Especializados Mexicanos", "SERVIMEX", "Mexico", + "SXN", "SaxonAir", "SAXONAIR", "United Kingdom", + "SXS", "SunExpress", "SUNEXPRESS", "Turkey", + "SXT", "Servicios de Taxi Aereos", "SERTAXI", "Mexico", + "SXX", "Satellite Aero", "SATELLITE EXPRESS", "United States", + "SXY", "Safari Express Cargo", "SAFARI EXPRESS", "Kenya", + "SYA", "Skyways", "LINEAS CARDINAL", "Argentina", + "SYC", "Systec 2000", "SYSTEC", "United States", + "SYE", "Sheba Aviation", "", "Yemen", + "SYF", "Sky One Express Airlines", "SKY FIRST", "United States", + "SYG", "Synergy Aviation", "SYNERGY", "United Kingdom", + "SYH", "Sky Handling", "", "Ukraine", + "SYI", "Sonalysts", "", "United States", + "SYJ", "Slate Falls Airways", "", "Canada", + "SYK", "Satsair", "AEROCAB", "United States", + "SYL", "Aircompany Yakutia", "AIR YAKUTIA", "Russia", + "SYN", "Syncrude Canada", "SYNCRUDE", "Canada", + "SYR", "Syrian Arab Airlines", "SYRIANAIR", "Syrian Arab Republic", + "SYS", "Shawbury Flying Training Unit", "SHAWBURY", "United Kingdom", + "SYT", "Aerosud Aviation", "SKYTRACK", "South Africa", + "SYV", "Special Aviation Systems", "SPECIAL SYSTEM", "United States", + "SYX", "Skywalk Airlines", "SKYWAY-EX", "United States", + "SYY", "South African Historic Flight", "SKY COACH", "South Africa", + "SYZ", "Zil Air", "ZIL AIR", "Seychelles", + "SZB", "Samoa Air", "SAMOA", "Samoa", + "SZN", "Air Senegal", "AIR SENEGAL", "Senegal", + "SZT", "Servicios Aeronáuticos Z", "AERO ZEE", "Mexico", + "TAA", "Aeroservicios de La Costa", "AERO COSTA", "Mexico", + "TAC", "Turbot Air Cargo", "TURBOT", "Senegal", + "TAD", "Transporte Aéreo Dominicano", "TRANS DOMINICAN", "Dominican Republic", + "TAE", "TAME", "TAME", "Ecuador", + "TAG", "TAG Aviation USA", "TAG U-S", "United States", + "TAJ", "Tunisavia", "TUNISAVIA", "Tunisia", + "TAL", "Talair", "TALAIR", "Papua New Guinea", + "TAM", "LATAM Brasil", "TAM", "Brazil", + "TAN", "Zanair", "ZANAIR", "Tanzania", + "TAO", "Aeromar", "TRANS-AEROMAR", "Mexico", + "TAP", "TAP Portugal", "AIR PORTUGAL", "Portugal", + "TAR", "Tunisair", "TUNAIR", "Tunisia", + "TAS", "Lotus Air", "LOTUS FLOWER", "Egypt", + "TAT", "Grupo TACA", "TACA-COSTARICA", "Costa Rica", + "TAU", "Transportes Aéreos Tauro", "TRANSTAURO", "Mexico", + "TAV", "Compañía de Servicios Aéreos Tavisa", "TAVISA", "Spain", + "TAX", "Thai AirAsia X", "EXPRESS WING", "Thailand", + "TAY", "TNT Airways", "QUALITY", "Belgium", + "TBA", "Tibet Airlines", "TIBET", "China", + "TBC", "Turbine Air Cargo UK", "", "United Kingdom", + "TBD", "Thunderbird Tours", "ORCA", "Canada", + "TBG", "Tropical Airways", "", "Haiti", + "TBH", "Trinity Air Bahamas", "", "Bahamas", + "TBI", "TAB Express International", "TAB INTERNATIONAL", "United States", + "TBL", "Bell Aliant Regional Communications", "TELCO", "Canada", + "TBM", "Taban Air Lines", "TABAN AIR", "Iran", + "TBN", "Teebah Airlines", "TEEBAH", "Sierra Leone", + "TBO", "Aero Taxi de Los Cabos", "AERO CABOS", "Mexico", + "TBR", "Tubelair", "TUBELAIR", "Tunisia", + "TBS", "Timbis Air", "TIMBIS", "Kenya", + "TBX", "Tobago Express", "TABEX", "Trinidad and Tobago", + "TCA", "Tropican Air Services", "TROPICANA", "Egypt", + "TCB", "Transporte Aereo De Colombia", "AERO COLOMBIA", "Colombia", + "TCC", "Trans Continental Airlines", "TRANSCAL", "Sudan", + "TCD", "Tchad Airlines", "TCHADLINES", "Chad", + "TCE", "Trans-Colorado Airlines", "TRANS-COLORADO", "United States", + "TCF", "Shuttle America", "MERCURY", "United States", + "TCG", "Thai Air Cargo", "THAI CARGO", "Thailand", + "TCH", "Transcontinental Air", "TRANS GULF", "Bahrain", + "TCL", "Coastal Air Transport", "TRANS COASTAL", "United States", + "TCM", "Teledyne Continental Motors", "TELEDYNE", "United States", + "TCN", "Trans Continental Airlines", "TRANSCON", "United States", + "TCO", "Aerotranscolombina de Carga", "TRANSCOLOMBIA", "Colombia", + "TCP", "Transcorp Airways", "TRANSCORP", "United Kingdom", + "TCR", "Lanaes Aereas Trans Costa Rica", "TICOS", "Costa Rica", + "TCT", "Transcontinental Sur", "TRANS-CONT", "Uruguay", + "TCU", "Transglobal Airways Corporation", "TRANSGLOBAL", "Philippines", + "TCV", "TACV", "CABOVERDE", "Cape Verde", + "TCX", "Thomas Cook Airlines", "KESTREL", "United Kingdom", + "TCY", "Twin Cities Air Service", "TWIN CITY", "United States", + "TDA", "Trend Aviation", "TREND AIR", "United States", + "TDB", "Welch Aviation", "THUNDER BAY", "United States", + "TDC", "Tadair", "TADAIR", "Spain", + "TDE", "Tellavia / Flight One", "TELLURIDE", "United States", + "TDG", "Air Cargo Express", "TURBO DOG", "United States", + "TDI", "Transportes Aéreos de Ixtlán", "TRANSIXTLAN", "Mexico", + "TDM", "Tandem Aero", "TANDEM", "Moldova", + "TDO", "TRADO", "TRADO", "Dominican Republic", + "TDR", "Trade Air", "TRADEAIR", "Croatia", + "TDT", "Atlas Helicopters", "TRIDENT", "United Kingdom", + "TDV", "Taxi Aero Nacional Del Evora", "TAXI EVORA", "Mexico", + "TDX", "Tradewinds Airlines", "TRADEWINDS EXPRESS", "United States", + "TDY", "Air Today", "AIR TODAY", "United States", + "TEA", "Executive Turbine Aviation", "TRAVELMAX", "South Africa", + "TEB", "Tenir Airlines", "TENIR AIR", "Kyrgyzstan", + "TEC", "ADI Shuttle Group", "TECHJET", "United States", + "TED", "Aero Servicios Azteca", "AEROAZTECA", "Mexico", + "TEE", "West Freugh DTEO", "TEEBIRD", "United Kingdom", + "TEF", "Tecnicas Fotograficas", "TECFOTO", "Spain", + "TEH", "Tempelhof Airways", "TEMPELHOF", "United States", + "TEL", "Telford Aviation", "TELFORD", "United States", + "TEM", "Tech-Mont Helicopter Company", "TECHMONT", "Slovakia", + "TEN", "Tennessee Airways", "TENNESSEE", "United States", + "TEP", "Transeuropean Airlines", "TRANSEURLINE", "Russia", + "TER", "Territorial Airlines", "TERRI-AIRE", "United States", + "TES", "Taespejo Portugal LDA", "Tesaban", "Portugal", + "TET", "Tepavia-Trans Airlines", "TEPAVIA", "Moldova", + "TEW", "Airteam Charter", "TEAMWORK", "South Africa", + "TEX", "Catex", "CATEX", "France", + "TEZ", "TezJet Airlines", "", "Kyrgyzstan", + "TFA", "Trans-Florida Airlines", "TRANS FLORIDA", "United States", + "TFB", "Tair Airways", "ROYAL TEE-AIR", "Philippines", + "TFF", "Talon Air", "TALON FLIGHT", "United States", + "TFG", "MAS Airways", "TRAFALGAR", "United Kingdom", + "TFH", "Thai Flying Helicopter Service", "THAI HELICOPTER", "Thailand", + "TFI", "Transport Facilitators", "", "United States", + "TFK", "Transafrik International", "", "São Tomé and Príncipe", + "TFL", "TUI fly Netherlands", "ORANGE", "Netherlands", + "TFN", "Norwegian Aviation College", "SPIRIT", "Norway", + "TFO", "Transportes Aéreos del Pacífico", "TRANSPORTES PACIFICO", "Mexico", + "TFT", "Thai Flying Service", "THAI FLYING", "Thailand", + "TFU", "213th Flight Unit", "THJY", "Russia", + "TFY", "Tayside Aviation", "TAYSIDE", "United Kingdom", + "TGC", "TG Aviation", "THANET", "United Kingdom", + "TGE", "Trabajos Aéreos", "TASA", "Spain", + "TGG", "Tigerair Australia", "TIGGOZ", "Australia", + "TGI", "Transportes Aéreos Regionales", "TRANSPORTE REGIONAL", "Mexico", + "TGM", "TAG Aviation Espana", "TAG ESPANA", "Spain", + "TGN", "Trigana Air Service", "TRIGANA", "Indonesia", + "TGO", "Transport Canada", "TRANSPORT", "Canada", + "TGT", "SAAB Nyge Aero", "TARGET", "Sweden", + "TGW", "Scoot", "SCOOTER", "Singapore", + "TGX", "Transair Gabon", "TRANSGABON", "Gabon", + "TGY", "Trans Guyana Airways", "TRANS GUYANA", "Guyana", + "TGZ", "Georgian Airways", "TAMAZI", "Georgia", + "THA", "Thai Airways International", "THAI", "Thailand", + "THB", "Spark Air", "THAI SABAI", "Thailand", + "THC", "Tar Heel Aviation", "TARHEEL", "United States", + "THD", "Thai Smile Airways", "THAI SMILE", "Thailand", + "THE", "Toumai Air Tchad", "TOUMAI AIR", "Chad", + "THF", "Touraine Helicoptere", "TOURAINE HELICO", "France", + "THG", "Thai Global Airline", "THAI GLOBAL", "Thailand", + "THJ", "Thai Jet Intergroup", "THAI JET", "Thailand", + "THK", "Turk Hava Kurumu Hava Taksi Isletmesi", "HUR KUS", "Turkey", + "THM", "Airmark Aviation", "THAI AIRMARK", "Thailand", + "THN", "International Security Assistance Force", "ATHENA", "Canada", + "THO", "TACA De Honduras", "LEMPIRA", "Honduras", + "THR", "Tehran Airline", "TEHRAN AIR", "Iran", + "THS", "Turkish Aerospace Industries", "TUSAS", "Turkey", + "THT", "Air Tahiti Nui", "TAHITI AIRLINES", "France", + "THU", "Thunder Airlines", "AIR THUNDER", "Canada", + "THY", "Turkish Airlines", "TURKISH", "Turkey", + "THZ", "Trans Helicoptere Service", "LYON HELIJET", "France", + "TIA", "Trans International Airlines", "TRANS INTERNATIONAL", "United States", + "TIB", "TRIP Linhas Aéreas", "TRIP", "Brazil", + "TIC", "Travel International Air Charters", "TRAVEL INTERNATIONAL", "Zambia", + "TID", "Air Tindi", "TINDI", "Canada", + "TIE", "Time Air", "TIME AIR", "Czech Republic", + "TIH", "S C Ion Tiriac", "TIRIAC AIR", "Romania", + "TIK", "Tic Air", "TICAIR", "Australia", + "TIM", "TEAM Linhas Aéreas", "TEAM BRASIL", "Brazil", + "TIN", "Taino Tours", "TAINO", "Dominican Republic", + "TIP", "C and M Aviation", "TRANSPAC", "United States", + "TIR", "Antair", "ANTAIR", "Mexico", + "TIS", "Tesis", "TESIS", "Russia", + "TIW", "Transcarga Intl Airways", "TIACA", "Venezuela", + "TJK", "Tajikair", "TAJIKAIR", "Tajikistan", + "TJN", "Tien-Shan", "NERON", "Kazakhstan", + "TJS", "Tyrolean Jet Services", "TYROLJET", "Austria", + "TJT", "Twin Jet", "TWINJET", "France", + "TKC", "Tikal Jets Airlines", "TIKAL", "Guatemala", + "TKE", "Take Air Line", "ISLAND BIRD", "France", + "TKJ", "Tarkim Aviation", "TARKIM AVIATION", "Turkey", + "TKX", "Tropical International Airways", "TROPEXPRESS", "Saint Kitts and Nevis", + "TLA", "Translift Airways", "TRANSLIFT", "Ireland", + "TLB", "Atlantique Air Assistance", "TRIPLE-A", "France", + "TLC", "Caribbean Express", "CARIB-X", "United States", + "TLD", "Aereo Taxi Autlan", "AEREO AUTLAN", "Mexico", + "TLE", "Aero Util", "AEROUTIL", "Mexico", + "TLF", "Transport Africa", "TRANS-LEONE", "Sierra Leone", + "TLK", "Starlink Aviation", "STARLINK", "Canada", + "TLL", "Trans Atlantic Airlines", "ATLANTIC LEONE", "Sierra Leone", + "TLM", "Thai Lion Mentari", "MENTARI", "Thailand", + "TLO", "Eagle Canyon Airlines", "TALON AIR", "United States", + "TLP", "Tulip Air", "TULIPAIR", "Netherlands", + "TLR", "Air Libya Tibesti", "AIR LIBYA", "Libya", + "TLS", "TLC Air", "TEALSY", "United States", + "TLT", "Turtle Airways", "TURTLE", "Fiji", + "TLU", "Aero Toluca Internactional", "AEROTOLUCA", "Mexico", + "TLV", "Travelair", "PAJAROS", "Uruguay", + "TLW", "Teamline Air", "Teamline", "Austria", + "TLX", "Telesis Transair", "TELESIS", "United States", + "TLY", "Top Fly", "TOPFLY", "Spain", + "TMA", "Trans Mediterranean Airlines", "TANGO LIMA", "Lebanon", + "TMB", "Volato", "Tombo", "United States", + "TMC", "Travel Management Company", "TRAIL BLAZER", "United States", + "TMD", "Transmandu", "TRANSMANDU", "Venezuela", + "TME", "Aero Taxi del Centro de Mexico", "TAXICENTRO", "Mexico", + "TMG", "Tri-MG Intra Asia Airlines", "TRILINES", "Indonesia", + "TMH", "Taxis Turisticos Marakame", "TAXIMARAKAME", "Mexico", + "TMI", "Tamir Airways", "TAMIRWAYS", "Israel", + "TMK", "Tomahawk Airways", "TOMAHAWK", "United States", + "TML", "Transports et Travaux Aériens de Madagascar", "TAM AIRLINE", "Madagascar", + "TMM", "TMC Airlines", "WILLOW RUN", "United States", + "TMN", "Tasman Cargo Airlines", "TASMAN", "Australia", + "TMP", "Arizona Express Airlines", "TEMPE", "United States", + "TMQ", "TRAM", "TRAM AIR", "Mauritania", + "TMR", "Timberline Air", "TIMBER", "Canada", + "TMS", "Temsco Helicopters", "TEMSCO", "United States", + "TMT", "Trans Midwest Airlines", "TRANS MIDWEST", "United States", + "TMX", "Tramon Air", "TRAMON", "South Africa", + "TMY", "Transportes Aéreos del Mundo Maya", "MUNDO MAYA", "Mexico", + "TMZ", "Transporte Amazonair", "TRANS AMAZON", "Venezuela", + "TNB", "Trans Air-Benin", "TRANS-BENIN", "Benin", + "TNC", "National Aviation Consultants", "NATCOM", "Canada", + "TND", "Aero Taxis Cessna", "TAXIS CESSNA", "Mexico", + "TNE", "Taxis Aéreos del Noroeste", "TAXINOROESTE", "Mexico", + "TNF", "Transafricaine", "TRANSFAS", "Burkina Faso", + "TNG", "Tennessee Air National Guard 164th Airlift Group", "", "United States", + "TNI", "Transair International Linhas Aéreas", "TRANSINTER", "United States", + "TNL", "Tengeriyn Ulaach Shine", "SKY HORSE", "Mongolia", + "TNM", "Tiara Air", "TIARA", "Aruba", + "TNO", "Aerotransporte de Carga Union", "AEROUNION", "Mexico", + "TNP", "Transped Aviation", "TRANSPED", "Austria", + "TNR", "Tanana Air Services", "TAN AIR", "United States", + "TNT", "Trans North Turbo Air", "TRANS NORTH", "Canada", + "TNV", "Transnorthern", "TRANSNORTHERN", "United States", + "TNW", "Trans Nation Airways", "TRANS-NATION", "Ethiopia", + "TNX", "Trener Ltd", "TRAINER", "Hungary", + "TNY", "Twin Town Leasing Company", "TWINCAL", "United States", + "TOB", "Tobruk Air", "TOBRUK AIR", "Libya", + "TOC", "Aerotropical", "TROPICMEX", "Mexico", + "TOF", "RAF Topcliffe Flying Training Unit", "TOPCLIFFE", "United Kingdom", + "TOH", "Air Tomisko", "TOMISKO CARGO", "Serbia", + "TOJ", "TOJ Airlines", "TOJ AIRLINE", "Tajikistan", + "TOK", "Airlines PNG", "BALUS", "Papua New Guinea", + "TOL", "Tol-Air Services", "TOL AIR", "United States", + "TOM", "TUI Airways", "TOM JET", "United Kingdom", + "TON", "Aero Tonala", "AEROTONALA", "Mexico", + "TOP", "Top Air", "AIR TOP", "Indonesia", + "TOR", "FlyGTA Airlines", "HOMERUN", "Canada", + "TOS", "Tropic Air", "TROPISER", "Belize", + "TOT", "Totavia", "", "Canada", + "TOW", "AirTanker Services", "TOWLINE", "United Kingdom", + "TOX", "Neosiam Airways", "SKY KINGDOM", "Thailand", + "TOY", "Toyota Canada", "TOYOTA", "Canada", + "TPA", "TAMPA", "TAMPA", "Colombia", + "TPB", "Aero Tropical", "AERO TROPICAL", "Angola", + "TPC", "Air Calédonie", "AIRCAL", "France", + "TPD", "Top Speed", "TOP SPEED", "Austria", + "TPF", "Taxis Aéreos del Pacífico", "TAXIPACIFICO", "Mexico", + "TPG", "Transportes Aéreos Pegaso", "TRANSPEGASO", "Mexico", + "TPK", "Air Horizon", "TCHAD-HORIZON", "Chad", + "TPL", "TAR Interpilot", "INTERPILOT", "Mauritania", + "TPM", "Transpaís Aéreo", "TRANSPAIS", "Mexico", + "TPN", "Transportación Aérea del Norte", "AEREA DELNORTE", "Mexico", + "TPO", "Aero Taxi del Potosi", "TAXI-POTOSI", "Mexico", + "TPP", "Transpac Express", "TRANS EXPRESS", "Australia", + "TPR", "Taxis Aéreos De Parral", "TAXIS PARRAL", "Mexico", + "TPS", "TAPSA Transportes Aéreos Petroleros", "TAPSA", "Argentina", + "TPT", "Transportes Aéreo del Sureste", "TASSA", "Mexico", + "TPU", "Trans American Airlines (Trans Am)", "TRANS PERU", "Peru", + "TPV", "Thai Pacific Airlines Business", "THAI PACIFIC", "Thailand", + "TPX", "Transportes Aéreos De Xalapa", "TRANSXALAPA", "Mexico", + "TPY", "Trans-Provincial Airlines", "TRANS PROVINCIAL", "Canada", + "TPZ", "Transportes La Paz", "TRANSPAZ", "Mexico", + "TQE", "Taxair Mexiqienses", "TAXAIR", "Mexico", + "TQF", "United Kingdom Royal VIP Flights", "RAINBOW", "United Kingdom", + "TQM", "JM Family Aviation", "TACOMA", "United States", + "TQN", "Taquan Air Services", "TAQUAN", "United States", + "TQR", "Transportación Aérea De Querétaro", "TRANSQUERETARO", "Mexico", + "TQS", "Aeroturquesa", "AEROTURQUESA", "Mexico", + "TRA", "Transavia Holland", "TRANSAVIA", "Netherlands", + "TRB", "Ukraine Transavia", "KIROVTRANS", "Ukraine", + "TRC", "Trans Air Charter", "TRACKER", "United States", + "TRD", "Trans Island Air", "TRANS ISLAND", "Barbados", + "TRF", "Taxi Air Fret", "TAXI JET", "France", + "TRG", "TRAGSA (Medios Aéreos)", "", "Spain", + "TRH", "Airmark Aviation", "TRANSTAR", "United States", + "TRI", "Executive Flight Operations Ontario Government", "TRILLIUM", "Canada", + "TRJ", "Trans Euro Air", "HIGH TIDE", "United Kingdom", + "TRK", "Turkish Airlines General Aviation", "TURKISH REPUBLIC", "Turkey", + "TRL", "Starlite Aviation", "STARSTREAM", "South Africa", + "TRM", "Transport Aerien de Mauritanie", "SOTRANS", "Mauritania", + "TRN", "Servicios Aéreos Corporativos", "AEROTRON", "Mauritania", + "TRO", "Tropic Airlines-Air Molokai", "MOLOKAI", "United States", + "TRP", "Maryland State Police", "TROOPER", "United States", + "TRR", "Tramson Limited", "TRAMSON", "Sudan", + "TRT", "Trans Arabian Air Transport", "TRANS ARABIAN", "Sudan", + "TRU", "Triangle Airline (Uganda)", "TRI AIR", "Uganda", + "TRW", "Transwestern Airlines of Utah", "TRANS-WEST", "United States", + "TRY", "Tristar Airlines", "TRISTAR AIR", "United States", + "TSA", "Transair France", "AIRTRAF", "France", + "TSC", "Air Transat", "AIR TRANSAT", "Canada", + "TSD", "TAF-Linhas Aéreas", "TAFI", "Brazil", + "TSG", "Trans-Air-Congo", "TRANS-CONGO", "Republic of the Congo", + "TSH", "R1 Airlines previously Regional 1", "TRANSCANADA", "Canada", + "TSI", "Transport'air", "TRANSPORTAIR", "France", + "TSJ", "Trast Aero", "TRAST AERO", "Kyrgyzstan", + "TSK", "Trast Aero", "TOMSK-AVIA", "Kyrgyzstan", + "TSL", "Thai Aviation Services", "THAI AVIATION", "Thailand", + "TSM", "Trans Sayegh Airport Services", "", "Lebanon", + "TSN", "Trans-Air Services", "AIR TRANS", "Nigeria", + "TSO", "Transaero Airlines", "TRANSOVIET", "Russia", + "TSP", "Transportes Aéreos Inter", "TRANSPO-INTER", "Guatemala", + "TSQ", "airtransse", "AIRTRA", "Japan", + "TSR", "TJS San Marino S.r.L.", "SAN MARINO", "San Marino", + "TSS", "Tri-State Aero", "TRI-STATE", "United States", + "TST", "TRAST", "TRAST", "Kazakhstan", + "TSU", "Gulf & Caribbean Cargo / Contract Air Cargo", "TRANSAUTO", "United States", + "TSV", "Tropair Airservices", "TROPIC", "United Kingdom", + "TSW", "Transwings", "SWISSTRANS", "Switzerland", + "TSX", "Thai Star Airlines", "THAI STAR", "Thailand", + "TSY", "Tristar Air", "TRIPLE STAR", "Egypt", + "TTA", "TTA - Sociedade de Transporte e Trabalho Aéreo", "KANIMANBO", "Mozambique", + "TTB", "Aerolíneas Turísticas del Caribe", "AERO TURISTICAS", "Mexico", + "TTC", "Transteco", "TRANSTECO", "Angola", + "TTE", "Avcenter", "TETON", "United States", + "TTF", "224th Flight Unit", "CARGO UNIT", "Russia", + "TTH", "Tarhan Tower Airlines", "", "Turkey", + "TTL", "Total Linhas Aéreas", "TOTAL", "Brazil", + "TTM", "Societe Tout Transport Mauritanien", "TOUT-AIR", "Mauritania", + "TTN", "Absolute Flight Services", "TITANIUM", "South Africa", + "TTP", "Triple O Aviation", "MIGHTY WING", "Nigeria", + "TTR", "Transportaciones Y Servicios Aéreos", "TRANSPORTACIONES", "Mexico", + "TTS", "Transporte Aéreo Técnico Ejecutivo", "TECNICO", "Mexico", + "TTW", "Tigerair Taiwan", "SMART CAT", "Taiwan", + "TTX", "Alliance Air Charters", "TWISTER", "United States", + "TUA", "Turkmenistan Airlines", "TURKMENISTAN", "Turkmenistan", + "TUC", "Turismo Aéreo de Chile", "TURICHILE", "Chile", + "TUD", "Flight Alaska", "TUNDRA", "United States", + "TUI", "TUI fly Deutschland", "TUI JET", "Germany", + "TUK", "Ocean Wings Commuter Service", "TUCKERNUCK", "United States", + "TUL", "Tulpar Air", "URSAL", "Russia", + "TUM", "Tyumenspecavia", "TUMTEL", "Russia", + "TUO", "Taxi Aéreo Turístico", "TURISTICO", "Mexico", + "TUP", "Aviastar-Tu", "TUPOLEVAIR", "Russia", + "TUR", "ATUR", "", "Ecuador", + "TUS", "ABSA Cargo", "Turismo", "Brazil", + "TUY", "Línea Turística Aereotuy", "AEREOTUY", "Venezuela", + "TUZ", "Tuna Aero", "TUNA", "Sweden", + "TVA", "Trans America Airlines", "TRANS-AMERICA", "United States", + "TVF", "Transavia France", "FRANCE SOLEIL", "France", + "TVH", "Trabajos Aéreos Vascongados", "TRAVASA", "Spain", + "TVI", "Tiramavia", "TIRAMAVIA", "Moldova", + "TVJ", "Thai Vietjet Air", "THAIVIET JET", "Thailand", + "TVL", "Smartwings Hungary", "TRAVEL SERVICE", "Hungary", + "TVO", "Transavio", "TRANS-BALLERIO", "Italy", + "TVP", "Smartwings Poland", "JETTRAVEL", "Poland", + "TVQ", "Smartwings Slovakia", "SLOVAKTRAVEL", "Slovakia", + "TVR", "Tavrey Airlines", "TAVREY", "Ukraine", + "TVS", "Smartwings", "SKYTRAVEL", "Czech Republic", + "TWA", "Trans World Airlines", "TWA", "United States", + "TWB", "T'way Air", "TWAYAIR", "Republic of Korea", + "TWE", "Transwede Airways", "TRANSWEDE", "Sweden", + "TWF", "247 Jet Ltd", "CLOUD RUNNER", "United Kingdom", + "TWG", "air-taxi Europe", "TWINGOOSE", "Germany", + "TWI", "Tailwind Airlines", "TAILWIND", "Turkey", + "TWJ", "Twinjet Aircraft Sales", "", "United Kingdom", + "TWL", "Tradewinds Aviation", "TRADEWINDS CANADA", "Canada", + "TWM", "Transairways", "", "Mozambique", + "TWN", "Avialeasing Aviation Company", "TWINARROW", "Uzbekistan", + "TWO", "Twente Airlines", "COLIBRI", "Netherlands", + "TWW", "Trans Air Welwitchia", "WELWITCHIA", "Angola", + "TWY", "Sunset Aviation LLC", "TWILIGHT", "United States", + "TXA", "Texair Charter", "OKAY AIR", "United States", + "TXB", "Bell Helicopter Textron", "TEXTRON", "Canada", + "TXC", "TransAVIAexport Airlines", "TRANSEXPORT", "Belarus", + "TXD", "Aerotaxis del Noroeste", "TAXI OESTE", "Mexico", + "TXE", "Transilvania Express", "TRANSAIR EXPRESS", "Romania", + "TXF", "Aerotaxis Alfe", "ALFE", "Mexico", + "TXI", "Aereotaxis", "AEREOTAXIS", "Mexico", + "TXL", "Taxi Aéreo Cozatl", "TAXI COZATL", "Mexico", + "TXM", "Taxi Aéreo de México", "TAXIMEX", "Mexico", + "TXN", "Texas National Airlines", "TEXAS NATIONAL", "United States", + "TXO", "Taxis Aéreos de Sinaloa", "TAXIS SINALOA", "Mexico", + "TXR", "Taxirey", "TAXIREY", "Mexico", + "TXS", "Texas Airlines", "TEXAIR", "United States", + "TXT", "Texas Air Charters", "TEXAS CHARTER", "United States", + "TXU", "ATESA Aerotaxis Ecuatorianos", "ATESA", "Ecuador", + "TXV", "Puerto Vallarta Taxi Aéreo", "TAXIVALLARTA", "Mexico", + "TXZ", "Tex Star Air Freight", "TEX STAR", "United States", + "TYA", "NordStar", "TAIMYR", "Russia", + "TYF", "Tayflite", "TAYFLITE", "United Kingdom", + "TYG", "Trygg-Flyg", "TRYGG", "Sweden", + "TYJ", "TJS Malta Ltd.", "TYROLMALTA", "Malta", + "TYR", "Tyrolean Airways", "TYROLEAN", "Austria", + "TYW", "Tyrol Air Ambulance", "TYROL AMBULANCE", "Austria", + "TZA", "Aero Tomza", "AERO TOMZA", "Mexico", + "TZE", "Transporte Aéreo Ernesto Saenz", "TRANSPORTE SAENZ", "Mexico", + "TZK", "Tajikistan International Airlines", "TAJIKISTAN", "Tajikistan", + "TZP", "Zipair Tokyo", "ZIPPY", "Japan", + "TZT", "Air Zambezi", "ZAMBEZI", "Zimbabwe", + "TZU", "Servicios Aéreos Tamazula", "TAMAZULA", "Mexico", + "UAA", "University Air Squadron", "", "United Kingdom", + "UAB", "United Arabian Airlines", "UNITED ARABIAN", "Sudan", + "UAC", "United Air Charters", "UNITAIR", "Zimbabwe", + "UAD", "University Air Squadron", "", "United Kingdom", + "UAE", "Emirates Airlines", "EMIRATES", "United Arab Emirates", + "UAF", "United Arab Emirates Air Force", "UNIFORCE", "United Arab Emirates", + "UAG", "Afra Airlines", "AFRALINE", "Ghana", + "UAH", "Air Experience Flight  Cranwell", "", "United Kingdom", + "UAI", "Union Africaine des Transports", "UNAIR", "Ivory Coast", + "UAJ", "University Air Squadron", "", "United Kingdom", + "UAK", "Kiev Aviation Plant", "AVIATION PLANT", "Ukraine", + "UAL", "United Airlines", "UNITED", "United States", + "UAR", "Aerostar Airlines", "AEROSTAR", "Ukraine", + "UAS", "University Air Squadron", "", "United Kingdom", + "UBA", "Myanma Airways", "UNIONAIR", "Myanmar", + "UBD", "United Airways", "UNITED BANGLADESH", "Bangladesh", + "UBG", "US-Bangla Airlines", "BANGLA STAR", "Bangladesh", + "UCA", "CommutAir", "COMMUTAIR", "United States", + "UCC", "Uganda Air Cargo", "UGANDA CARGO", "Uganda", + "UCG", "Uniworld Air Cargo", "UNIWORLD", "Panama", + "UCH", "US Airports Air Charter", "US CHARTER", "United States", + "UCK", "Air Division of the Eastern Kazakhstan Region", "GALETA", "Kazakhstan", + "UCO", "Ucoaviacion", "UCOAVIACION", "Spain", + "UCR", "Aero-Charter Ukraine", "CHARTER UKRAINE", "Ukraine", + "UCS", "United Carriers Systems", "UNITED CARRIERS", "United States", + "UDA", "Psudiklat Perhubungan Udara/PLP", "UDARA", "Indonesia", + "UDC", "DonbassAero", "DONBASS AERO", "Ukraine", + "UDN", "Dniproavia", "DNIEPRO", "Ukraine", + "UEA", "United Eagle Airlines", "UNITED EAGLE", "China", + "UED", "Air LA", "AIR L-A", "United States", + "UEJ", "Jetcorp", "JETCORP", "United States", + "UES", "Ues-Avia Aircompany", "AVIASYSTEM", "Ukraine", + "UEU", "United European Airlines", "UNITED EUROPEAN", "Romania", + "UFA", "State Flight Academy of Ukraine", "FLIGHT ACADEMY", "Ukraine", + "UFS", "United Feeder Service", "FEEDER EXPRESS", "United States", + "UGA", "Air Uganda", "UGANDA", "Uganda", + "UGC", "Urgemer Canarias", "URGEMER", "Spain", + "UGD", "Uganda Airlines", "CRESTED", "Uganda", + "UGL", "Inter-Island Air", "UGLY VAN", "United States", + "UGN", "Yuzhnaya Aircompany", "PLUTON", "Kazakhstan", + "UGP", "Shar Ink", "SHARINK", "Russia", + "UHL", "Ukrainian Helicopters", "UKRAINE COPTERS", "Ukraine", + "UHS", "Ulyanovsk Higher Civil Aviation School", "PILOT AIR", "Russia", + "UIA", "UNI Air", "GLORY", "Taiwan", + "UIT", "University of Tromsø School of Aviation", "ARCTIC", "Norway", + "UJR", "Universal Jet Rental de Mexico", "UNIVERSAL JET", "Mexico", + "UJT", "Universal Jet Aviation", "UNI-JET", "United States", + "UJX", "Atlas Ukraine Airlines", "ATLAS UKRAINE", "Ukraine", + "UKA", "Buzzaway Limited", "UKAY", "United Kingdom", + "UKI", "UK International Airlines", "KHALIQ", "United Kingdom", + "UKL", "Ukraine Air Alliance", "UKRAINE ALLIANCE", "Ukraine", + "UKM", "UM Airlines", "UKRAINE MEDITERRANEE", "Ukraine", + "UKN", "Ukraine Air Enterprise", "ENTERPRISE UKRAINE", "Ukraine", + "UKP", "National Police Air Service", "POLICE", "United Kingdom", + "UKR", "Air Ukraine", "AIR UKRAINE", "Ukraine", + "UKS", "Ukrainian Cargo Airways", "CARGOTRANS", "Ukraine", + "UKU", "Second Sverdlovsk Air Enterprise", "PYSHMA", "Russia", + "UKW", "Lviv Airlines", "UKRAINE WEST", "Ukraine", + "ULH", "Ultimate HELI", "ULTIMATEHELI", "South Africa", + "ULR", "Ultimate Air", "VIPER", "South Africa", + "ULS", "Carroll Air Service", "ULSTER", "United States", + "ULT", "Ultrair", "ULTRAIR", "United States", + "UMA", "Líneas Aéreas del Humaya", "HUMAYA", "Mexico", + "UMB", "Air Umbria", "AIR UMBRIA", "Italy", + "UMK", "Yuzhmashavia", "YUZMASH", "Ukraine", + "UNC", "Uni-Fly", "UNICOPTER", "Denmark", + "UND", "Atuneros Unidos de California", "ATUNEROS UNIDOS", "Mexico", + "UNF", "Union Flights", "UNION FLIGHTS", "United States", + "UNJ", "Universal Jet", "PROJET", "Spain", + "UNO", "United Nations", "UNITED NATIONS", "n/a", + "UNR", "Rivne Universal Avia", "RIVNE UNIVERSAL", "Ukraine", + "UNS", "Unsped Paket Servisi", "UNSPED", "Turkey", + "UNT", "Servicios Aéreos Universitarios", "UNIVERSITARIO", "Mexico", + "UNU", "Unifly Servizi Aerei", "UNIEURO", "Italy", + "UNY", "Lund University School of Aviation", "UNIVERSITY", "Sweden", + "UPL", "Ukrainian Pilot School", "PILOT SCHOOL", "Ukraine", + "UPS", "United Parcel Service", "UPS", "United States", + "URA", "Aircompany Rosavia", "ROSAVIA", "Ukraine", + "URF", "Surf Air", "SURF AIR", "United States", + "URG", "Air Urga", "URGA", "Ukraine", + "URJ", "Star Air", "STARAV", "Pakistan", + "URN", "Turan Air", "TURAN", "Azerbaijan", + "URP", "ARP 410 Airlines", "AIR-ARP", "Ukraine", + "URV", "Uraiavia", "URAI", "Russia", + "URY", "Century Aviation", "CENTURY AVIA", "Mexico", + "USB", "Tusheti", "TUSHETI", "Georgia", + "USC", "AirNet Express", "STAR CHECK", "United States", + "USF", "USAfrica Airways", "AFRICA EXPRESS", "United States", + "USH", "US Helicopter", "US-HELI", "United States", + "USJ", "US Jet", "USJET", "United States", + "USK", "Skif-Air", "SKIF-AIR", "Ukraine", + "USN", "Smarkand Aero Servise", "SAMAS", "Uzbekistan", + "UST", "Austro Aéreo", "AUSTRO AEREO", "Ecuador", + "USW", "Special Aviation Works", "AKSAR", "Uzbekistan", + "USX", "US Express", "AIR EXPRESS", "United States", + "UTA", "UTair Aviation", "UTAIR", "Russia", + "UTM", "TAPC Aviatrans Aircompany", "AVIATAPS", "Uzbekistan", + "UTN", "UTair-Ukraine", "UT Ukraine", "Ukraine", + "UTR", "Utair South Africa", "AIRUT", "South Africa", + "UTS", "Ukrainian State Air Traffic Service Enterprise", "AIRRUH", "Ukraine", + "UTT", "Transarabian Transportation Services", "ARABIAN TRANSPORT", "Uganda", + "UTU", "Urartu-Air", "", "Armenia", + "UTX", "Avfinity", "", "United States", + "UTY", "Alliance Airlines", "UNITY", "Australia", + "UVA", "Universal Airways", "UNIVERSAL", "United States", + "UVG", "Universal Airlines", "GUYANA JET", "Guyana", + "UVM", "Uvavemex", "UVAVEMEX", "Mexico", + "UVN", "United Aviation", "UNITED AVIATION", "Kuwait", + "UVT", "Auvia Air", "AUVIA", "Indonesia", + "UYA", "Yute Air Alaska", "", "United States", + "UZA", "Constanta Airline", "CONSTANTA", "Ukraine", + "UZB", "Uzbekistan Airways", "UZBEK", "Uzbekistan", + "UZS", "Samarkand Airways", "SOGDIANA", "Uzbekistan", + "VAA", "Van Air Europe", "EUROVAN", "Czech Republic", + "VAB", "Airtrans Ltd", "", "Russia", + "VAC", "Vacationair", "VACATIONAIR", "Canada", + "VAD", "Aero Taxi Los Valles", "VALLES", "Spain", + "VAE", "Air Evans", "AIR-EVANS", "Spain", + "VAG", "Vietravel Airlines", "VIETRAVEL AIR", "Vietnam", + "VAI", "Avalair", "AIR AVALAIR", "Serbia", + "VAL", "Voyageur Airways", "VOYAGEUR", "Canada", + "VAM", "Ameravia", "AMERAVIA", "Uruguay", + "VAN", "Caravan Air", "CAMEL", "Mauritania", + "VAP", "Phuket Air", "PHUKET AIR", "Thailand", + "VAR", "Veca Airlines", "VECA", "El Salvador", + "VAS", "ATRAN Cargo Airlines", "ATRAN", "Russian Federation", + "VAT", "Visionair", "VISIONAIR", "Ireland", + "VAW", "Fly2Sky", "SOFIA JET", "Bulgaria", + "VAZ", "Airlines 400", "REMONT AIR", "Russia", + "VBA", "V Bird Airlines Netherlands", "VEEBEE", "Netherlands", + "VBC", "AVB-2004 Ltd", "AIR VICTOR", "Bulgaria", + "VBD", "V-Berd-Avia", "VEEBIRD-AVIA", "Armenia", + "VBG", "North-West Air Transport Company - Vyborg", "VYBORG AIR", "Russia", + "VBW", "Air Burkina", "BURKINA", "Burkina Faso", + "VCA", "VICA - Viacao Charter Aéreos", "VICA", "Brazil", + "VCB", "Real Aero Club de Vizcaya", "", "Spain", + "VCH", "Consorcio Helitec", "CONSORCIO HELITEC", "Venezuela", + "VCI", "CI-Tours", "CI-TOURS", "Ivory Coast", + "VCM", "Volare Air Charter Company", "CARMEN", "United States", + "VCN", "Execujet Charter", "AVCON", "Switzerland", + "VCR", "Cruiser Linhas Aéreas", "VOE CRUISER", "Brazil", + "VCT", "Viscount Air Service", "VISCOUNT AIR", "United States", + "VCV", "Conviasa", "CONVIASA", "Venezuela", + "VCX", "Ocean Airlines", "OCEANCARGO", "Italy", + "VDA", "Volga-Dnepr Airlines", "VOLGA", "Russia", + "VDO", "Servicios Aéreos Avandaro", "AVANDARO", "Mexico", + "VDR", "Voldirect", "VOLDIR", "France", + "VEA", "Vega Airlines", "VEGA AIRLINES", "Bulgaria", + "VEC", "Venescar Internacional", "VECAR", "Venezuela", + "VEE", "Victor Echo", "VICTOR ECHO", "Spain", + "VEG", "Aerovega", "AEROVEGA", "Mexico", + "VEJ", "Aero Ejecutivos", "VENEJECUTIV", "Venezuela", + "VEN", "Transaven", "TRANSAVEN AIRLINE", "Venezuela", + "VER", "Almaver", "ALMAVER", "Mexico", + "VES", "Vieques Air Link", "VIEQUES", "United States", + "VFC", "Vietnam Air Services Company (VASCO)", "VASCO AIR", "Vietnam", + "VFT", "VZ Flights", "ZETA FLIGHTS", "Mexico", + "VGC", "Vanguardia en Aviación en Colima", "VANGUARDIA COLIMA", "Mexico", + "VGD", "Vanguard Airlines", "VANGUARD AIR", "United States", + "VGF", "Aerovista Gulf Express", "VISTA GULF", "United Arab Emirates", + "VGN", "Virgin Nigeria Airways", "VIRGIN NIGERIA", "Nigeria", + "VGO", "Sabaidee Airways", "VIRGO", "Thailand", + "VGS", "Stichting Vliegschool 16Hoven", "SMART", "Netherlands", + "VGV", "Vologda State Air Enterprise", "VOLOGDA AIR", "Russia", + "VHA", "VH-Air Industrie", "AIR V-H", "Angola", + "VHM", "VHM Schul-und-Charterflug", "EARLY BIRD", "Germany", + "VHT", "Corporate Flight International", "VEGAS HEAT", "United States", + "VIB", "Vibroair Flugservice", "VITUS", "Germany", + "VIC", "VIP Servicios Aéreos Ejecutivos", "VIP-EJECUTIVO", "Mexico", + "VIE", "VIP Empresarial", "VIP EMPRESARIAL", "Mexico", + "VIF", "VIF Luftahrt", "VIENNA FLIGHT", "Austria", + "VIG", "Vega Aviation", "VEGA AVIATION", "Sudan", + "VIH", "Vichi", "VICHI", "Moldova", + "VIK", "Viking Airlines", "SWEDJET", "Sweden", + "VIL", "V I Airlink", "TURTLE DOVE", "British Virgin Islands", + "VIM", "Air VIA", "CRYSTAL", "Bulgaria", + "VIN", "Vinair Aeroserviços", "VINAIR", "Portugal", + "VIP", "Tag Aviation UK", "SOVEREIGN", "United Kingdom", + "VIR", "Virgin Atlantic", "VIRGIN", "United Kingdom", + "VIV", "Aeroenlaces Nacionales", "AEROENLACES", "Mexico", + "VIZ", "Aerovis Airlines", "AEROVIZ", "Ukraine", + "VJA", "ValuJet Airlines", "CRITTER", "United States", + "VJC", "Vietjet Air", "VIETJET", "Vietnam", + "VJE", "AvJet Routing", "", "United Arab Emirates", + "VJM", "Viajes Ejecutivos Mexicanos", "VIAJES MEXICANOS", "Mexico", + "VJT", "Vistajet", "VISTA", "Canada", + "VLA", "Valan International Cargo Charter", "NALAU", "South Africa", + "VLB", "Air Volta", "VOLTA", "Bulgaria", + "VLE", "C.A.I. Second", "VOLA", "Italy", + "VLF", "DFS UK Limited", "VOLANTE", "United Kingdom", + "VLG", "Vueling Airlines", "VUELING", "Spain", + "VLK", "Vladivostok Air", "VLADAIR", "Russia", + "VLL", "RAF Valley SAR Training Unit", "", "United Kingdom", + "VLN", "Valan Limited", "VALAN", "Moldova", + "VLR", "Aerolíneas Villaverde", "VILLAVERDE", "Mexico", + "VLS", "Aero Virel", "VIREL", "Mexico", + "VLT", "Vertical-T Air Company", "VERTICAL", "Russia", + "VLU", "Valuair", "VALUAIR", "Singapore", + "VLV", "Avialift Vladivostok", "VLADLIFT", "Russia", + "VLX", "Biz Jet Charter", "AVOLAR", "United States", + "VMA", "Vero Monmouth Airlines", "VERO MONMOUTH", "United States", + "VME", "Aviación Comercial de América", "AVIAMERICA", "Mexico", + "VMM", "Grupo Vuelos Mediterraneo", "VUELOS MED", "Spain", + "VMP", "Execujet Scandinavia", "VAMPIRE", "Denmark", + "VMS", "His Majesty King Maha Vajiralongkorn", "VICTOR MIKE[32]", "Thailand", + "VMX", "Aeroventas de Mexico", "VENTA", "Mexico", + "VNA", "Empresa Aviación Interamericana", "EBBA", "Uruguay", + "VNE", "Empresa Venezolana", "VENEZOLANA", "Venezuela", + "VNG", "Aero Servicios Vanguardia", "VANGUARDIA", "Mexico", + "VNK", "Vipport Joint Stock Company", "", "Russia", + "VNL", "Vanilla Air", "VANILLA", "Japan", + "VNT", "Avient Air Zambia", "AVIENT", "Zambia", + "VNX", "Fly Advance", "VANCE", "United States", + "VNZ", "Tbilaviamsheni", "TBILAVIA", "Georgia", + "VOA", "Viaggio Air", "VIAGGIO", "Bulgaria", + "VOE", "Volotea", "VOLOTEA", "Spain", + "VOG", "Voyager Airlines", "VOYAGER AIR", "Bangladesh", + "VOI", "Volaris", "VOLARIS", "Mexico", + "VOL", "Blue Chip Jet", "BLUE SPEED", "Sweden", + "VOR", "Flight Calibration Services Ltd.", "FLIGHT CAL", "United Kingdom", + "VOS", "Rovos Air", "ROVOS", "South Africa", + "VOZ", "Virgin Australia", "VELOCITY", "Australia", + "VPA", "DanubeWings", "VIP TAXI", "Slovakia", + "VPB", "Veteran Air", "VETERAN", "Ukraine", + "VPV", "VIP-Avia", "VIP AVIA", "Georgia", + "VRA", "Vertair", "VERITAIR", "United Kingdom", + "VRB", "Silverback Cargo Freighters", "SILVERBACK", "Rwanda", + "VRC", "Taxi de Veracruz", "VERACRUZ", "Mexico", + "VRD", "Virgin America", "REDWOOD", "United States", + "VRE", "Volare Airlines", "UKRAINE VOLARE", "Ukraine", + "VRI", "Aerotaxi Villa Rica", "VILLARICA", "Mexico", + "VRL", "Voar Lda", "VOAR LINHAS", "Angola", + "VRN", "Varig", "VARIG", "Brazil", + "VRO", "Aerovitro", "AEROVITRO", "Mexico", + "VRS", "Sirvair", "VAIRSA", "Mexico", + "VRT", "Averitt Air Charter", "AVERITT", "United States", + "VSA", "Avstar Aviation", "STARBIRD", "South Africa", + "VSB", "Vickers Limited", "VICKERS", "United Kingdom", + "VSC", "Aeronautic de Los Pirineos", "", "Spain", + "VSG", "AirClass Airways", "VISIG", "Spain", + "VSN", "Vision Airways Corporation", "VISION", "Canada", + "VSO", "Voronezh Aircraft Manufacturing Society", "VASO", "Russia", + "VSR", "Aviostart AS", "AVIOSTART", "Bulgaria", + "VSS", "Virign Islands Seaplane Shuttle", "WATERBIRD", "United States", + "VSV", "Scat Air", "VLASTA", "Kazakhstan", + "VTA", "Air Tahiti", "AIR TAHITI", "French Polynesia", + "VTB", "Jet Stream Charter KFT.", "SUXAIR", "Hungary", + "VTC", "Vuelos Especializados Tollocan", "VUELOS TOLLOCAN", "Mexico", + "VTE", "Contour Airlines", "VOLUNTEER", "United States", + "VTG", "Aviação Transportes Aéreos e Cargas", "ATACARGO", "Angola", + "VTH", "Vuelos Corporativos de Tehuacan", "VUELOS TEHUACAN", "Mexico", + "VTI", "Vistara", "Vistara", "India", + "VTK", "Vostok Airlines", "VOSTOK", "Russia", + "VTL", "Victor Tagle Larrain", "VITALA", "Chile", + "VTM", "Aeronaves TSM", "AERONAVES TSM", "Mexico", + "VTS", "Everts Air Alaska/Everts Air Cargo", "EVERTS", "United States", + "VTT", "Avia Trans Air Transport", "VIATRANSPORT", "Sudan", + "VTV", "Vointeh", "VOINTEH", "Bulgaria", + "VTX", "Verataxis", "VERATAXIS", "Mexico", + "VTY", "Air Midwest (Nigeria)", "VICTORY", "Nigeria", + "VUE", "AD Aviation", "FLIGHTVUE", "United Kingdom", + "VUL", "Elios", "ELIOS", "Italy", + "VUO", "Aerovuelox", "AEROVUELOX", "Mexico", + "VUS", "Vuela Bus", "VUELA BUS", "Mexico", + "VVC", "Viva Air Colombia", "Viva Air Colombia", "Colombia", + "VVF", "Dunyaya Bakis Hava Tasimaciligi", "WORLDFOCUS", "Turkey", + "VVG", "Aerovilla", "AEROVILLA", "Colombia", + "VVV", "Valair Aviação Lda", "VALAIRJET", "Portugal", + "VXG", "Avirex", "AVIREX-GABON", "Gabon", + "VXN", "Sunset Aviation", "VIXEN", "United States", + "VXP", "Avelo Airlines", "AVELO", "United States", + "VXX", "Aviaexpress Aircompany", "EXPRESSAVIA", "Ukraine", + "VYT", "RAF Valley Flying Training Unit", "ANGLESEY", "United Kingdom", + "VZL", "Vzlyet", "VZLYET", "Russia", + "VZR", "Aviazur", "IAZUR", "France", + "WAA", "Westair Aviation", "WESTAIR WINGS", "Namibia", + "WAB", "Aero Industries Inc", "WABASH", "United States", + "WAC", "West African Cargo Airlines", "WESTAF CARGO", "Mauritania", + "WAD", "RAF Waddington", "VULCAN", "United Kingdom", + "WAE", "Western Air Express", "WESTERN EXPRESS", "United States", + "WAF", "Flamenco Airways", "FLAMENCO", "United States", + "WAG", "Wisconsin Air National Guard", "", "United States", + "WAL", "Western Arctic Air", "WESTERN ARCTIC", "Canada", + "WAM", "Air Taxi & Cargo", "TAXI CARGO", "Sudan", + "WAN", "Wataniya Airways", "WATANIYA", "Kuwait", + "WAP", "Arrow Panama", "ARROW PANAMA", "Panama", + "WAR", "NZ Warbirds Association", "WARBIRDS", "New Zealand", + "WAS", "Walsten Air Services", "WALSTEN", "Canada", + "WAT", "Wings Air Transport", "", "Sudan", + "WAV", "Warbelow's Air Ventures", "WARBELOW", "United States", + "WAW", "Wings Airways", "WING SHUTTLE", "United States", + "WAY", "Airways", "GARONNE", "France", + "WAZ", "Wizz Air Abu Dhabi", "WIZZ SKY", "United Arab Emirates", + "WBA", "Finncomm Airlines", "WESTBIRD", "Finland", + "WBR", "Multi-Aero", "WEBER", "United States", + "WCA", "West Coast Airways", "WEST-LEONE", "Sierra Leone", + "WCB", "West Africa Airlines", "KILO YANKEE", "Ghana", + "WCC", "West Coast Charters", "WEST COAST", "United States", + "WCG", "West Coast Airlines", "WHISKY INDIA", "Ghana", + "WCO", "Columbia Helicopters", "COLUMBIA HELI", "United States", + "WCP", "Primaris Airlines", "WHITECAP", "United States", + "WCR", "West Caribbean Costa Rica", "WEST CARIBBEAN", "Costa Rica", + "WCW", "West Caribbean Airways", "WEST", "Colombia", + "WCY", "Viking Express", "TITAN AIR", "United States", + "WDA", "Wimbi Dira Airways", "WIMBI DIRA", "Democratic Republic of Congo", + "WDG", "Ministry of Agriculture Fisheries and Food", "WATCHDOG", "United Kingdom", + "WDK", "Oxford Air Services", "WOODSTOCK", "United Kingdom", + "WDL", "WDL Aviation", "WDL", "Germany", + "WDR", "Air Net Private Charter", "WIND RIDER", "United States", + "WDS", "Four Winds Aviation", "WINDS", "United States", + "WDY", "Phoenix Airline Services", "WINDYCITY", "United States", + "WEA", "White Eagle Aviation", "WHITE EAGLE", "Poland", + "WEB", "WebJet Linhas Aéreas", "WEB-BRASIL", "Brazil", + "WEC", "Universal Airlines", "AIRGO", "United States", + "WEL", "Veles Ukrainian Aviation Company", "VELES", "Ukraine", + "WEN", "WestJet Encore", "ENCORE", "Canada", + "WES", "Western Express Air Lines", "WEST EX", "Canada", + "WEV", "Victoria International Airways", "VICTORIA UGANDA", "Uganda", + "WEW", "West Wind Aviation", "WESTWIND", "Canada", + "WEX", "Wings Express", "WINGS EXPRESS", "United States", + "WFC", "Swift Copters", "SWIFTCOPTERS", "Switzerland", + "WFD", "BAE Systems", "AVRO", "United Kingdom", + "WFO", "Wilbur's Flight Operations", "WILBURS", "United States", + "WFT", "Aircharters Worldwide", "WORLD FLIGHT", "United States", + "WGA", "Vega Air Company", "WEGA FRANKO", "Ukraine", + "WGN", "Western Global Airlines", "WESTERN GLOBAL", "United States", + "WGP", "Williams Grand Prix Engineering", "GRAND PRIX", "United Kingdom", + "WGS", "Airwings oy", "AIRWINGS", "Finland", + "WGT", "Volkswagen AirService GmbH", "WORLDGATE", "United Kingdom", + "WHE", "Westland Helicopters", "WESTLAND", "United Kingdom", + "WHH", "Richy Skylark", "", "Sri Lanka", + "WHR", "Hummingbird Helicopter Service", "WHIRLEYBIRD", "United States", + "WHS", "Wiking Helikopter Service", "WEEKING", "Germany", + "WHT", "White", "WHITEJET", "Portugal", + "WHY", "Air Sorel", "AIR SOREL", "Canada", + "WIA", "Windward Islands Airways International", "WINDWARD", "Netherlands", + "WIF", "Widerøe", "WIDEROE", "Norway", + "WIG", "Wiggins Airways", "WIGGINS AIRWAYS", "United States", + "WIL", "Aero Air", "WILLIAMETTE", "United States", + "WIN", "Winlink", "WINLINK", "Saint Lucia", + "WIS", "Paccair", "WISCAIR", "United States", + "WIT", "RAF Wittering", "STRIKER", "United Kingdom", + "WIW", "V-avia Airline", "VEE-AVIA", "Ukraine", + "WIZ", "Micromatter Technology Solutions", "WIZARD", "United Kingdom", + "WJA", "WestJet", "WESTJET", "Canada", + "WKH", "Kharkov Aircraft Manufacturing Company", "WEST-KHARKOV", "Ukraine", + "WLA", "Airwaves Airlink", "AIRLIMITED", "Zambia", + "WLB", "Wings of Lebanon Aviation", "WING LEBANON", "Lebanon", + "WLC", "Welcome Air", "WELCOMEAIR", "Austria", + "WLG", "Air Volga", "GOUMRAK", "Russia", + "WLK", "Skyrover CC", "SKYWATCH", "South Africa", + "WLR", "Air Walser", "AIRWALSER", "Italy", + "WLS", "Air Wales Virtual", "WALES", "United Kingdom", + "WLT", "Aviation Partners", "WINGLET", "United States", + "WLV", "Aviation North", "WOLVERINE", "United States", + "WLX", "West Air Luxembourg", "WEST LUX", "Luxembourg", + "WML", "Chantilly Air", "MARLIN", "United States", + "WMT", "Wizz Air Malta", "WIZZ AIR MALTA", "Malta", + "WNA", "Winair", "WINAIR", "United States", + "WNR", "Wondair on Demand Aviation", "WONDAIR", "Spain", + "WOA", "World Airways", "WORLD", "United States", + "WOK", "Kovar Air", "WOKAIR", "Czech Republic", + "WOL", "Wings Aviation", "WINGJET", "Guyana", + "WON", "Wings Air", "WINGS ABADI", "Indonesia", + "WOW", "WOW air", "WOW air", "Iceland", + "WPA", "Western Pacific Airservice", "WESTPAC", "Solomon Islands", + "WPK", "Air-Lift Associates", "WOLFPACK", "United States", + "WPR", "Auckland Regional Rescue Helicopter Trust", "WESTPAC RESCUE", "New Zealand", + "WPT", "Wapiti Aviation", "WAPITI", "Canada", + "WRA", "White River Air Services", "", "Canada", + "WRF", "Wright Air Service", "WRIGHT FLYER", "United States", + "WRR", "WRA Inc", "WRAP AIR", "United States", + "WSA", "Westgates Airlines", "WESTATES", "United States", + "WSC", "Westair Cargo Airlines", "WESTCAR", "Côte d'Ivoire", + "WSF", "West African Airlines", "", "Benin", + "WSG", "Wasaya Airways", "WASAYA", "Canada", + "WSI", "Wind Spirit Air", "WIND SPIRIT", "United States", + "WSL", "Westflight Aviation", "WEST LINE", "United Kingdom", + "WSM", "Wisman Aviation", "WISMAN", "United States", + "WSN", "Advanced Air", "WINGSPAN", "United States", + "WST", "Western Air", "WESTERN BAHAMAS", "Bahamas", + "WSW", "Swoop", "SWOOP", "Canada", + "WTA", "Africa West", "WEST TOGO", "Togo", + "WTC", "Weasua Air Transport Company", "WATCO", "Liberia", + "WTF", "West African Air Transport", "WESTAF AIRTRANS", "Senegal", + "WTN", "BAE Systems", "TARNISH", "United Kingdom", + "WTP", "Westpoint Air", "WESTPOINT", "Canada", + "WTV", "Western Aviators", "WESTAVIA", "United States", + "WUK", "Wizz Air UK", "WIZZ GO", "United Kingdom", + "WVA", "Hand D Aviation", "WABASH VALLEY", "United States", + "WWD", "Westward Airways", "WESTWARD", "United States", + "WWG", "Aereo WWG", "AERO-W", "Mexico", + "WWI", "Worldwide Jet Charter", "WORLDWIDE", "United States", + "WWL", "Whyalla Airlines", "", "Australia", + "WWM", "World Wing Aviation", "MANAS WING", "Kyrgyzstan", + "WWS", "Worldwide Aviation Services", "", "Pakistan", + "WWW", "Janet", "JANET", "United States", + "WYC", "Wycombe Air Centre", "WYCOMBE", "United Kingdom", + "WYG", "Wyoming Airlines", "WYOMING", "United States", + "WYT", "2 Sqn No 1 Elementary Flying Training School", "WYTON", "United Kingdom", + "WZZ", "Wizz Air", "WIZZAIR", "Hungary", + "XAA", "Aeronautical Radio Inc", "", "United States", + "XAB", "Xabre Aerolineas", "AERO XABRE", "Mexico", + "XAC", "Air Charter World", "", "United States", + "XAD", "Certified Air Dispatch", "", "United States", + "XAE", "Xair", "AURA", "Czech Republic", + "XAF", "Executive Air Fleet", "", "United States", + "XAH", "Executive Aircraft Services", "", "United Kingdom", + "XAK", "Airkenya", "SUNEXPRESS", "Kenya", + "XAM", "AMR Services Corporation", "ALLIANCE", "United States", + "XAO", "Airline Operations Services", "", "United States", + "XAP", "Direct Air trading as Midway Connection", "MID-TOWN", "United States", + "XAR", "XpressAir", "XPRESS", "Indonesia", + "XAS", "PHH Aviation System", "", "United States", + "XAT", "AT and T Aviation Division", "", "United States", + "XAU", "Aerolink Uganda", "PEARL", "Uganda", + "XAV", "Aviaprom Enterprises", "AVIAPROM", "Russia", + "XAX", "AirAsia X", "XANADU", "Malaysia", + "XBG", "City of Bangor", "", "United States", + "XBO", "Baseops International", "", "United States", + "XCA", "Colt Transportes Aereos", "COLT", "Brazil", + "XCC", "Ecoturistica de Xcalak", "XCALAK", "Mexico", + "XCL", "Contel ASC", "", "United States", + "XCO", "Compuflight Operations Service", "", "United States", + "XCS", "Compuserve Incorporated", "", "United States", + "XCT", "Aero Costa Taxi Aéreo", "AEROCOSTAXI", "Mexico", + "XCX", "Citibank", "", "United States", + "XDA", "Bureau Veritas", "", "France", + "XDD", "Lockheed DUATS", "", "United States", + "XDS", "Dispatch Services", "", "United States", + "XDT", "Date Transformation Corp", "", "United States", + "XDY", "Dynair Services", "", "United States", + "XEC", "Air Executive Charter", "", "Germany", + "XEL", "Excel Charter", "HELI EXCEL", "United Kingdom", + "XER", "Xerox Corporation", "XEROX", "United States", + "XFA", "FlyAsianXpress", "FAX AIR", "Malaysia", + "XFS", "American Flight Service Systems", "", "United States", + "XFX", "Airways Corporation of New Zealand", "AIRCORP", "New Zealand", + "XGA", "General Aviation Terminal", "", "Canada", + "XGG", "IMP Group Aviation Services", "", "Canada", + "XGS", "Global System", "", "United States", + "XGW", "Global Weather Dynamics", "", "United States", + "XJA", "Assistance Aeroportuaire de L'Aeroport de Paris", "", "France", + "XJC", "XJC Limited", "EXCLUSIVE JET", "United Kingdom", + "XJE", "X-Jet", "", "Austria", + "XJT", "Xjet Limited", "XRAY", "United Kingdom", + "XKA", "Kavouras Inc", "", "United States", + "XKX", "ASECNA", "", "France", + "XLA", "Excel Airways", "EXPO", "United Kingdom", + "XLB", "Aircraft Performance Group", "", "United States", + "XLD", "Jeppesen Data Plan", "", "United States", + "XLG", "Lockheed Air Terminal", "", "United States", + "XLK", "Safarilink Aviation", "SAFARILINK", "Kenya", + "XLL", "Air Excel", "TINGA-TINGA", "Tanzania", + "XLT", "Empressa Brasileira de Infra-Estrutura Aeroportuaria-Infraero", "INFRAERO", "Brazil", + "XMA", "Martin Aviation Services", "", "United States", + "XME", "Australian airExpress", "AUS-CARGO", "Australia", + "XMG", "AMS Group", "", "Russia", + "XMR", "Irish Aviation Authority", "AUTHORITY", "Ireland", + "XMS", "British Airways Santa", "SANTA", "United Kingdom", + "XMX", "SENEAM", "SENEAM", "Mexico", + "XNA", "Express Net Airlines", "EXPRESSNET", "United States", + "XNR", "Taxi Aero Del Norte", "TAXI NORTE", "Mexico", + "XNS", "Navtech System Support", "", "Canada", + "XNT", "Notams International", "", "United States", + "XNV", "Navinc Airlines Services", "", "United States", + "XOJ", "XOJet", "EXOJET", "United States", + "XPA", "Pan Am Weather Systems", "", "United States", + "XPE", "Amira Air", "EXPERT", "Austria", + "XPG", "Southport Air Service", "", "United States", + "XPL", "Express Line Aircompany", "EXPRESSLINE", "United States", + "XPN", "Aero Express", "", "Niger", + "XPR", "Air-Rep", "", "United States", + "XPS", "XP International", "XP PARCEL", "Netherlands", + "XPX", "Phoenix Flight Operations", "", "United States", + "XRA", "Intensive Air", "INTENSIVE", "South Africa", + "XRC", "Express Air Cargo", "TUNISIA CARGO", "Tunisia", + "XRO", "ExxAero", "CRAMER", "Netherlands", + "XSA", "Spectrum Air Service", "", "United States", + "XSL", "Excel-Aire Service", "EXCELAIRE", "United States", + "XSN", "Stephenville Aviation Services", "", "Canada", + "XSR", "Executive Flight Services", "AIRSHARE", "United States", + "XSS", "Aero Express Intercontinental", "INTER EXPRESS", "Mexico", + "XTA", "Servicios Aéreos Textra", "TEXTRA", "Mexico", + "XTJ", "Advance Aviation Services", "", "United States", + "XTO", "Express Tours", "EXPRESS TOURS", "Canada", + "XTR", "Sector Airlines", "EXTER", "Canada", + "XWS", "WSI Corporation", "", "United States", + "XWW", "World Weatherwatch", "", "Canada", + "XXS", "Skyplan Services", "", "Canada", + "XXV", "AASANA", "", "Bolivia", + "XXX", "ASL (Air Service Liege)", "", "Belgium", + "XYZ", "Island Air Express", "RAINBIRD", "United States", + "YAK", "Yakolev", "YAK AVIA", "Russia", + "YBE", "Stewart Aviation Services", "YELLOW BIRD", "United States", + "YFS", "Young Flying Service", "YOUNG AIR", "United States", + "YOG", "Central Aviation", "YOGAN AIR", "United States", + "YRG", "Yak Air", "YAKAIR GEORGIA", "Georgia", + "YWZ", "West Coast Air", "COAST AIR", "Canada", + "YZR", "Suparna Airlines", "YANGTZE RIVER", "China", + "ZAI", "Zaire Aero Service", "ZASAIR", "Democratic Republic of Congo", + "ZAK", "Zambia Skyways", "ZAMBIA SKIES", "Zambia", + "ZAR", "Zairean Airlines", "ZAIREAN", "Domocratic Republic of Congo", + "ZAV", "Zetavia", "ZETAVIA", "Ukraine", + "ZAW", "Zoom Airways", "ZED AIR", "Bangladesh", + "ZBA", "Boskovic Air Charters Limited", "BOSKY", "Kenya", + "ZMA", "Zambezi Airlines", "ZAMBEZI WINGS", "Zambia", + "ZZM", "Agence Nationale des Aerodromes et de la Meteorologie", "", "Ivory Coast", + NULL +}; + +QHash Airline::m_icaoHash; +QHash Airline::m_callsignHash; + +const Airline *Airline::getByICAO(const QString& icao) +{ + if (m_icaoHash.contains(icao)) { + return m_icaoHash.value(icao); + } else { + return nullptr; + } +} + +const Airline *Airline::getByCallsign(const QString& callsign) +{ + if (m_callsignHash.contains(callsign)) { + return m_callsignHash.value(callsign); + } else { + return nullptr; + } +} + +Airline::Init Airline::m_init; + +Airline::Init::Init() +{ + const char **s = Airline::Init::m_airlines; + while (s[0]) + { + const Airline *airline = new Airline(s[0], s[1], s[2], s[3]); + Airline::m_icaoHash.insert(airline->m_icao, airline); + Airline::m_callsignHash.insert(airline->m_callsign, airline); + s += 4; + } +} + +Airline::Init::~Init() +{ + qDeleteAll(m_icaoHash); +} diff --git a/android/app/src/main/cpp/util/airlines.h b/android/app/src/main/cpp/util/airlines.h new file mode 100644 index 0000000..34ac589 --- /dev/null +++ b/android/app/src/main/cpp/util/airlines.h @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_AIRLINES_H +#define INCLUDE_UTIL_AIRLINES_H + +#include +#include +#include + +#include "export.h" + +class SDRBASE_API Airline { + +public: + + QString m_icao; + QString m_name; + QString m_callsign; + QString m_country; + + static const Airline *getByICAO(const QString& icao); + static const Airline *getByCallsign(const QString& callsign); + +private: + + Airline(const QString& icao, const QString& name, const QString& callsign, const QString& country) : + m_icao(icao), + m_name(name), + m_callsign(callsign), + m_country(country) + { + } + + static QHash m_icaoHash; + static QHash m_callsignHash; + + friend struct Init; + struct Init { + Init(); + ~Init(); + static const char *m_airlines[]; + }; + static Init m_init; + +}; + +#endif diff --git a/android/app/src/main/cpp/util/ais.cpp b/android/app/src/main/cpp/util/ais.cpp new file mode 100644 index 0000000..3a2f674 --- /dev/null +++ b/android/app/src/main/cpp/util/ais.cpp @@ -0,0 +1,819 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "ais.h" + +AISMessage::AISMessage(const QByteArray ba) +{ + // All AIS messages have these 3 fields in common + m_id = (ba[0] & 0xff) >> 2 & 0x3f; + m_repeatIndicator = ba[0] & 0x3; + m_mmsi = ((ba[1] & 0xff) << 22) | ((ba[2] & 0xff) << 14) | ((ba[3] & 0xff) << 6) | ((ba[4] & 0xff) >> 2); + m_bytes = ba; +} + +QString AISMessage::toHex() +{ + return m_bytes.toHex(); +} + +// See: https://gpsd.gitlab.io/gpsd/AIVDM.html +QString AISMessage::toNMEA(const QByteArray bytes) +{ + QStringList nmeaSentences; + + // Max payload is ~61 chars -> 366 bits + int sentences = bytes.size() / 45 + 1; + int sentence = 1; + + int bits = 8; + int i = 0; + while (i < bytes.size()) + { + QStringList nmeaSentence; + QStringList nmea; + QStringList payload; + + nmea.append(QString("AIVDM,%1,%2,%3,,").arg(sentences).arg(sentence).arg(sentences > 1 ? "1" : "")); + + int maxPayload = 80 - 1 - nmea[0].length() - 5; + + // Encode message data in 6-bit ASCII + while ((payload.size() < maxPayload) && (i < bytes.size())) + { + int c = 0; + for (int j = 0; j < 6; j++) + { + if (i < bytes.size()) { + c = (c << 1) | ((bytes[i] >> (bits - 1)) & 0x1); + } else { + c = (c << 1); + } + bits--; + if (bits == 0) + { + i++; + bits = 8; + } + } + if (c >= 40) { + c += 56; + } else { + c += 48; + } + payload.append(QChar(c)); + } + + nmea.append(payload); + nmea.append(QString(",%1").arg((i == bytes.size()) ? (8 - bits) : 0)); // Number of filler bits to ignore + + // Calculate checksum + QString nmeaProtected = nmea.join(""); + int checksum = AISMessage::nmeaChecksum(nmeaProtected); + + // Construct complete sentence with leading ! and trailing checksum + nmeaSentence.append("!"); + nmeaSentence.append(nmeaProtected); + nmeaSentence.append(QString("*%1").arg(checksum, 2, 16, QChar('0'))); + + nmeaSentences.append(nmeaSentence.join("")); + sentence++; + } + + return nmeaSentences.join("\r\n").append("\r\n"); // NMEA-0183 requires CR and LF +} + +QString AISMessage::toNMEA() +{ + return AISMessage::toNMEA(m_bytes); +} + +qint8 AISMessage::nmeaChecksum(QString string) +{ + qint8 checksum = 0; + + for (int i = 0; i < string.length(); i++) + { + qint8 c = (qint8)string[i].toLatin1(); + checksum ^= c; + } + + return checksum; +} + +// Type as in message 5 and 19 +QString AISMessage::typeToString(quint8 type) +{ + if (type == 0) { + return "N/A"; + } else if ((type >= 100) && (type < 199)) { + return "Preserved for regional use"; + } else if (type >= 200) { + return "Preserved for future use"; + } else if ((type >= 50) && (type <= 59)) { + const QStringList specialCrafts = { + "Pilot vessel", + "Search and rescue vessel", + "Tug", + "Port tender", + "Anti-pollution vessel", + "Law enforcement vessel", + "Spare (56)", + "Spare (57)", + "Medical transport", + "Ships and aircraft of States not parties to an armed conflict" + }; + return specialCrafts[type-50]; + } else { + int firstDigit = type / 10; + int secondDigit = type % 10; + + const QStringList shipType = { + "0", + "Reserved (1)", + "WIG", + "Vessel", + "HSC", + "5", + "Passenger", + "Cargo", + "Tanker", + "Other" + }; + const QStringList activity = { + "Fishing", + "Towing", + "Towing", + "Dredging or underwater operations", + "Diving operations", + "Military operations", + "Sailing", + "Pleasure craft", + "Reserved (8)", + "Reserved (9)" + }; + + if (firstDigit == 3) { + return shipType[firstDigit] + " - " + activity[secondDigit]; + } else { + return shipType[firstDigit]; + } + } +} + +AISMessage* AISMessage::decode(const QByteArray ba) +{ + if (ba.size() < 1) { + return nullptr; + } + + int id = (ba[0] >> 2) & 0x3f; + + if ((id == 1) || (id == 2) || (id == 3)) { + return new AISPositionReport(ba); + } else if ((id == 4) || (id == 11)) { + return new AISBaseStationReport(ba); + } else if (id == 5) { + return new AISShipStaticAndVoyageData(ba); + } else if (id == 6) { + return new AISBinaryMessage(ba); + } else if (id == 7) { + return new AISBinaryAck(ba); + } else if (id == 8) { + return new AISBinaryBroadcast(ba); + } else if (id == 9) { + return new AISSARAircraftPositionReport(ba); + } else if (id == 10) { + return new AISUTCInquiry(ba); + } else if (id == 12) { + return new AISSafetyMessage(ba); + } else if (id == 13) { + return new AISSafetyAck(ba); + } else if (id == 14) { + return new AISSafetyBroadcast(ba); + } else if (id == 15) { + return new AISInterrogation(ba); + } else if (id == 16) { + return new AISAssignedModeCommand(ba); + } else if (id == 17) { + return new AISGNSSBroadcast(ba); + } else if (id == 18) { + return new AISStandardClassBPositionReport(ba); + } else if (id == 19) { + return new AISExtendedClassBPositionReport(ba); + } else if (id == 20) { + return new AISDatalinkManagement(ba); + } else if (id == 21) { + return new AISAidsToNavigationReport(ba); + } else if (id == 22) { + return new AISChannelManagement(ba); + } else if (id == 23) { + return new AISGroupAssignment(ba); + } else if (id == 24) { + return new AISStaticDataReport(ba); + } else if (id == 25) { + return new AISSingleSlotBinaryMessage(ba); + } else if (id == 26) { + return new AISMultipleSlotBinaryMessage(ba); + } else if (id == 27) { + return new AISLongRangePositionReport(ba); + } else { + return new AISUnknownMessageID(ba); + } +} + +// Extract 6-bit ASCII string +QString AISMessage::getString(const QByteArray ba, int byteIdx, int bitsLeft, int chars) +{ + QString s; + for (int i = 0; i < chars; i++) + { + // Extract 6-bits + int c = 0; + for (int j = 0; j < 6; j++) + { + c = (c << 1) | ((ba[byteIdx] >> (bitsLeft - 1)) & 0x1); + bitsLeft--; + if (bitsLeft == 0) + { + byteIdx++; + bitsLeft = 8; + } + } + // Map from 6-bit to 8-bit ASCII + if (c < 32) { + c |= 0x40; + } + s.append(QChar(c)); + } + // Remove leading/trailing spaces + s = s.trimmed(); + // Remove @s, which indicate no character + while (s.endsWith("@")) { + s = s.left(s.length() - 1); + } + while (s.startsWith("@")) { + s = s.mid(1); + } + return s; +} + +AISPositionReport::AISPositionReport(QByteArray ba) : + AISMessage(ba) +{ + m_status = ((ba[4] & 0x3) << 2) | ((ba[5] >> 6) & 0x3); + + int rateOfTurn = ((ba[5] << 2) & 0xfc) | ((ba[6] >> 6) & 0x3); + if (rateOfTurn == 127) { + m_rateOfTurn = 720.0f; + } else if (rateOfTurn == -127) { + m_rateOfTurn = -720.0f; + } else { + m_rateOfTurn = (rateOfTurn / 4.733f) * (rateOfTurn / 4.733f); + } + m_rateOfTurnAvailable = rateOfTurn != 0x80; + + int sog = ((ba[6] & 0x3f) << 4) | ((ba[7] >> 4) & 0xf); + m_speedOverGroundAvailable = sog != 1023; + m_speedOverGround = sog * 0.1f; + + m_positionAccuracy = (ba[7] >> 3) & 0x1; + + int32_t longitude = ((ba[7] & 0x7) << 25) | ((ba[8] & 0xff) << 17) | ((ba[9] & 0xff) << 9) | ((ba[10] & 0xff) << 1) | ((ba[11] >> 7) & 1); + longitude = (longitude << 4) >> 4; + m_longitudeAvailable = longitude != 0x6791ac0; + m_longitude = longitude / 60.0f / 10000.0f; + + int32_t latitude = ((ba[11] & 0x7f) << 20) | ((ba[12] & 0xff) << 12) | ((ba[13] & 0xff) << 4) | ((ba[14] >> 4) & 0x4); + latitude = (latitude << 5) >> 5; + m_latitudeAvailable = latitude != 0x3412140; + m_latitude = latitude / 60.0f / 10000.0f; + + int cog = ((ba[14] & 0xf) << 8) | (ba[15] & 0xff); + m_courseAvailable = cog != 3600; + m_course = cog * 0.1f; + + m_heading = ((ba[16] & 0xff) << 1) | ((ba[17] >> 7) & 0x1); + m_headingAvailable = m_heading != 511; + + m_timeStamp = (ba[17] >> 1) & 0x3f; + + m_specialManoeuvre = ((ba[17] & 0x1) << 1) | ((ba[18] >> 7) & 0x1); +} + +QString AISPositionReport::getStatusString(int status) +{ + const QStringList statuses = { + "Under way using engine", + "At anchor", + "Not under command", + "Restricted manoeuvrability", + "Constrained by her draught", + "Moored", + "Aground", + "Engaged in fishing", + "Under way sailing", + "Reserved for future amendment of navigational status for ships carrying DG, HS, or MP, or IMO hazard or pollutant category C (HSC)", + "Reserved for future amendment of navigational status for carrying DG, HS or MP, or IMO hazard or pollutant category A (WIG)", + "Reserved for future use", + "Reserved for future use", + "Reserved for future use", + "Reserved for future use", + "Not defined" + }; + return statuses[status]; +} + +QString AISPositionReport::getType() +{ + if (m_id == 1) { + return "Position report (Scheduled)"; + } else if (m_id == 2) { + return "Position report (Assigned)"; + } else { + return "Position report (Interrogated)"; + } +} + +QString AISPositionReport::toString() +{ + QString speed = m_speedOverGround == 1022 ? ">102.2" : QString::number(m_speedOverGround); + return QString("Lat: %1%6 Lon: %2%6 Speed: %3 knts Course: %4%6 Status: %5") + .arg(m_latitude) + .arg(m_longitude) + .arg(speed) + .arg(m_course) + .arg(AISPositionReport::getStatusString(m_status)) + .arg(QChar(0xb0)); +} + +AISBaseStationReport::AISBaseStationReport(QByteArray ba) : + AISMessage(ba) +{ + int year = ((ba[4] & 0x3) << 12) | ((ba[5] & 0xff) << 4) | ((ba[6] >> 4) & 0xf); + int month = ba[6] & 0xf; + int day = ((ba[7] >> 3) & 0x1f); + int hour = ((ba[7] & 0x7) << 2) | ((ba[8] >> 6) & 0x3); + int minute = ba[8] & 0x3f; + int second = (ba[9] >> 2) & 0x3f; + m_utc = QDateTime(QDate(year, month, day), QTime(hour, minute, second), Qt::UTC); + + m_positionAccuracy = (ba[9] >> 1) & 0x1; + + int32_t longitude = ((ba[9] & 0x1) << 27) | ((ba[10] & 0xff) << 19) | ((ba[11] & 0xff) << 11) | ((ba[12] & 0xff) << 3) | ((ba[13] >> 5) & 0x7); + longitude = (longitude << 4) >> 4; + m_longitudeAvailable = longitude != 0x6791ac0; + m_longitude = longitude / 60.0f / 10000.0f; + + int32_t latitude = ((ba[13] & 0x1f) << 22) | ((ba[14] & 0xff) << 14) | ((ba[15] & 0xff) << 6) | ((ba[16] >> 2) & 0x3f); + latitude = (latitude << 5) >> 5; + m_latitudeAvailable = latitude != 0x3412140; + m_latitude = latitude / 60.0f / 10000.0f; +} + +QString AISBaseStationReport::toString() +{ + return QString("Lat: %1%3 Lon: %2%3 %4") + .arg(m_latitude) + .arg(m_longitude) + .arg(QChar(0xb0)) + .arg(m_utc.toString()); +} + +AISShipStaticAndVoyageData::AISShipStaticAndVoyageData(QByteArray ba) : + AISMessage(ba) +{ + m_version = ba[4] & 0x3; + m_imo = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f); + m_callsign = AISMessage::getString(ba, 8, 2, 7); + m_name = AISMessage::getString(ba, 14, 8, 20); + m_type = ba[29] & 0xff; + m_dimension = ((ba[30] & 0xff) << 22) | ((ba[31] & 0xff) << 14) | ((ba[32] & 0xff) << 6) | ((ba[33] >> 2) & 0x3f); + m_a = (m_dimension >> 21) & 0x1ff; + m_b = (m_dimension >> 12) & 0x1ff; + m_c = (m_dimension >> 6) & 0x3f; + m_d = m_dimension & 0x3f; + m_positionFixing = ((ba[33] & 0x3) << 2) | ((ba[34] >> 6) & 0x3); + m_eta = ((ba[34] & 0x3f) << 14) | ((ba[35] & 0xff) << 6) | ((ba[36] >> 2) & 0x3f); + m_draught = ((ba[36] & 0x3) << 6) | ((ba[37] >> 2) & 0x3f); + m_destination = AISMessage::getString(ba, 37, 2, 20); +} + +QString AISShipStaticAndVoyageData::toString() +{ + return QString("IMO: %1 Callsign: %2 Name: %3 Type: %4 Destination: %5") + .arg(m_imo == 0 ? "N/A" : QString::number(m_imo)) + .arg(m_callsign) + .arg(m_name) + .arg(AISMessage::typeToString(m_type)) + .arg(m_destination); +} + +AISBinaryMessage::AISBinaryMessage(QByteArray ba) : + AISMessage(ba) +{ + m_sequenceNumber = ba[4] & 0x3; + m_destinationId = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f); + m_retransmitFlag = (ba[8] >> 1) & 0x1; +} + +QString AISBinaryMessage::toString() +{ + return QString("Seq No: %1 Destination: %2 Retransmit: %3") + .arg(m_sequenceNumber) + .arg(m_destinationId) + .arg(m_retransmitFlag); +} + + +AISBinaryAck::AISBinaryAck(QByteArray ba) : + AISMessage(ba) +{ +} + +AISBinaryBroadcast::AISBinaryBroadcast(QByteArray ba) : + AISMessage(ba) +{ +} + +AISSARAircraftPositionReport::AISSARAircraftPositionReport(QByteArray ba) : + AISMessage(ba) +{ + m_altitude = ((ba[4] & 0x3) << 10) | ((ba[5] & 0xff) << 2) | ((ba[6] >> 6) & 0x3); + m_altitudeAvailable = m_altitude != 4095; + + int sog = ((ba[6] & 0x3f) << 4) | ((ba[7] >> 4) & 0xf); + m_speedOverGroundAvailable = sog != 1023; + m_speedOverGround = sog; + + m_positionAccuracy = (ba[7] >> 3) & 0x1; + + int32_t longitude = ((ba[7] & 0x7) << 25) | ((ba[8] & 0xff) << 17) | ((ba[9] & 0xff) << 9) | ((ba[10] & 0xff) << 1) | ((ba[11] >> 7) & 1); + longitude = (longitude << 4) >> 4; + m_longitudeAvailable = longitude != 0x6791ac0; + m_longitude = longitude / 60.0f / 10000.0f; + + int32_t latitude = ((ba[11] & 0x7f) << 20) | ((ba[12] & 0xff) << 12) | ((ba[13] & 0xff) << 4) | ((ba[14] >> 4) & 0x4); + latitude = (latitude << 5) >> 5; + m_latitudeAvailable = latitude != 0x3412140; + m_latitude = latitude / 60.0f / 10000.0f; + + int cog = ((ba[14] & 0xf) << 8) | (ba[15] & 0xff); + m_courseAvailable = cog != 3600; + m_course = cog * 0.1f; + + m_timeStamp = (ba[16] >> 2) & 0x3f; +} + +QString AISSARAircraftPositionReport::toString() +{ + QString altitude = m_altitude == 4094 ? ">4094" : QString::number(m_altitude); + QString speed = m_speedOverGround == 1022 ? ">1022" : QString::number(m_speedOverGround); + return QString("Lat: %1%6 Lon: %2%6 Speed: %3 knts Course: %4%6 Alt: %5 m") + .arg(m_latitude) + .arg(m_longitude) + .arg(speed) + .arg(m_course) + .arg(altitude) + .arg(QChar(0xb0)); +} + + +AISUTCInquiry::AISUTCInquiry(QByteArray ba) : + AISMessage(ba) +{ +} + +AISSafetyMessage::AISSafetyMessage(QByteArray ba) : + AISMessage(ba) +{ + m_sequenceNumber = ba[4] & 0x3; + m_destinationId = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f); + m_retransmitFlag = (ba[8] >> 1) & 0x1; + m_safetyRelatedText = AISMessage::getString(ba, 9, 8, (ba.size() - 9) * 8 / 6); +} + +QString AISSafetyMessage::toString() +{ + return QString("To %1: Safety message: %2").arg(m_destinationId).arg(m_safetyRelatedText); +} + +AISSafetyAck::AISSafetyAck(QByteArray ba) : + AISMessage(ba) +{ +} + +AISSafetyBroadcast::AISSafetyBroadcast(QByteArray ba) : + AISMessage(ba) +{ + m_safetyRelatedText = AISMessage::getString(ba, 5, 8, (ba.size() - 5) * 8 / 6); +} + +QString AISSafetyBroadcast::toString() +{ + return QString("Safety message: %1").arg(m_safetyRelatedText); +} + +AISInterrogation::AISInterrogation(QByteArray ba) : + AISMessage(ba) +{ +} + +AISAssignedModeCommand::AISAssignedModeCommand(QByteArray ba) : + AISMessage(ba) +{ + m_destinationIdA = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f); + m_offsetA = ((ba[8] & 0x3) << 10) | ((ba[9] & 0xff) << 2) | ((ba[10] >> 6) & 0x3); + m_incrementA = ((ba[10] & 0x3f) << 4) | ((ba[11] >> 4) & 0xf); + m_bAvailable = false; +} + +QString AISAssignedModeCommand::toString() +{ + return QString("Dest A: %1 Offset A: %2 Inc A: %3") + .arg(m_destinationIdA) + .arg(m_offsetA) + .arg(m_incrementA); +} + +AISGNSSBroadcast::AISGNSSBroadcast(QByteArray ba) : + AISMessage(ba) +{ +} + +AISStandardClassBPositionReport::AISStandardClassBPositionReport(QByteArray ba) : + AISMessage(ba) +{ + int sog = ((ba[5] & 0x3) << 8) | (ba[6] & 0xff); + m_speedOverGroundAvailable = sog != 1023; + m_speedOverGround = sog * 0.1f; + + m_positionAccuracy = (ba[7] >> 7) & 0x1; + + int32_t longitude = ((ba[7] & 0x7f) << 21) | ((ba[8] & 0xff) << 13) | ((ba[9] & 0xff) << 5) | ((ba[10] >> 3) & 0x1f); + longitude = (longitude << 4) >> 4; + m_longitudeAvailable = longitude != 0x6791ac0; + m_longitude = longitude / 60.0f / 10000.0f; + + int32_t latitude = ((ba[10] & 0x7) << 24) | ((ba[11] & 0xff) << 16) | ((ba[12] & 0xff) << 8) | (ba[13] & 0xff); + latitude = (latitude << 5) >> 5; + m_latitudeAvailable = latitude != 0x3412140; + m_latitude = latitude / 60.0f / 10000.0f; + + int cog = ((ba[14] & 0xff) << 4) | ((ba[15] >> 4) & 0xf); + m_courseAvailable = cog != 3600; + m_course = cog * 0.1f; + + m_heading = ((ba[15] & 0xf) << 5) | ((ba[16] >> 3) & 0x1f); + m_headingAvailable = m_heading != 511; + + m_timeStamp = ((ba[16] & 0x7) << 3) | ((ba[17] >> 5) & 0x7); +} + +QString AISStandardClassBPositionReport::toString() +{ + return QString("Lat: %1%5 Lon: %2%5 Speed: %3 knts Course: %4%5") + .arg(m_latitude) + .arg(m_longitude) + .arg(m_speedOverGround) + .arg(m_course) + .arg(QChar(0xb0)); +} + + +AISExtendedClassBPositionReport::AISExtendedClassBPositionReport(QByteArray ba) : + AISMessage(ba) +{ + int sog = ((ba[5] & 0x3) << 8) | (ba[6] & 0xff); + m_speedOverGroundAvailable = sog != 1023; + m_speedOverGround = sog * 0.1f; + + m_positionAccuracy = (ba[7] >> 7) & 0x1; + + int32_t longitude = ((ba[7] & 0x7f) << 21) | ((ba[8] & 0xff) << 13) | ((ba[9] & 0xff) << 5) | ((ba[10] >> 3) & 0x1f); + longitude = (longitude << 4) >> 4; + m_longitudeAvailable = longitude != 0x6791ac0; + m_longitude = longitude / 60.0f / 10000.0f; + + int32_t latitude = ((ba[10] & 0x7) << 24) | ((ba[11] & 0xff) << 16) | ((ba[12] & 0xff) << 8) | (ba[13] & 0xff); + latitude = (latitude << 5) >> 5; + m_latitudeAvailable = latitude != 0x3412140; + m_latitude = latitude / 60.0f / 10000.0f; + + int cog = ((ba[14] & 0xff) << 4) | ((ba[15] >> 4) & 0xf); + m_courseAvailable = cog != 3600; + m_course = cog * 0.1f; + + m_heading = ((ba[15] & 0xf) << 5) | ((ba[16] >> 3) & 0x1f); + m_headingAvailable = m_heading != 511; + + m_timeStamp = ((ba[16] & 0x7) << 3) | ((ba[17] >> 5) & 0x7); + + m_name = AISMessage::getString(ba, 17, 1, 20); + + m_type = ((ba[32] & 1) << 7) | ((ba[33] >> 1) & 0x3f); +} + +QString AISExtendedClassBPositionReport::toString() +{ + return QString("Lat: %1%5 Lon: %2%5 Speed: %3 knts Course: %4%5 Name: %6 Type: %7") + .arg(m_latitude) + .arg(m_longitude) + .arg(m_speedOverGround) + .arg(m_course) + .arg(QChar(0xb0)) + .arg(m_name) + .arg(typeToString(m_type)); +} + +AISDatalinkManagement::AISDatalinkManagement(QByteArray ba) : + AISMessage(ba) +{ +} + +AISAidsToNavigationReport::AISAidsToNavigationReport(QByteArray ba) : + AISMessage(ba) +{ + m_type = ((ba[4] & 0x3) << 3) | ((ba[5] >> 5) & 0x7); + + m_name = AISMessage::getString(ba, 5, 5, 20); + + m_positionAccuracy = (ba[20] >> 4) & 0x1; + + int32_t longitude = ((ba[20] & 0xf) << 24) | ((ba[21] & 0xff) << 16) | ((ba[22] & 0xff) << 8) | (ba[23] & 0xff); + longitude = (longitude << 4) >> 4; + m_longitudeAvailable = longitude != 0x6791ac0; + m_longitude = longitude / 60.0f / 10000.0f; + + int32_t latitude = ((ba[24] & 0xff) << 19) | ((ba[25] & 0xff) << 11) | ((ba[26] & 0xff) << 3) | ((ba[27] >> 5) & 0x7); + latitude = (latitude << 5) >> 5; + m_latitudeAvailable = latitude != 0x3412140; + m_latitude = latitude / 60.0f / 10000.0f; +} + +QString AISAidsToNavigationReport::toString() +{ + const QStringList types = { + "N/A", + "Reference point", + "RACON", + "Fixed structure off short", + "Emergency wreck marking buoy", + "Light, without sectors", + "Light, with sectors", + "Leading light front", + "Leading light rear", + "Beacon, Cardinal N", + "Beacon, Cardinal E", + "Beacon, Cardinal S", + "Beacon, Cardianl W", + "Beacon, Port hand", + "Beacon, Starboard hand", + "Beacon, Preferred channel port hand", + "Beacon, Preferred channel starboard hand", + "Beacon, Isolated danger", + "Beacon, Safe water", + "Beacon, Special mark" + "Cardinal mark N", + "Cardinal mark E", + "Cardinal mark S", + "Cardinal mark W", + "Port hand mark", + "Starboard hand mark", + "Preferred channel port hand", + "Preferred channel starboard hand", + "Isolated danger", + "Safe water", + "Special mark", + "Light vessel/LANBY/Rigs" + }; + return QString("Lat: %1%5 Lon: %2%5 Name: %3 Type: %4") + .arg(m_latitude) + .arg(m_longitude) + .arg(m_name) + .arg(types[m_type]) + .arg(QChar(0xb0)); +} + +AISChannelManagement::AISChannelManagement(QByteArray ba) : + AISMessage(ba) +{ +} + +AISGroupAssignment::AISGroupAssignment(QByteArray ba) : + AISMessage(ba) +{ +} + +AISStaticDataReport::AISStaticDataReport(QByteArray ba) : + AISMessage(ba) +{ + m_partNumber = ba[4] & 0x3; + if (m_partNumber == 0) + { + m_name = AISMessage::getString(ba, 5, 8, 20); + } + else if (m_partNumber == 1) + { + m_type = ba[5] & 0xff; + m_vendorId = AISMessage::getString(ba, 6, 8, 7); + m_callsign = AISMessage::getString(ba, 11, 6, 7); + } +} + +QString AISStaticDataReport::toString() +{ + if (m_partNumber == 0) { + return QString("Name: %1").arg(m_name); + } else if (m_partNumber == 1) { + return QString("Type: %1 Vendor ID: %2 Callsign: %3").arg(typeToString(m_type)).arg(m_vendorId).arg(m_callsign); + } else { + return ""; + } +} + +AISSingleSlotBinaryMessage::AISSingleSlotBinaryMessage(QByteArray ba) : + AISMessage(ba) +{ + m_destinationIndicator = (ba[4] >> 1) & 1; + m_binaryDataFlag = ba[4] & 1; + if (m_destinationIndicator) { + m_destinationId = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f); + } + m_destinationIdAvailable = m_destinationIndicator; +} + +QString AISSingleSlotBinaryMessage::toString() +{ + QStringList s; + + s.append(QString("Destination: %1").arg(m_destinationIndicator ? "Broadcast" : "Addressed")); + s.append(QString("Flag: %1").arg(m_binaryDataFlag ? "Unstructured" : "Structured")); + if (m_destinationIdAvailable) { + s.append(QString("Destination Id: %1").arg(m_destinationId)); + } + + return s.join(" "); +} + +AISMultipleSlotBinaryMessage::AISMultipleSlotBinaryMessage(QByteArray ba) : + AISMessage(ba) +{ +} + +AISLongRangePositionReport::AISLongRangePositionReport(QByteArray ba) : + AISMessage(ba) +{ + m_positionAccuracy = (ba[4] >> 1) & 0x1; + m_raim = ba[4] & 0x1; + m_status = (ba[5] >> 4) & 0xf; + + int32_t longitude = ((ba[5] & 0xf) << 14) | ((ba[6] & 0xff) << 6) | ((ba[7] >> 2) & 0x3f); + longitude = (longitude << 14) >> 14; + m_longitudeAvailable = longitude != 0x1a838; + m_longitude = longitude / 60.0f / 10.0f; + + int32_t latitude = ((ba[7] & 0x3) << 15) | ((ba[8] & 0xff) << 7) | ((ba[9] >> 1) & 0x7f); + latitude = (latitude << 15) >> 15; + m_latitudeAvailable = latitude != 0xd548; + m_latitude = latitude / 60.0f / 10.0f; + + m_speedOverGround = ((ba[9] & 0x1) << 5) | ((ba[10] >> 3) & 0x1f); + m_speedOverGroundAvailable = m_speedOverGround != 63; + + m_course = ((ba[10] & 0x7) << 6) | ((ba[11] >> 2) & 0x3f); + m_courseAvailable = m_course != 512; +} + +QString AISLongRangePositionReport::toString() +{ + return QString("Lat: %1%6 Lon: %2%6 Speed: %3 knts Course: %4%6 Status: %5") + .arg(m_latitude) + .arg(m_longitude) + .arg(m_speedOverGround) + .arg(m_course) + .arg(AISPositionReport::getStatusString(m_status)) + .arg(QChar(0xb0)); +} + +AISUnknownMessageID::AISUnknownMessageID(QByteArray ba) : + AISMessage(ba) +{ +} + diff --git a/android/app/src/main/cpp/util/ais.h b/android/app/src/main/cpp/util/ais.h new file mode 100644 index 0000000..dc04131 --- /dev/null +++ b/android/app/src/main/cpp/util/ais.h @@ -0,0 +1,405 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AIS_H +#define INCLUDE_AIS_H + +#include +#include +#include +#include + +#include "export.h" + +class SDRBASE_API AISMessage { +public: + + int m_id; + int m_repeatIndicator; + int m_mmsi; + + AISMessage(const QByteArray ba); + virtual ~AISMessage() {} + virtual QString getType() = 0; + virtual bool hasPosition() { return false; } + virtual float getLatitude() { return 0.0f; } + virtual float getLongitude() { return 0.0f; } + virtual bool hasCourse() { return false; } + virtual float getCourse() { return 0.0f; } + virtual bool hasSpeed() { return false; } + virtual float getSpeed() { return 0.0f; } + virtual bool hasHeading() { return false; } + virtual float getHeading() { return 0.0f; } + virtual QString toString() { return ""; } + QString toHex(); + QString toNMEA(); + + static AISMessage* decode(const QByteArray ba); + static QString toNMEA(const QByteArray ba); + static QString typeToString(quint8 type); + +protected: + static QString getString(QByteArray ba, int byteIdx, int bitsLeft, int chars); + static qint8 nmeaChecksum(QString string); + QByteArray m_bytes; + +}; + +class SDRBASE_API AISPositionReport : public AISMessage { +public: + int m_status; + bool m_rateOfTurnAvailable; + float m_rateOfTurn; // Degrees per minute + bool m_speedOverGroundAvailable; + float m_speedOverGround; // Knots + int m_positionAccuracy; + bool m_longitudeAvailable; + float m_longitude; // Degrees, North positive + bool m_latitudeAvailable; + float m_latitude; // Degrees, East positive + bool m_courseAvailable; + float m_course; // Degrees + bool m_headingAvailable; + int m_heading; // Degrees + int m_timeStamp; + int m_specialManoeuvre; + + AISPositionReport(const QByteArray ba); + virtual QString getType() override; + virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; } + virtual float getLatitude() { return m_latitude; } + virtual float getLongitude() { return m_longitude; } + virtual bool hasCourse() { return m_courseAvailable; } + virtual float getCourse() { return m_course; } + virtual bool hasSpeed() { return m_speedOverGroundAvailable; } + virtual float getSpeed() { return m_speedOverGround; } + virtual bool hasHeading() { return m_headingAvailable; } + virtual float getHeading() { return m_heading; } + virtual QString toString() override; + + static QString getStatusString(int status); +}; + +class SDRBASE_API AISBaseStationReport : public AISMessage { +public: + QDateTime m_utc; + int m_positionAccuracy; + bool m_longitudeAvailable; + float m_longitude; // Degrees, North positive + bool m_latitudeAvailable; + float m_latitude; // Degrees, East positive + + AISBaseStationReport(const QByteArray ba); + virtual QString getType() override { + if (m_id == 4) + return "Base station report"; + else + return "UTC and data response"; + } + virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; } + virtual float getLatitude() { return m_latitude; } + virtual float getLongitude() { return m_longitude; } + virtual QString toString() override; +}; + +class SDRBASE_API AISShipStaticAndVoyageData : public AISMessage { +public: + int m_version; + int m_imo; + QString m_callsign; + QString m_name; + quint8 m_type; + int m_dimension; + int m_a; + int m_b; + int m_c; + int m_d; + int m_positionFixing; + int m_eta; + int m_draught; + QString m_destination; + AISShipStaticAndVoyageData(const QByteArray ba); + virtual QString getType() override { return "Ship static and voyage related data"; } + virtual QString toString() override; +}; + +class SDRBASE_API AISBinaryMessage : public AISMessage { +public: + int m_sequenceNumber; + int m_destinationId; + int m_retransmitFlag; + AISBinaryMessage(const QByteArray ba); + virtual QString getType() override { return "Addressed binary message"; } + virtual QString toString() override; +}; + +class SDRBASE_API AISBinaryAck : public AISMessage { +public: + AISBinaryAck(const QByteArray ba); + virtual QString getType() override { return "Binary acknowledge"; } +}; + +class SDRBASE_API AISBinaryBroadcast : public AISMessage { +public: + AISBinaryBroadcast(const QByteArray ba); + virtual QString getType() override { return "Binary broadcast message"; } +}; + +class SDRBASE_API AISSARAircraftPositionReport : public AISMessage { +public: + bool m_altitudeAvailable; + float m_altitude; // Metres. 4094 = 4094+ + bool m_speedOverGroundAvailable; + float m_speedOverGround; // Knots + int m_positionAccuracy; + bool m_longitudeAvailable; + float m_longitude; // Degrees, North positive + bool m_latitudeAvailable; + float m_latitude; // Degrees, East positive + bool m_courseAvailable; + float m_course; // Degrees + bool m_headingAvailable; + int m_heading; // Degrees + int m_timeStamp; + + AISSARAircraftPositionReport(const QByteArray ba); + virtual QString getType() override { return "Standard SAR aircraft position report"; } + virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; } + virtual float getLatitude() { return m_latitude; } + virtual float getLongitude() { return m_longitude; } + virtual bool hasCourse() { return m_courseAvailable; } + virtual float getCourse() { return m_course; } + virtual bool hasSpeed() { return m_speedOverGroundAvailable; } + virtual float getSpeed() { return m_speedOverGround; } + virtual QString toString() override; +}; + +class SDRBASE_API AISUTCInquiry : public AISMessage { +public: + AISUTCInquiry(const QByteArray ba); + virtual QString getType() override { return "UTC and date inquiry"; } +}; + +class SDRBASE_API AISSafetyMessage : public AISMessage { +public: + int m_sequenceNumber; + int m_destinationId; + int m_retransmitFlag; + QString m_safetyRelatedText; + + AISSafetyMessage(const QByteArray ba); + virtual QString getType() override { return "Addressed safety related message"; } + virtual QString toString() override; +}; + +class SDRBASE_API AISSafetyAck : public AISMessage { +public: + AISSafetyAck(const QByteArray ba); + virtual QString getType() override { return "Safety related acknowledge"; } +}; + +class SDRBASE_API AISSafetyBroadcast : public AISMessage { +public: + QString m_safetyRelatedText; + + AISSafetyBroadcast(const QByteArray ba); + virtual QString getType() override { return "Safety related broadcast message"; } + virtual QString toString() override; +}; + +class SDRBASE_API AISInterrogation : public AISMessage { +public: + AISInterrogation(const QByteArray ba); + virtual QString getType() override { return "Interrogation"; } +}; + +class SDRBASE_API AISAssignedModeCommand : public AISMessage { +public: + int m_destinationIdA; + int m_offsetA; + int m_incrementA; + int m_destinationIdB; + int m_offsetB; + int m_incrementB; + bool m_bAvailable; + AISAssignedModeCommand(const QByteArray ba); + virtual QString getType() override { return "Assigned mode command"; } + virtual QString toString() override; +}; + +class SDRBASE_API AISGNSSBroadcast : public AISMessage { +public: + AISGNSSBroadcast(const QByteArray ba); + virtual QString getType() override { return "GNSS broadcast binary message"; } +}; + +class SDRBASE_API AISStandardClassBPositionReport : public AISMessage { +public: + bool m_speedOverGroundAvailable; + float m_speedOverGround; // Knots + int m_positionAccuracy; + bool m_longitudeAvailable; + float m_longitude; // Degrees, North positive + bool m_latitudeAvailable; + float m_latitude; // Degrees, East positive + bool m_courseAvailable; + float m_course; // Degrees + bool m_headingAvailable; + int m_heading; // Degrees + int m_timeStamp; + + AISStandardClassBPositionReport(const QByteArray ba); + virtual QString getType() override { return "Standard Class B equipment position report"; } + virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; } + virtual float getLatitude() { return m_latitude; } + virtual float getLongitude() { return m_longitude; } + virtual bool hasCourse() { return m_courseAvailable; } + virtual float getCourse() { return m_course; } + virtual bool hasSpeed() { return m_speedOverGroundAvailable; } + virtual float getSpeed() { return m_speedOverGround; } + virtual QString toString() override; +}; + +class SDRBASE_API AISExtendedClassBPositionReport : public AISMessage { +public: + bool m_speedOverGroundAvailable; + float m_speedOverGround; // Knots + int m_positionAccuracy; + bool m_longitudeAvailable; + float m_longitude; // Degrees, North positive + bool m_latitudeAvailable; + float m_latitude; // Degrees, East positive + bool m_courseAvailable; + float m_course; // Degrees + bool m_headingAvailable; + int m_heading; // Degrees + int m_timeStamp; + QString m_name; + quint8 m_type; + + AISExtendedClassBPositionReport(const QByteArray ba); + virtual QString getType() override { return "Extended Class B equipment position report"; } + virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; } + virtual float getLatitude() { return m_latitude; } + virtual float getLongitude() { return m_longitude; } + virtual bool hasCourse() { return m_courseAvailable; } + virtual float getCourse() { return m_course; } + virtual bool hasSpeed() { return m_speedOverGroundAvailable; } + virtual float getSpeed() { return m_speedOverGround; } + virtual QString toString() override; +}; + +class SDRBASE_API AISDatalinkManagement : public AISMessage { +public: + AISDatalinkManagement(const QByteArray ba); + virtual QString getType() override { return "Data link management message"; } +}; + +class SDRBASE_API AISAidsToNavigationReport : public AISMessage { +public: + int m_type; + QString m_name; + int m_positionAccuracy; + bool m_longitudeAvailable; + float m_longitude; // Degrees, North positive + bool m_latitudeAvailable; + float m_latitude; // Degrees, East positive + AISAidsToNavigationReport(const QByteArray ba); + virtual QString getType() override { return "Aids-to-navigation report"; } + virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; } + virtual float getLatitude() { return m_latitude; } + virtual float getLongitude() { return m_longitude; } + virtual QString toString() override; +}; + +class SDRBASE_API AISChannelManagement : public AISMessage { +public: + AISChannelManagement(const QByteArray ba); + virtual QString getType() override { return "Channel management"; } +}; + +class SDRBASE_API AISGroupAssignment : public AISMessage { +public: + AISGroupAssignment(const QByteArray ba); + virtual QString getType() override { return "Group assignment command"; } +}; + +class SDRBASE_API AISStaticDataReport : public AISMessage { +public: + int m_partNumber; + QString m_name; + quint8 m_type; + QString m_vendorId; + QString m_callsign; + + AISStaticDataReport(const QByteArray ba); + virtual QString getType() override { return "Static data report"; } + virtual QString toString() override; +}; + +class SDRBASE_API AISSingleSlotBinaryMessage : public AISMessage { +public: + bool m_destinationIndicator; + bool m_binaryDataFlag; + int m_destinationId; + bool m_destinationIdAvailable; + + AISSingleSlotBinaryMessage(const QByteArray ba); + virtual QString getType() override { return "Single slot binary message"; } + virtual QString toString() override; +}; + +class SDRBASE_API AISMultipleSlotBinaryMessage : public AISMessage { +public: + AISMultipleSlotBinaryMessage(const QByteArray ba); + virtual QString getType() override { return "Multiple slot binary message"; } +}; + +class SDRBASE_API AISLongRangePositionReport : public AISMessage { +public: + int m_positionAccuracy; + int m_raim; + int m_status; + bool m_longitudeAvailable; + float m_longitude; // Degrees, North positive + bool m_latitudeAvailable; + float m_latitude; // Degrees, East positive + bool m_speedOverGroundAvailable; + float m_speedOverGround; // Knots + bool m_courseAvailable; + float m_course; // Degrees + + AISLongRangePositionReport(const QByteArray ba); + virtual QString getType() override { return "Position report for long-range applications"; } + virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; } + virtual float getLatitude() { return m_latitude; } + virtual float getLongitude() { return m_longitude; } + virtual bool hasCourse() { return m_courseAvailable; } + virtual float getCourse() { return m_course; } + virtual bool hasSpeed() { return m_speedOverGroundAvailable; } + virtual float getSpeed() { return m_speedOverGround; } + QString getStatus(); + virtual QString toString() override; +}; + +class SDRBASE_API AISUnknownMessageID : public AISMessage { +public: + AISUnknownMessageID(const QByteArray ba); + virtual QString getType() override { return QString("Unknown message ID (%1)").arg(m_id); } +}; + +#endif // INCLUDE_AIS_H diff --git a/android/app/src/main/cpp/util/android.cpp b/android/app/src/main/cpp/util/android.cpp new file mode 100644 index 0000000..3e72ca8 --- /dev/null +++ b/android/app/src/main/cpp/util/android.cpp @@ -0,0 +1,279 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifdef ANDROID + +#include + +#include + +#include "android.h" + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + +#include +#include +#include + +void Android::sendIntent() +{ + QJniObject url = QJniObject::fromString("iqsrc://-f 1090000000 -p 1234 -s 2000000 -a 127.0.0.1 -g 100"); + QJniObject intent = QJniObject::callStaticObjectMethod("android/content/Intent", "parseUri", "(Ljava/lang/String;I)Landroid/content/Intent;", url.object(), 0x00000001); // Creates Intent(ACTION_VIEW, url) + QtAndroidPrivate::startActivity(intent, 0, [](int requestCode, int resultCode, const QJniObject &data) { + (void) data; + + qDebug() << "MainCore::sendIntent " << requestCode << resultCode; + }); +} + +QStringList Android::listUSBDeviceSerials(int vid, int pid) +{ + QStringList serials; + QJniEnvironment jniEnv; + + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) + { + QJniObject serialsObj = activity.callObjectMethod("listUSBDeviceSerials", "(II)[Ljava/lang/String;", vid, pid); + int serialsLen = jniEnv->GetArrayLength(serialsObj.object()); + for (int i = 0; i < serialsLen; i++) + { + QJniObject arrayElement = jniEnv->GetObjectArrayElement(serialsObj.object(), i); + QString serial = arrayElement.toString(); + serials.append(serial); + } + } + + return serials; +} + +int Android::openUSBDevice(const QString &serial) +{ + int fd = -1; + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) + { + QJniObject serialsObj = QJniObject::fromString(serial); + fd = activity.callMethod("openUSBDevice", "(Ljava/lang/String;)I", serialsObj.object()); + } + + return fd; +} + +void Android::closeUSBDevice(int fd) +{ + if (fd >= 0) + { + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("closeUSBDevice", "(I)V", fd); + } else { + qCritical() << "MainCore::closeUSBDevice: activity is not valid."; + } + } +} + +void Android::moveTaskToBack() +{ + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("moveTaskToBack", "(Z)Z", true); + } +} + +void Android::acquireWakeLock() +{ + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("acquireWakeLock"); + } +} + +void Android::releaseWakeLock() +{ + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("releaseWakeLock"); + } +} + +void Android::acquireScreenLock() +{ + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("acquireScreenLock"); + } +} + +void Android::releaseScreenLock() +{ + QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("releaseScreenLock"); + } +} + +#else // QT_VERSION + +#include +#include +#include +#include +#include + +void Android::sendIntent() +{ + QAndroidJniObject url = QAndroidJniObject::fromString("iqsrc://-f 1090000000 -p 1234 -s 2000000 -a 127.0.0.1 -g 100"); + QAndroidJniObject intent = QAndroidJniObject::callStaticObjectMethod("android/content/Intent", "parseUri", "(Ljava/lang/String;I)Landroid/content/Intent;", url.object(), 0x00000001); // Creates Intent(ACTION_VIEW, url) + QtAndroid::startActivity(intent, 0, [](int requestCode, int resultCode, const QAndroidJniObject &data) { + (void) data; + + qDebug() << "MainCore::sendIntent " << requestCode << resultCode; + }); +} + +QStringList Android::listUSBDeviceSerials(int vid, int pid) +{ + QStringList serials; + QAndroidJniEnvironment jniEnv; + + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) + { + QAndroidJniObject serialsObj = activity.callObjectMethod("listUSBDeviceSerials", "(II)[Ljava/lang/String;", vid, pid); + int serialsLen = jniEnv->GetArrayLength(serialsObj.object()); + for (int i = 0; i < serialsLen; i++) + { + QAndroidJniObject arrayElement = jniEnv->GetObjectArrayElement(serialsObj.object(), i); + QString serial = arrayElement.toString(); + serials.append(serial); + } + } + + return serials; +} + +int Android::openUSBDevice(const QString &serial) +{ + int fd = -1; + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) + { + QAndroidJniObject serialsObj = QAndroidJniObject::fromString(serial); + fd = activity.callMethod("openUSBDevice", "(Ljava/lang/String;)I", serialsObj.object()); + } + + return fd; +} + +void Android::closeUSBDevice(int fd) +{ + if (fd >= 0) + { + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("closeUSBDevice", "(I)V", fd); + } + } +} + +void Android::moveTaskToBack() +{ + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("moveTaskToBack", "(Z)Z", true); + } +} + +void Android::acquireWakeLock() +{ + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("acquireWakeLock"); + } +} + +void Android::releaseWakeLock() +{ + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("releaseWakeLock"); + } +} + +void Android::acquireScreenLock() +{ + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("acquireScreenLock"); + } +} + +void Android::releaseScreenLock() +{ + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + if (activity.isValid()) { + activity.callMethod("releaseScreenLock"); + } +} + +#endif // QT6 + +// Redirect qDebug/qWarning to Android log, so we can view remotely with adb +void Android::messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) +{ + QString report = msg; + if (context.file && !QString(context.file).isEmpty()) + { + report += " in file "; + report += QString(context.file); + report += " line "; + report += QString::number(context.line); + } + if (context.function && !QString(context.function).isEmpty()) + { + report += +" function "; + report += QString(context.function); + } + const char * const local = report.toLocal8Bit().constData(); + const char * const applicationName = "sdrangel"; + int ret; + switch (type) + { + case QtDebugMsg: + ret = __android_log_write(ANDROID_LOG_DEBUG, applicationName, local); + break; + case QtInfoMsg: + ret = __android_log_write(ANDROID_LOG_INFO, applicationName, local); + break; + case QtWarningMsg: + ret = __android_log_write(ANDROID_LOG_WARN, applicationName, local); + break; + case QtCriticalMsg: + ret = __android_log_write(ANDROID_LOG_ERROR, applicationName, local); + break; + case QtFatalMsg: + default: + ret = __android_log_write(ANDROID_LOG_FATAL, applicationName, local); + abort(); + } + if (ret < 0) { + __android_log_write(ANDROID_LOG_ERROR, applicationName, "Error writing to log"); + } +} + +#endif // ANDROID diff --git a/android/app/src/main/cpp/util/android.h b/android/app/src/main/cpp/util/android.h new file mode 100644 index 0000000..4312d98 --- /dev/null +++ b/android/app/src/main/cpp/util/android.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2017, 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_ANDROID_H_ +#define SDRBASE_ANDROID_H_ + +#ifdef ANDROID + +#include +#include + +#include "export.h" + +// Android specific functions +class SDRBASE_API Android +{ +public: + + static void sendIntent(); + static QStringList listUSBDeviceSerials(int vid, int pid); + static int openUSBDevice(const QString &serial); + static void closeUSBDevice(int fd); + static void moveTaskToBack(); + static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); + static void acquireWakeLock(); + static void releaseWakeLock(); + static void acquireScreenLock(); + static void releaseScreenLock(); + +}; + +#endif // ANDROID + +#endif // SDRBASE_ANDROID_H_ diff --git a/android/app/src/main/cpp/util/aprs.cpp b/android/app/src/main/cpp/util/aprs.cpp new file mode 100644 index 0000000..c0dc12d --- /dev/null +++ b/android/app/src/main/cpp/util/aprs.cpp @@ -0,0 +1,1217 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// Copyright (C) 2022 Peter Beckman // +// Copyright (C) 2023 Daniele Forsi // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "aprs.h" +#include "util/units.h" + +inline bool inRange(unsigned low, unsigned high, unsigned x) +{ + return (low <= x && x <= high); +} + +// Required for Mic-E Decoding +inline int charToIntAscii(QString&s, int idx) +{ + char c = s.toLatin1().at(idx); + return int(c); +} + + +// See: http://www.aprs.org/doc/APRS101.PDF + +// Currently we only decode what we want to display on the map +bool APRSPacket::decode(AX25Packet packet) +{ + // Check type, PID and length of packet + if ((packet.m_type == "UI") && (packet.m_pid == "f0") && (packet.m_data.length() >= 1)) + { + // Check destination address + QRegularExpression re("^(AIR.*|ALL.*|AP.*|BEACON|CQ.*|GPS.*|DF.*|DGPS.*|DRILL.*|DX.*|ID.*|JAVA.*|MAIL.*|MICE.*|QST.*|QTH.*|RTCM.*|SKY.*|SPACE.*|SPC.*|SYM.*|TEL.*|TEST.*|TLM.*|WX.*|ZIP.*)"); + QRegularExpression re_mice("^[A-LP-Z0-9]{3}[L-Z0-9]{3}.?$"); // Mic-E Encoded Destination, 6-7 bytes + if (re.match(packet.m_to).hasMatch() || re_mice.match(packet.m_to).hasMatch()) + { + m_from = packet.m_from; + m_to = packet.m_to; + m_via = packet.m_via; + m_data = packet.m_data; + + // UTF-8 is supported: http://aprs.org/aprs12/utf-8.txt + QString data = QString::fromUtf8(packet.m_data); + + if (packet.m_to.startsWith("GPS") || packet.m_to.startsWith("SPC") || packet.m_to.startsWith("SYM")) + { + // FIXME: Trailing letters xyz specify a symbol + } + + // Source address SSID can be used to specify a symbol + + // First byte of information field is data type ID + char dataType = data[0].toLatin1(); + int idx = 1; + switch (dataType) + { + case '!': // Position without timestamp or Ultimeter 2000 WX Station + parsePosition(data, idx); + if (m_symbolCode == '_') + parseWeather(data, idx, false); + else if (m_symbolCode == '@') + parseStorm(data, idx); + else + { + parseDataExension(data, idx); + parseComment(data, idx); + } + break; + case '#': // Peet Bros U-II Weather Station + case '$': // Raw GPS data or Ultimeter 2000 + case '%': // Agrelo DFJr / MicroFinder + break; + case ')': // Item + parseItem(data, idx); + parsePosition(data, idx); + parseDataExension(data, idx); + parseComment(data, idx); + break; + case '*': // Peet Bros U-II Weather Station + break; + case '/': // Position with timestamp (no APRS messaging) + parseTime(data, idx); + parsePosition(data, idx); + if (m_symbolCode == '_') + parseWeather(data, idx, false); + else if (m_symbolCode == '@') + parseStorm(data, idx); + else + { + parseDataExension(data, idx); + parseComment(data, idx); + } + break; + case ':': // Message + parseMessage(data, idx); + break; + case ';': // Object + parseObject(data, idx); + parseTime(data, idx); + parsePosition(data, idx); + if (m_symbolCode == '_') + parseWeather(data, idx, false); + else if (m_symbolCode == '@') + parseStorm(data, idx); + else + { + parseDataExension(data, idx); + parseComment(data, idx); + } + break; + case '<': // Station Capabilities + break; + case '=': // Position without timestamp (with APRS messaging) + parsePosition(data, idx); + if (m_symbolCode == '_') + parseWeather(data, idx, false); + else if (m_symbolCode == '@') + parseStorm(data, idx); + else + { + parseDataExension(data, idx); + parseComment(data, idx); + } + break; + case '>': // Status + parseStatus(data, idx); + break; + case '?': // Query + break; + case '@': // Position with timestamp (with APRS messaging) + parseTime(data, idx); + parsePosition(data, idx); + if (m_symbolCode == '_') + parseWeather(data, idx, false); + else if (m_symbolCode == '@') + parseStorm(data, idx); + else + { + parseDataExension(data, idx); + parseComment(data, idx); + } + break; + case 'T': // Telemetry data + parseTelemetry(data, idx); + break; + case '_': // Weather report (without position) + parseTimeMDHM(data, idx); + parseWeather(data, idx, true); + break; + case '`': // Mic-E Information Field Data (current) + case '\'': // Mic-E Information Field Data (old) + parseMicE(data, idx, m_to); + break; + case '{': // User-defined APRS packet format + break; + default: + return false; + } + + if (m_hasSymbol) + { + int num = m_symbolCode - '!'; + m_symbolImage = QString("aprs/aprs/aprs-symbols-24-%1-%2.png").arg(m_symbolTable == '/' ? 0 : 1).arg(num, 2, 10, QChar('0')); + } + + return true; + } else { + qDebug() << "APRSPacket::decode: AX.25 Destination did not match known regexp " << m_to; + } + } else { + qDebug() << "APRSPacket::decode: Not APRS: type=" << packet.m_type << " pid=" << packet.m_pid << " length=" << packet.m_data.length(); + } + + return false; +} + +int APRSPacket::charToInt(QString&s, int idx) +{ + char c = s[idx].toLatin1(); + return c == ' ' ? 0 : c - '0'; +} + +bool APRSPacket::parseTime(QString& info, int& idx) +{ + if (info.length() < idx+7) + return false; + + QDateTime currentDateTime; + + if (info[idx+6]=='h') + { + // HMS format + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && info[idx+3].isDigit() + && info[idx+4].isDigit() + && info[idx+5].isDigit()) + { + int hour = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int min = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int sec = charToInt(info, idx+4) * 10 + charToInt(info, idx+5); + + if (hour > 23) + return false; + if (min > 59) + return false; + if (sec > 60) // Can have 60 seconds when there's a leap second + return false; + + m_utc = true; + m_timestamp = QDateTime(QDate::currentDate(), QTime(hour, min, sec)); + m_hasTimestamp = true; + + idx += 7; + return true; + } + else + return false; + } + else if ((info[idx+6]=='z') || (info[idx+6]=='/')) + { + // DHM format + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && info[idx+3].isDigit() + && info[idx+4].isDigit() + && info[idx+5].isDigit()) + { + int day = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int hour = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int min = charToInt(info, idx+4) * 10 + charToInt(info, idx+5); + + if (day > 31) + return false; + if (hour > 23) + return false; + if (min > 59) + return false; + + m_utc = info[idx+6]=='z'; + currentDateTime = m_utc ? QDateTime::currentDateTimeUtc() : QDateTime::currentDateTime(); + m_timestamp = QDateTime(QDate(currentDateTime.date().year(), currentDateTime.date().month(), day), QTime(hour, min, 0)); + m_hasTimestamp = true; + + idx += 7; + return true; + } + else + return false; + } + else + return false; +} + +// Time format used in weather reports without position +bool APRSPacket::parseTimeMDHM(QString& info, int& idx) +{ + if (info.length() < idx+8) + return false; + + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && info[idx+3].isDigit() + && info[idx+4].isDigit() + && info[idx+5].isDigit() + && info[idx+6].isDigit() + && info[idx+7].isDigit()) + { + int month = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int day = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int hour = charToInt(info, idx+4) * 10 + charToInt(info, idx+5); + int min = charToInt(info, idx+6) * 10 + charToInt(info, idx+7); + + if (month > 12) + return false; + if (day > 31) + return false; + if (hour > 23) + return false; + if (min > 59) + return false; + + m_utc = true; + QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); + m_timestamp = QDateTime(QDate(currentDateTime.date().year(), month, day), QTime(hour, min, 0)); + m_hasTimestamp = true; + + return true; + } + else + return false; +} + +// Position ambigutiy can be specified by using spaces instead of digits in lats and longs +bool APRSPacket::isLatLongChar(const QChar c) +{ + return (c.isDigit() || c == ' '); +} + +bool APRSPacket::parsePosition(QString& info, int& idx) +{ + float latitude; + float longitude; + char table; + char code; + + if (info.length() < idx+8+1+9+1) + return false; + + // Latitude + if (info[idx].isDigit() + && info[idx+1].isDigit() + && isLatLongChar(info[idx+2]) + && isLatLongChar(info[idx+3]) + && (info[idx+4]=='.') + && isLatLongChar(info[idx+5]) + && isLatLongChar(info[idx+6]) + && ((info[idx+7]=='N') || (info[idx+7]=='S'))) + { + int deg = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int min = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int hundreths = charToInt(info, idx+5) * 10 + charToInt(info, idx+6); + bool north = (info[idx+7]=='N'); + if (deg > 90) + return false; + else if ((deg == 90) && ((min != 0) || (hundreths != 0))) + return false; + latitude = ((float)deg) + min/60.0 + hundreths/60.0/100.0; + if (!north) + latitude = -latitude; + idx += 8; + } + else + return false; + + // Symbol table identifier + table = info[idx++].toLatin1(); + + // Longitude + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && isLatLongChar(info[idx+3]) + && isLatLongChar(info[idx+4]) + && (info[idx+5]=='.') + && isLatLongChar(info[idx+6]) + && isLatLongChar(info[idx+7]) + && ((info[idx+8]=='E') || (info[idx+8]=='W'))) + { + int deg = charToInt(info, idx) * 100 + charToInt(info, idx+1) * 10 + charToInt(info, idx+2); + int min = charToInt(info, idx+3) * 10 + charToInt(info, idx+4); + int hundreths = charToInt(info, idx+6) * 10 + charToInt(info, idx+7); + bool east = (info[idx+8]=='E'); + if (deg > 180) + return false; + else if ((deg == 180) && ((min != 0) || (hundreths != 0))) + return false; + longitude = ((float)deg) + min/60.0 + hundreths/60.0/100.0; + if (!east) + longitude = -longitude; + idx += 9; + } + else + return false; + + // Symbol table code + code = info[idx++].toLatin1(); + + // Update state as we have a valid position + m_latitude = latitude; + m_longitude = longitude; + m_hasPosition = true; + m_symbolTable = table; + m_symbolCode = code; + m_hasSymbol = true; + return true; +} + +bool APRSPacket::parseDataExension(QString& info, int& idx) +{ + int heightMap[] = {10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120}; + QStringList directivityMap = {"Omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N", ""}; + + int remainingLength = info.length() - idx; + if (remainingLength < 7) + return true; + QString s = info.right(remainingLength); + + // Course and speed + QRegularExpression courseSpeed("^([0-9]{3})\\/([0-9]{3})"); + QRegularExpressionMatch match; + match = courseSpeed.match(s); + if (match.hasMatch()) + { + m_course = match.capturedTexts()[1].toInt(); + m_speed = match.capturedTexts()[2].toInt(); + m_hasCourseAndSpeed = true; + idx += 7; + return true; + } + + // Station radio details + QRegularExpression phg("^PHG([0-9])([0-9])([0-9])([0-9])"); + match = phg.match(s); + if (match.hasMatch()) + { + // Transmitter power + int powerCode = match.capturedTexts()[1].toInt(); + int powerMap[] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}; + m_powerWatts = powerMap[powerCode]; + + // Antenna height + int heightCode = match.capturedTexts()[2].toInt(); + m_antennaHeightFt = heightMap[heightCode]; + + // Antenna gain + m_antennaGainDB = match.capturedTexts()[3].toInt(); + + // Antenna directivity + int directivityCode = match.capturedTexts()[4].toInt(); + m_antennaDirectivity = directivityMap[directivityCode]; + + m_hasStationDetails = true; + + idx += 7; + return true; + } + + // Radio range + QRegularExpression rng("^RNG([0-9]{4})"); + match = rng.match(s); + if (match.hasMatch()) + { + m_radioRangeMiles = match.capturedTexts()[1].toInt(); + m_hasRadioRange = true; + idx += 7; + return true; + } + + // Omni-DF strength + QRegularExpression dfs("^DFS([0-9])([0-9])([0-9])([0-9])"); + match = dfs.match(s); + if (match.hasMatch()) + { + // Strength S-points + m_dfStrength = match.capturedTexts()[1].toInt(); + + // Antenna height + int heightCode = match.capturedTexts()[2].toInt(); + m_dfHeightFt = heightMap[heightCode]; + + // Antenna gain + m_dfGainDB = match.capturedTexts()[3].toInt(); + + // Antenna directivity + int directivityCode = match.capturedTexts()[4].toInt(); + m_dfAntennaDirectivity = directivityMap[directivityCode]; + + m_hasDf = true; + idx += 7; + return true; + } + + return true; +} + +bool APRSPacket::parseComment(QString& info, int& idx) +{ + int commentLength = info.length() - idx; + if (commentLength > 0) + { + m_comment = info.right(commentLength); + + // Comment can contain altitude anywhere in it. Of the form /A=001234 in feet + QRegularExpression re("\\/A=([0-9]{6})"); + QRegularExpressionMatch match = re.match(m_comment); + if (match.hasMatch()) + { + m_altitudeFt = match.capturedTexts()[1].toInt(); + m_hasAltitude = true; + // Strip it out of comment if at start of string + if (match.capturedStart(0) == 0) + m_comment = m_comment.mid(9); + } + } + + return true; +} + +bool APRSPacket::parseInt(QString& info, int& idx, int chars, int& value, bool& hasValue) +{ + int total = 0; + bool negative = false; + bool noValue = false; + + for (int i = 0; i < chars; i++) + { + if (info[idx].isDigit()) + { + total = total * 10; + total += info[idx].toLatin1() - '0'; + } + else if ((i == 0) && (info[idx] == '-')) + negative = true; + else if ((info[idx] == '.') || (info[idx] == ' ')) + noValue = true; + else + return false; + idx++; + } + if (!noValue) + { + if (negative) + value = -total; + else + value = total; + hasValue = true; + } + else + hasValue = false; + return true; +} + +bool APRSPacket::parseWeather(QString& info, int& idx, bool positionLess) +{ + if (!positionLess) + { + if (!parseInt(info, idx, 3, m_windDirection, m_hasWindDirection)) + return false; + if (info[idx++] != '/') + return false; + if (!parseInt(info, idx, 3, m_windSpeed, m_hasWindSpeed)) + return false; + } + + // Weather data + bool done = false; + while (!done && (idx < info.length())) + { + switch (info[idx++].toLatin1()) + { + case 'c': // Wind direction + if (!parseInt(info, idx, 3, m_windDirection, m_hasWindDirection)) + return false; + break; + case 's': // Wind speed + if (!parseInt(info, idx, 3, m_windSpeed, m_hasWindSpeed)) + return false; + break; + case 'g': // Gust + if (!parseInt(info, idx, 3, m_gust, m_hasGust)) + return false; + break; + case 't': // Temp + if (!parseInt(info, idx, 3, m_temp, m_hasTemp)) + { + qDebug() << "Failed parsing temp: idx" << idx; + return false; + } + break; + case 'r': // Rain last hour + if (!parseInt(info, idx, 3, m_rainLastHr, m_hasRainLastHr)) + return false; + break; + case 'p': // Rain last 24 hours + if (!parseInt(info, idx, 3, m_rainLast24Hrs, m_hasRainLast24Hrs)) + return false; + break; + case 'P': // Rain since midnight + if (!parseInt(info, idx, 3, m_rainSinceMidnight, m_hasRainSinceMidnight)) + return false; + break; + case 'h': // Humidity + if (!parseInt(info, idx, 2, m_humidity, m_hasHumidity)) + return false; + break; + case 'b': // Barometric pressure + if (!parseInt(info, idx, 5, m_barometricPressure, m_hasBarometricPressure)) + return false; + break; + case 'L': // Luminosity <999 + if (!parseInt(info, idx, 3, m_luminosity, m_hasLuminsoity)) + return false; + break; + case 'l': // Luminosity >= 1000 + if (!parseInt(info, idx, 3, m_luminosity, m_hasLuminsoity)) + return false; + m_luminosity += 1000; + break; + case 'S': // Snowfall + if (!parseInt(info, idx, 3, m_snowfallLast24Hrs, m_hasSnowfallLast24Hrs)) + return false; + break; + case '#': // Raw rain counter + if (!parseInt(info, idx, 3, m_rawRainCounter, m_hasRawRainCounter)) + return false; + break; + case 'X': // Radiation level + if (!parseInt(info, idx, 3, m_radiationLevel, m_hasRadiationLevel)) + return false; + break; + case 'F': // Floor water level + if (!parseInt(info, idx, 4, m_floodLevel, m_hasFloodLevel)) + return false; + break; + case 'V': // Battery volts + if (!parseInt(info, idx, 3, m_batteryVolts, m_hasBatteryVolts)) + return false; + break; + default: + done = true; + break; + } + } + if (done) + { + // APRS 1.1 spec says remaining fields are s/w and weather unit type + // But few real-world packets actually seem to conform to the spec + idx--; + int remaining = info.length() - idx; + m_weatherUnitType = info.right(remaining); + idx += remaining; + } + + m_hasWeather = true; + return true; +} + + +bool APRSPacket::parseStorm(QString& info, int& idx) +{ + bool unused; + + if (!parseInt(info, idx, 3, m_stormDirection, unused)) + return false; + if (info[idx++] != '/') + return false; + if (!parseInt(info, idx, 3, m_stormSpeed, unused)) + return false; + if (info[idx++] != '/') + return false; + QString type = info.mid(idx, 2); + idx += 2; + if (type == "TS") + m_stormType = "Tropical storm"; + else if (type == "HC") + m_stormType = "Hurrican"; + else if (type == "TD") + m_stormType = "Tropical depression"; + else + m_stormType = type; + + if (info[idx++] != '/') // Sustained wind speed + return false; + if (!parseInt(info, idx, 3, m_stormSustainedWindSpeed, unused)) + return false; + if (info[idx++] != '^') // Peak wind gusts + return false; + if (!parseInt(info, idx, 3, m_stormPeakWindGusts, unused)) + return false; + if (info[idx++] != '/') // Central pressure + return false; + if (!parseInt(info, idx, 4, m_stormCentralPresure, unused)) + return false; + if (info[idx++] != '>') // Radius hurrican winds + return false; + if (!parseInt(info, idx, 3, m_stormRadiusHurricanWinds, unused)) + return false; + if (info[idx++] != '&') // Radius tropical storm winds + return false; + if (!parseInt(info, idx, 3, m_stormRadiusTropicalStormWinds, unused)) + return false; + m_hasStormData = true; + // Optional field + if (info.length() >= idx + 4) + { + if (info[idx] != '%') // Radius whole gail + return true; + idx++; + if (!parseInt(info, idx, 3, m_stormRadiusWholeGail, m_hasStormRadiusWholeGail)) + return false; + } + return true; +} + +bool APRSPacket::parseObject(QString& info, int& idx) +{ + if (info.length() < idx+10) + return false; + + // Object names are 9 chars + m_objectName = info.mid(idx, 9).trimmed(); + idx += 9; + + if (info[idx] == '*') + m_objectLive = true; + else if (info[idx] == '_') + m_objectKilled = true; + else + return false; + idx++; + + return true; +} + +bool APRSPacket::parseItem(QString& info, int& idx) +{ + if (info.length() < idx+3) + return false; + + // Item names are 3-9 chars long, excluding ! or _ + m_objectName = ""; + int i; + for (i = 0; i < 10; i++) + { + if (info.length() >= idx) + { + QChar c = info[idx]; + if (c == '!' || c == '_') + break; + else + { + m_objectName.append(c); + idx++; + } + } + } + if (i == 11) + return false; + if (info[idx] == '!') + m_objectLive = true; + else if (info[idx] == '_') + m_objectKilled = true; + idx++; + + return true; +} + +bool APRSPacket::parseStatus(QString& info, int& idx) +{ + QString remaining = info.mid(idx); + + QRegularExpression timestampRE("^([0-9]{6})z"); // DHM timestamp + QRegularExpression maidenheadRE("^([A-Z]{2}[0-9]{2}[A-Z]{0,2})[/\\\\]."); // Maidenhead grid locator and symbol + QRegularExpressionMatch matchTimestamp = timestampRE.match(remaining); + QRegularExpressionMatch matchMaidenhead = maidenheadRE.match(remaining); + + if (matchTimestamp.hasMatch()) + { + parseTime(info, idx); + m_status = info.mid(idx); + idx += m_status.length(); + } + else if (matchMaidenhead.hasMatch()) + { + m_maidenhead = matchMaidenhead.capturedTexts()[1]; + idx += m_maidenhead.length(); + m_symbolTable = info[idx++].toLatin1(); + m_symbolCode = info[idx++].toLatin1(); + m_hasSymbol = true; + if (info[idx] == ' ') + { + idx++; + m_status = info.mid(idx); + idx += m_status.length(); + } + } + else + { + m_status = remaining; + idx += m_status.length(); + } + m_hasStatus = true; + + // Check for beam heading and power in meteor scatter status reports + int len = m_status.length(); + if (len >= 3) + { + if (m_status[len-3] == '^') + { + bool error = false; + char h = m_status[len-2].toLatin1(); + char p = m_status[len-1].toLatin1(); + + if (isdigit(h)) + m_beamHeading = (h - '0') * 10; + else if (isupper(h)) + m_beamHeading = (h - 'A') * 10 + 100; + else + error = true; + + switch (p) + { + case '1': m_beamPower = 10; break; + case '2': m_beamPower = 40; break; + case '3': m_beamPower = 90; break; + case '4': m_beamPower = 160; break; + case '5': m_beamPower = 250; break; + case '6': m_beamPower = 360; break; + case '7': m_beamPower = 490; break; + case '8': m_beamPower = 640; break; + case '9': m_beamPower = 810; break; + case ':': m_beamPower = 1000; break; + case ';': m_beamPower = 1210; break; + case '<': m_beamPower = 1440; break; + case '=': m_beamPower = 1690; break; + case '>': m_beamPower = 1960; break; + case '?': m_beamPower = 2250; break; + case '@': m_beamPower = 2560; break; + case 'A': m_beamPower = 2890; break; + case 'B': m_beamPower = 3240; break; + case 'C': m_beamPower = 3610; break; + case 'D': m_beamPower = 4000; break; + case 'E': m_beamPower = 4410; break; + case 'F': m_beamPower = 4840; break; + case 'G': m_beamPower = 5290; break; + case 'H': m_beamPower = 5760; break; + case 'I': m_beamPower = 6250; break; + case 'J': m_beamPower = 6760; break; + case 'K': m_beamPower = 7290; break; + default: error = true; break; + } + if (!error) + { + m_hasBeam = true; + m_status = m_status.left(len - 3); + } + } + } + return true; +} + +bool APRSPacket::parseMessage(QString& info, int& idx) +{ + if (info.length() < idx+10) + return false; + + // Addressee is fixed width + if (info[idx+9] != ':') + return false; + m_addressee = info.mid(idx, 9).trimmed(); + idx += 10; + + // Message + m_message = info.mid(idx); + idx += m_message.length(); + + // Check if telemetry parameter/unit names + if (m_message.startsWith("PARM.")) + { + bool done = false; + QString s(""); + int i = 5; + while (!done) + { + if (i >= m_message.length()) + { + if (!s.isEmpty()) + m_telemetryNames.append(s); + done = true; + } + else if (m_message[i] == ',') + { + if (!s.isEmpty()) + m_telemetryNames.append(s); + i++; + s = ""; + } + else + s.append(m_message[i++]); + } + } + else if (m_message.startsWith("UNIT.")) + { + bool done = false; + QString s(""); + int i = 5; + while (!done) + { + if (i >= m_message.length()) + { + if (!s.isEmpty()) + m_telemetryLabels.append(s); + done = true; + } + else if (m_message[i] == ',') + { + if (!s.isEmpty()) + m_telemetryLabels.append(s); + i++; + s = ""; + } + else + s.append(m_message[i++]); + } + } + else if (m_message.startsWith("EQNS.")) + { + bool done = false; + QString s(""); + int i = 5; + QList telemetryCoefficients; + while (!done) + { + if (i >= m_message.length()) + { + if (!s.isEmpty()) + telemetryCoefficients.append(s); + done = true; + } + else if (m_message[i] == ',') + { + if (!s.isEmpty()) + telemetryCoefficients.append(s); + i++; + s = ""; + } + else + s.append(m_message[i++]); + } + m_hasTelemetryCoefficients = 0; + for (int j = 0; j < telemetryCoefficients.length() / 3; j++) + { + m_telemetryCoefficientsA[j] = telemetryCoefficients[j*3].toDouble(); + m_telemetryCoefficientsB[j] = telemetryCoefficients[j*3+1].toDouble(); + m_telemetryCoefficientsC[j] = telemetryCoefficients[j*3+2].toDouble(); + m_hasTelemetryCoefficients++; + } + } + else if (m_message.startsWith("BITS.")) + { + QString s(""); + int i = 5; + for (int j = 0; j < 8; j++) + { + if (i >= m_message.length()) + m_telemetryBitSense[j] = m_message[i] == '1'; + else + m_telemetryBitSense[j] = true; + i++; + } + m_hasTelemetryBitSense = true; + m_telemetryProjectName = m_message.mid(i); + i += m_telemetryProjectName.length(); + } + else + { + // Check for message number + QRegularExpression noRE("\\{([0-9]{1,5})$"); + QRegularExpressionMatch match = noRE.match(m_message); + if (match.hasMatch()) + { + m_messageNo = match.capturedTexts()[1]; + m_message = m_message.left(m_message.length() - m_messageNo.length() - 1); + } + } + m_hasMessage = true; + + return true; +} + +bool APRSPacket::parseTelemetry(QString& info, int& idx) +{ + if (info[idx] == '#') + { + // Telemetry report + idx++; + if ((info[idx] == 'M') && (info[idx+1] == 'I') && (info[idx+2] == 'C')) + idx += 3; + else if (isdigit(info[idx].toLatin1()) && isdigit(info[idx+1].toLatin1()) && isdigit(info[idx+2].toLatin1())) + { + m_seqNo = info.mid(idx, 3).toInt(); + m_hasSeqNo = true; + idx += 3; + } + else + return false; + + if (info[idx] == ',') + idx++; + parseInt(info, idx, 3, m_a1, m_a1HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a2, m_a2HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a3, m_a3HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a4, m_a4HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a5, m_a5HasValue); + if (info[idx++] != ',') + return false; + for (int i = 0; i < 8; i++) + m_b[i] = info[idx++] == '1'; + m_bHasValue = true; + + m_telemetryComment = info.mid(idx); + idx += m_telemetryComment.length(); + m_hasTelemetry = true; + return true; + } + else + return false; +} + +// Mic-E Implementation by Peter Beckman KM4BBB github:ooglek +bool APRSPacket::parseMicE(QString& info, int& idx, QString& dest) +{ + info = info.toLatin1(); + // Mic-E Location data is encoded in the AX.25 Destination Address + if (dest.length() < 6) { + qDebug() << "APRSPacket::parseMicE: Destination invalid length " << dest; + return false; + } + + // Mic-E Information data is 8 bytes minimum, 13-14 with altitude + if (info.length() < idx+8) { + qDebug() << "APRSPacket::parseMicE: Information Data invalid length " << info; + return false; + } + + QString latDigits = ""; + QString messageBits = ""; + int messageType = 0; // 0 = Standard, 1 = Custom + int longitudeOffset = 0; + // Assume South & East, as North & West are encoded using consecutive Characters, easier and shorter to code + float latitudeDirection = -1; // South + float longitudeDirection = 1; // East + + QHash messageTypeLookup = { + {"111", "Off Duty"}, + {"110", "En Route"}, + {"101", "In Service"}, + {"100", "Returning"}, + {"011", "Committed"}, + {"010", "Special"}, + {"001", "Priority"}, + {"000", "Emergency"} + }; + + QRegularExpression re("^[A-LP-Z0-9]{3}[L-Z0-9]{3}.?$"); // 6-7 bytes + if (re.match(dest).hasMatch()) { + m_comment = "Mic-E"; + for (int i = 0; i < 6; i++) { + int charInt = charToIntAscii(dest, i); + if (inRange(48, 57, charInt)) { + latDigits.append(QString::number(charInt % 48)); + } else if (inRange(65, 74, charInt)) { + latDigits.append(QString::number(charInt % 65)); + } else if (inRange(80, 89, charInt)) { + latDigits.append(QString::number(charInt % 80)); + } else { + latDigits.append('0'); // Standard states "space" but we put a zero for math + } + + // Message Type is encoded in 3 bits + if (i < 3) { + if (inRange(48, 57, charInt) || charInt == 76) { // 0-9 or L + messageBits.append('0'); + } else if (inRange(80, 90, charInt)) { // P-Z, Standard + messageBits.append('1'); + messageType = 0; + } else if (inRange(65, 75, charInt)) { // A-K, Custom + messageBits.append('1'); + messageType = 1; + } + } + + // Latitude Direction + if (i == 3 && inRange(80, 90, charInt)) { + latitudeDirection = 1; // North + } + + // Longitude Offset + if (i == 4 && inRange(80, 90, charInt)) { + longitudeOffset = 100; + } + + // Longitude Direction + if (i == 5 && inRange(80, 90, charInt)) { + longitudeDirection = -1; // West + } + } + + if (messageTypeLookup.find(messageBits) != messageTypeLookup.end()) { + m_status = messageTypeLookup[messageBits]; + if (messageType == 1) { + m_status.append(" (custom)"); + } + m_hasStatus = true; + } + m_latitude = (latDigits.mid(0, 2).toFloat() + latDigits.mid(2, 2).toFloat()/60.00 + latDigits.mid(4, 2).toFloat()/60.0/100.0) * latitudeDirection; + m_hasPosition = true; + } else { + qDebug() << "APRSPacket::parseMicE: Destination invalid regexp match " << dest; + return false; + } + + // Mic-E Data is encoded in ASCII Characters + if (inRange(38, 127, charToIntAscii(info, idx)) // 0: Longitude Degrees, 0-360 + && inRange(38, 97, charToIntAscii(info, idx+1)) // 1: Longitude Minutes, 0-59 + && inRange(28, 127, charToIntAscii(info, idx+2)) // 2: Longitude Hundredths of a minute, 0-99 + && inRange(28, 127, charToIntAscii(info, idx+3)) // 3: Speed (tens), 0-800 + && inRange(28, 125, charToIntAscii(info, idx+4)) // 4: Speed (ones), 0-9, and Course (hundreds), {0, 100, 200, 300} + && inRange(28, 127, charToIntAscii(info, idx+5)) // 5: Course, 0-99 degrees + ) + { + // Longitude; Degrees plus offset encoded in the AX.25 Destination + // Destination Byte 5, ASCII P through Z indicates an offset of +100 + int deg = (charToIntAscii(info, idx) - 28) + longitudeOffset; + if (inRange(180, 189, deg)) + deg -= 80; + if (inRange(190, 199, deg)) + deg -= 190; + + int min = (charToIntAscii(info, idx+1) - 28) % 60; + int hundreths = charToIntAscii(info, idx+2); + + // Course and Speed + // Speed (SP+28, units of 10) can use two encodings: ASCII 28-47 and 108-127 are the same + // Speed & Course (DC+28, Speed units of 1, Course units of 100 e.g. 0, 100, 200, 300) uses two encodings + int speed = ((charToIntAscii(info, idx+3) - 28) * 10) % 800; // Speed in 10 kts units + float decoded_speed_course = (float)(charToIntAscii(info, idx+4) - 28) / 10.0; + speed += floor(decoded_speed_course); // Speed in 1 kt units, added to above + int course = (((charToIntAscii(info, idx+4) - 28) % 10) * 100) % 400; + course += charToIntAscii(info, idx+5) - 28; + + m_longitude = (((float)deg) + min/60.00 + hundreths/60.0/100.0) * longitudeDirection; + m_hasPosition = true; + m_course = course; + m_speed = speed; + m_hasCourseAndSpeed = true; + } else { + qDebug() << "APRSPacket::parseMicE: Information Data invalid ASCII range " << info; + return false; + } + + // 6: Symbol Code + // 7: Symbol Table ID, / = standard, \ = alternate, "," = Telemetry + if (inRange(33, 126, charToIntAscii(info, idx+6)) + && (charToIntAscii(info, idx+7) == 47 || charToIntAscii(info, idx+7) == 92) + ) + { + m_symbolTable = info[idx+7].toLatin1(); + m_symbolCode = info[idx+6].toLatin1(); + m_hasSymbol = true; + } + + // Altitude, encoded in Status Message in meters, converted to feet, above -10000 meters + // e.g. "4T} -> Doublequote is 34, digit 4 is 52, Capital T is 84. Subtract 33 from each -> 1, 19, 51 + // Multiply -> (1 * 91 * 91) + (19 * 91) + (51 * 1) - 10000 = 61 meters Mean Sea Level (MSL) + // ASCII Integer Character Range is 33 to 127 + float altitude = -10000; + + // 4-5 bytes, we only need the 3 to get altitude, e.g. "4T} + // Some HTs prefix the altitude with ']' or '>', so we match that optionally but ignore it + QRegularExpression re_mice_altitude("[\\]>]?(.{3})}"); + QRegularExpressionMatch altitude_str = re_mice_altitude.match(info); + if (altitude_str.hasMatch()) { + QList micEAltitudeMultipliers = {91 * 91, 91, 1}; + + for (int i = 0; i < 3; i++) { + QString altmatch = altitude_str.captured(1); + int charInt = charToIntAscii(altmatch, i); + if (!inRange(33, 127, charInt)) { + qDebug() << "APRSPacket::parseMicE: Invalid Altitude Byte Found pos:" << QString::number(i) << " ascii int:" << QString::number(charInt); + break; + } + altitude += (float)(charInt - 33) * micEAltitudeMultipliers.at(i); + } + + // Assume that the Mic-E transmission is Above Ground Level + if (altitude >= 0) { + m_altitudeFt = std::round(Units::metresToFeet(altitude)); + m_hasAltitude = true; + } + } + + // Mic-E Text Format + if (info.length() >= 9) { + QString mice_status = info.mid(9); + if (altitude_str.hasMatch() && mice_status.indexOf(altitude_str.captured(0)) != -1) { + mice_status.replace(altitude_str.captured(0), ""); + } + + m_comment += " " + mice_status; + // TODO Implement the APRS 1.2 Mic-E Text Format http://www.aprs.org/aprs12/mic-e-types.txt + // Consider the Kenwood leading characters + } + + // TODO Implement Mic-E Telemetry Data -- need to modify regexp for the Symbol Table Identifier to include comma (,) + + return true; +} + diff --git a/android/app/src/main/cpp/util/aprs.h b/android/app/src/main/cpp/util/aprs.h new file mode 100644 index 0000000..97fe59a --- /dev/null +++ b/android/app/src/main/cpp/util/aprs.h @@ -0,0 +1,508 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// Copyright (C) 2022 Peter Beckman // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_APRS_H +#define INCLUDE_APRS_H + +#include +#include +#include +#include +#include + +#include "export.h" +#include "ax25.h" +#include "util/units.h" + +struct SDRBASE_API APRSPacket { + QString m_from; + QString m_to; + QString m_via; + QByteArray m_data; // Original binary data + + QDateTime m_dateTime; // Date/time of reception / decoding + + // Timestamp (where fields are not transmitted, time of decoding is used) + QDateTime m_timestamp; + bool m_utc; // Whether UTC (true) or local time (false) + bool m_hasTimestamp; + + // Position + float m_latitude; + float m_longitude; + bool m_hasPosition; + + float m_altitudeFt; + bool m_hasAltitude; + + // Symbol + char m_symbolTable; + char m_symbolCode; + bool m_hasSymbol; + QString m_symbolImage; // Image filename for the symbol + + // Course and speed + int m_course; + int m_speed; + bool m_hasCourseAndSpeed; + + // Power, antenna height, gain, directivity + int m_powerWatts; + int m_antennaHeightFt; + int m_antennaGainDB; + QString m_antennaDirectivity; // Omni, or N, NE... + bool m_hasStationDetails; + + // Radio range + int m_radioRangeMiles; + bool m_hasRadioRange; + + // Omni-DF + int m_dfStrength; + int m_dfHeightFt; + int m_dfGainDB; + QString m_dfAntennaDirectivity; + bool m_hasDf; + + QString m_objectName; // Also used for items + bool m_objectLive; + bool m_objectKilled; + + QString m_comment; + + // Weather reports + int m_windDirection; // In degrees + bool m_hasWindDirection; + int m_windSpeed; // In mph + bool m_hasWindSpeed; + int m_gust; // Peak wind speed in last 5 minutes in mph + bool m_hasGust; + int m_temp; // Fahrenheit, can be negative down to -99 + bool m_hasTemp; + int m_rainLastHr; // Hundredths of an inch + bool m_hasRainLastHr; + int m_rainLast24Hrs; + bool m_hasRainLast24Hrs; + int m_rainSinceMidnight; + bool m_hasRainSinceMidnight; + int m_humidity; // % + bool m_hasHumidity; + int m_barometricPressure; // Tenths of millibars / tenths of hPascal + bool m_hasBarometricPressure; + int m_luminosity; // Watts per m^2 + bool m_hasLuminsoity; + int m_snowfallLast24Hrs; // In inches + bool m_hasSnowfallLast24Hrs; + int m_rawRainCounter; + bool m_hasRawRainCounter; + int m_radiationLevel; + bool m_hasRadiationLevel; + int m_floodLevel; // Tenths of a foot. Can be negative + bool m_hasFloodLevel; + int m_batteryVolts; // Tenths of a volt + bool m_hasBatteryVolts; + QString m_weatherUnitType; + bool m_hasWeather; + + int m_stormDirection; + int m_stormSpeed; + QString m_stormType; + int m_stormSustainedWindSpeed; // knots + int m_stormPeakWindGusts; // knots + int m_stormCentralPresure; // millibars/hPascal + int m_stormRadiusHurricanWinds; // nautical miles + int m_stormRadiusTropicalStormWinds;// nautical miles + int m_stormRadiusWholeGail; // nautical miles + bool m_hasStormRadiusWholeGail; + bool m_hasStormData; + + // Status messages + QString m_status; + QString m_maidenhead; + int m_beamHeading; + int m_beamPower; + bool m_hasBeam; + bool m_hasStatus; + + // Messages + QString m_addressee; + QString m_message; + QString m_messageNo; + bool m_hasMessage; + QList m_telemetryNames; + QList m_telemetryLabels; + double m_telemetryCoefficientsA[5]; + double m_telemetryCoefficientsB[5]; + double m_telemetryCoefficientsC[5]; + int m_hasTelemetryCoefficients; + int m_telemetryBitSense[8]; + bool m_hasTelemetryBitSense; + QString m_telemetryProjectName; + + // Telemetry + int m_seqNo; + bool m_hasSeqNo; + int m_a1; + bool m_a1HasValue; + int m_a2; + bool m_a2HasValue; + int m_a3; + bool m_a3HasValue; + int m_a4; + bool m_a4HasValue; + int m_a5; + bool m_a5HasValue; + bool m_b[8]; + bool m_bHasValue; + QString m_telemetryComment; + bool m_hasTelemetry; + + bool decode(AX25Packet packet); + + APRSPacket() : + m_hasTimestamp(false), + m_hasPosition(false), + m_hasAltitude(false), + m_hasSymbol(false), + m_hasCourseAndSpeed(false), + m_hasStationDetails(false), + m_hasRadioRange(false), + m_hasDf(false), + m_objectLive(false), + m_objectKilled(false), + m_hasWindDirection(false), + m_hasWindSpeed(false), + m_hasGust(false), + m_hasTemp(false), + m_hasRainLastHr(false), + m_hasRainLast24Hrs(false), + m_hasRainSinceMidnight(false), + m_hasHumidity(false), + m_hasBarometricPressure(false), + m_hasLuminsoity(false), + m_hasSnowfallLast24Hrs(false), + m_hasRawRainCounter(false), + m_hasRadiationLevel(false), + m_hasFloodLevel(false), + m_hasBatteryVolts(false), + m_hasWeather(false), + m_hasStormRadiusWholeGail(false), + m_hasStormData(false), + m_hasBeam(false), + m_hasStatus(false), + m_hasMessage(false), + m_hasTelemetryCoefficients(0), + m_hasTelemetryBitSense(false), + m_hasSeqNo(false), + m_a1HasValue(false), + m_a2HasValue(false), + m_a3HasValue(false), + m_a4HasValue(false), + m_a5HasValue(false), + m_bHasValue(false), + m_hasTelemetry(false) + { + } + + QString date() + { + if (m_hasTimestamp) + return m_timestamp.date().toString("yyyy/MM/dd"); + else + return QString(""); + } + + QString time() + { + if (m_hasTimestamp) + return m_timestamp.time().toString("hh:mm:ss"); + else + return QString(""); + } + + QString dateTime() + { + return QString("%1 %2").arg(date()).arg(time()); + } + + QString position() + { + return QString("%1,%2").arg(m_latitude).arg(m_longitude); + } + + QByteArray toTNC2(QString igateCallsign) + { + QByteArray data; + + data.append(m_from.toLatin1()); + data.append('>'); + data.append(m_to.toLatin1()); + if (!m_via.isEmpty()) + { + data.append(','); + data.append(m_via.toLatin1()); + } + data.append(",qAR,"); + data.append(igateCallsign.toLatin1()); + data.append(':'); + + // #2028 - Protect against APRS-IS server command injection, by only sending up to first CR/LF + int idx = m_data.indexOf("\r"); + if (idx == -1) { + idx = m_data.indexOf("\n"); + } + if (idx >= 0) { + data.append(m_data.left(idx)); + } else { + data.append(m_data); + } + data.append('\r'); + data.append('\n'); + + return data; + } + + // Convert a TNC2 formatted packet (as sent by APRS-IS Igates) to an AX25 byte array + static QByteArray toByteArray(QString tnc2) + { + QByteArray bytes; + QString tmp = ""; + QString from; + int state = 0; + + for (int i = 0; i < tnc2.length(); i++) + { + if (state == 0) + { + // From + if (tnc2[i] == '>') + { + from = tmp; + tmp = ""; + state = 1; + } + else + tmp.append(tnc2[i]); + } + else if (state == 1) + { + // To + if (tnc2[i] == ':') + { + bytes.append(AX25Packet::encodeAddress(tmp)); + bytes.append(AX25Packet::encodeAddress(from, 1)); + state = 3; + } + else if (tnc2[i] == ',') + { + bytes.append(AX25Packet::encodeAddress(tmp)); + bytes.append(AX25Packet::encodeAddress(from)); + tmp = ""; + state = 2; + } + else + tmp.append(tnc2[i]); + } + else if (state == 2) + { + // Via + if (tnc2[i] == ':') + { + bytes.append(AX25Packet::encodeAddress(tmp, 1)); + state = 3; + } + else if (tnc2[i] == ',') + { + bytes.append(AX25Packet::encodeAddress(tmp)); + tmp = ""; + } + else + tmp.append(tnc2[i]); + } + else if (state == 3) + { + // UI Type and PID + bytes.append(3); + bytes.append(-16); // 0xf0 + // APRS message + bytes.append(tnc2.mid(i).toLatin1().trimmed()); + // CRC + bytes.append((char)0); + bytes.append((char)0); + break; + } + } + + return bytes; + } + + QString toText(bool includeFrom=true, bool includePosition=false, char separator='\n', + bool altitudeMetres=false, int speedUnits=0, bool tempCelsius=false, bool rainfallMM=false) + { + QStringList text; + + if (!m_objectName.isEmpty()) + { + text.append(QString("%1 (%2)").arg(m_objectName).arg(m_from)); + } + else + { + if (includeFrom) + text.append(m_from); + } + + if (m_hasTimestamp) + { + QStringList time; + time.append(this->date()); + time.append(this->time()); + if (m_utc) + time.append("UTC"); + else + time.append("local"); + text.append(time.join(' ')); + } + + if (includePosition && m_hasPosition) + text.append(QString("Latitude: %1 Longitude: %2").arg(m_latitude).arg(m_longitude)); + if (m_hasAltitude) + { + if (altitudeMetres) + text.append(QString("Altitude: %1 m").arg((int)std::round(Units::feetToMetres(m_altitudeFt)))); + else + text.append(QString("Altitude: %1 ft").arg(m_altitudeFt)); + } + if (m_hasCourseAndSpeed) + { + if (speedUnits == 0) + text.append(QString("Course: %1%3 Speed: %2 knts").arg(m_course).arg(m_speed).arg(QChar(0xb0))); + else if (speedUnits == 1) + text.append(QString("Course: %1%3 Speed: %2 mph").arg(m_course).arg(Units::knotsToIntegerMPH(m_speed)).arg(QChar(0xb0))); + else + text.append(QString("Course: %1%3 Speed: %2 kph").arg(m_course).arg(Units::knotsToIntegerKPH(m_speed)).arg(QChar(0xb0))); + } + + if (m_hasStationDetails) + text.append(QString("TX Power: %1 Watts Antenna Height: %2 m Gain: %3 dB Direction: %4").arg(m_powerWatts).arg(std::round(Units::feetToMetres(m_antennaHeightFt))).arg(m_antennaGainDB).arg(m_antennaDirectivity)); + if (m_hasRadioRange) + text.append(QString("Range: %1 km").arg(Units::milesToKilometres(m_radioRangeMiles))); + if (m_hasDf) + text.append(QString("DF Strength: S %1 Height: %2 m Gain: %3 dB Direction: %4").arg(m_dfStrength).arg(std::round(Units::feetToMetres(m_dfHeightFt))).arg(m_dfGainDB).arg(m_dfAntennaDirectivity)); + + if (m_hasWeather) + { + QStringList weather, wind, air, rain; + + wind.append(QString("Wind")); + if (m_hasWindDirection) + wind.append(QString("%1%2").arg(m_windDirection).arg(QChar(0xb0))); + if (m_hasWindSpeed) + wind.append(QString("%1 mph").arg(m_windSpeed)); + if (m_hasGust) + wind.append(QString("Gusts %1 mph").arg(m_gust)); + weather.append(wind.join(' ')); + + if (m_hasTemp || m_hasHumidity || m_hasBarometricPressure) + { + air.append("Air"); + if (m_hasTemp) + { + if (tempCelsius) + air.append(QString("Temperature %1C").arg(Units::fahrenheitToCelsius(m_temp), 0, 'f', 1)); + else + air.append(QString("Temperature %1F").arg(m_temp)); + } + if (m_hasHumidity) + air.append(QString("Humidity %1%").arg(m_humidity)); + if (m_hasBarometricPressure) + air.append(QString("Pressure %1 mbar").arg(m_barometricPressure/10.0)); + weather.append(air.join(' ')); + } + + if (m_hasRainLastHr || m_hasRainLast24Hrs || m_hasRainSinceMidnight) + { + rain.append("Rain"); + if (m_hasRainLastHr) + { + if (rainfallMM) + rain.append(QString("%1 mm last hour").arg(std::round(Units::inchesToMilimetres(m_rainLastHr/100.0)))); + else + rain.append(QString("%1 1/100\" last hour").arg(m_rainLastHr)); + } + if (m_hasRainLast24Hrs) + { + if (rainfallMM) + rain.append(QString("%1 mm last 24 hours").arg(std::round(Units::inchesToMilimetres(m_rainLast24Hrs/100.0)))); + else + rain.append(QString("%1 1/100\" last 24 hours").arg(m_rainLast24Hrs)); + } + if (m_hasRainSinceMidnight) + { + if (rainfallMM) + rain.append(QString("%1 mm since midnight").arg(std::round(Units::inchesToMilimetres(m_rainSinceMidnight/100.0)))); + else + rain.append(QString("%1 1/100\" since midnight").arg(m_rainSinceMidnight)); + } + weather.append(rain.join(' ')); + } + + if (!m_weatherUnitType.isEmpty()) + weather.append(m_weatherUnitType); + + text.append(weather.join(separator)); + } + + if (m_hasStormData) + { + QStringList storm; + + storm.append(m_stormType); + + storm.append(QString("Direction: %1%3 Speed: %2").arg(m_stormDirection).arg(m_stormSpeed).arg(QChar(0xb0))); + storm.append(QString("Sustained wind speed: %1 knots Peak wind gusts: %2 knots Central pressure: %3 mbar").arg(m_stormSustainedWindSpeed).arg(m_stormPeakWindGusts).arg(m_stormCentralPresure)); + storm.append(QString("Hurrican winds radius: %1 nm Tropical storm winds radius: %2 nm%3").arg(m_stormRadiusHurricanWinds).arg(m_stormRadiusTropicalStormWinds).arg(m_hasStormRadiusWholeGail ? QString("") : QString(" Whole gail radius: %3 nm").arg(m_stormRadiusWholeGail))); + + text.append(storm.join(separator)); + } + + if (!m_comment.isEmpty()) + text.append(m_comment); + + return text.join(separator); + } + +private: + int charToInt(QString &s, int idx); + bool parseTime(QString& info, int& idx); + bool parseTimeMDHM(QString& info, int& idx); + bool isLatLongChar(const QChar c); + bool parsePosition(QString& info, int& idx); + bool parseDataExension(QString& info, int& idx); + bool parseComment(QString& info, int& idx); + bool parseInt(QString& info, int& idx, int chars, int& value, bool& hasValue); + bool parseWeather(QString& info, int& idx, bool positionLess); + bool parseStorm(QString& info, int& idx); + bool parseObject(QString& info, int& idx); + bool parseItem(QString& info, int& idx); + bool parseStatus(QString& info, int& idx); + bool parseMessage(QString& info, int& idx); + bool parseTelemetry(QString& info, int& idx); + bool parseMicE(QString& info, int& idx, QString& dest); +}; + +#endif // INCLUDE_APRS_H diff --git a/android/app/src/main/cpp/util/aprsfi.cpp b/android/app/src/main/cpp/util/aprsfi.cpp new file mode 100644 index 0000000..71786fd --- /dev/null +++ b/android/app/src/main/cpp/util/aprsfi.cpp @@ -0,0 +1,187 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "aprsfi.h" + +#include +#include +#include +#include +#include +#include + +QMutex APRSFi::m_mutex; +QHash APRSFi::m_aisCache; + +APRSFi::APRSFi(const QString& apiKey, int cacheValidMins) : + m_apiKey(apiKey), + m_cacheValidMins(cacheValidMins) +{ + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &APRSFi::handleReply); +} + +APRSFi::~APRSFi() +{ + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &APRSFi::handleReply); + delete m_networkManager; +} + +APRSFi* APRSFi::create(const QString& apiKey, int cacheValidMins) +{ + return new APRSFi(apiKey, cacheValidMins); +} + +void APRSFi::getData(const QStringList& names) +{ + QStringList nonCachedNames; + QDateTime currentDateTime = QDateTime::currentDateTime(); + + QMutexLocker locker(&m_mutex); + for (const auto& name : names) + { + bool cached = false; + QList dataList; + if (m_aisCache.contains(name)) + { + const AISData& d = m_aisCache[name]; + if (d.m_dateTime.secsTo(currentDateTime) < m_cacheValidMins*60) + { + dataList.append(d); + cached = true; + } + } + if (dataList.size() > 0) { + emit dataUpdated(dataList); + } + if (!cached) { + nonCachedNames.append(name); + } + } + if (nonCachedNames.size() > 0) + { + QString nameList = nonCachedNames.join(","); + QUrl url(QString("https://api.aprs.fi/api/get")); + QUrlQuery query; + query.addQueryItem("name", nameList); + query.addQueryItem("what", "loc"); + query.addQueryItem("apikey", m_apiKey); + query.addQueryItem("format", "json"); + url.setQuery(query); + m_networkManager->get(QNetworkRequest(url)); + } +} + +void APRSFi::getData(const QString& name) +{ + QStringList names; + names.append(name); + getData(names); +} + +bool APRSFi::containsNonNull(const QJsonObject& obj, const QString &key) const +{ + if (obj.contains(key)) + { + QJsonValue val = obj.value(key); + return !val.isNull(); + } + return false; +} + +void APRSFi::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + + if (document.isObject()) + { + QJsonObject docObj = document.object(); + QDateTime receivedDateTime = QDateTime::currentDateTime(); + + if (docObj.contains(QStringLiteral("entries"))) + { + QJsonArray array = docObj.value(QStringLiteral("entries")).toArray(); + + QList data; + for (auto valRef : array) + { + if (valRef.isObject()) + { + QJsonObject obj = valRef.toObject(); + + AISData measurement; + + measurement.m_dateTime = receivedDateTime; + if (obj.contains(QStringLiteral("name"))) { + measurement.m_name = obj.value(QStringLiteral("name")).toString(); + } + if (obj.contains(QStringLiteral("mmsi"))) { + measurement.m_mmsi = obj.value(QStringLiteral("mmsi")).toString(); + } + if (containsNonNull(obj, QStringLiteral("time"))) { + measurement.m_firstTime = QDateTime::fromString(obj.value(QStringLiteral("time")).toString(), Qt::ISODate); + } + if (containsNonNull(obj, QStringLiteral("lastTime"))) { + measurement.m_lastTime = QDateTime::fromString(obj.value(QStringLiteral("lastTime")).toString(), Qt::ISODate); + } + if (containsNonNull(obj, QStringLiteral("lat"))) { + measurement.m_latitude = obj.value(QStringLiteral("lat")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("lng"))) { + measurement.m_longitude = obj.value(QStringLiteral("lng")).toDouble(); + } + + data.append(measurement); + + if (!measurement.m_mmsi.isEmpty()) + { + QMutexLocker locker(&m_mutex); + m_aisCache.insert(measurement.m_mmsi, measurement); + } + } + else + { + qDebug() << "APRSFi::handleReply: Array element is not an object: " << valRef; + } + } + if (data.size() > 0) { + emit dataUpdated(data); + } else { + qDebug() << "APRSFi::handleReply: No data in array: " << document; + } + } + } + else + { + qDebug() << "APRSFi::handleReply: Document is not an object: " << document; + } + } + else + { + qWarning() << "APRSFi::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qWarning() << "APRSFi::handleReply: reply is null"; + } +} diff --git a/android/app/src/main/cpp/util/aprsfi.h b/android/app/src/main/cpp/util/aprsfi.h new file mode 100644 index 0000000..8cda240 --- /dev/null +++ b/android/app/src/main/cpp/util/aprsfi.h @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_APRSFI_H +#define INCLUDE_APRSFI_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +// aprs.fi API +// Allows querying APRS and AIS data +// Data can be cached to help avoid rate limiting on the server +class SDRBASE_API APRSFi : public QObject +{ + Q_OBJECT +protected: + APRSFi(const QString& apiKey, int cacheValidMins); + +public: + + struct LocationData { + QString m_name; + QDateTime m_firstTime; // First time this position was reported + QDateTime m_lastTime; // Last time this position was reported + float m_latitude; + float m_longitude; + QString m_callsign; + + QDateTime m_dateTime; // Data/time this data was received from APRS.fi + + LocationData() : + m_latitude(NAN), + m_longitude(NAN) + { + } + }; + + struct AISData : LocationData { + QString m_mmsi; + QString m_imo; + + AISData() + { + } + }; + + // Keys are free from https://aprs.fi/ - so get your own + static APRSFi* create(const QString& apiKey="184212.WhYgz2jqu3l2O", int cacheValidMins=10); + + ~APRSFi(); + void getData(const QStringList& names); + void getData(const QString& name); + +private slots: + void handleReply(QNetworkReply* reply); + +signals: + void dataUpdated(const QList& data); // Called when new data available. + +private: + bool containsNonNull(const QJsonObject& obj, const QString &key) const; + + QNetworkAccessManager *m_networkManager; + QString m_apiKey; + int m_cacheValidMins; + static QMutex m_mutex; + static QHash m_aisCache; +}; + +#endif /* INCLUDE_APRSFI_H */ diff --git a/android/app/src/main/cpp/util/astronomy.cpp b/android/app/src/main/cpp/util/astronomy.cpp new file mode 100644 index 0000000..b64a69b --- /dev/null +++ b/android/app/src/main/cpp/util/astronomy.cpp @@ -0,0 +1,3901 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// Copyright (C) 2004 Rutherford Appleton Laboratory // +// Copyright (C) 2012 Science and Technology Facilities Council. // +// Copyright (C) 2013-2021, NumFOCUS Foundation. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include "util/units.h" +#include "astronomy.h" + +const double Astronomy::m_boltzmann = 1.380649e-23; //!< Boltzmann constant (k) in J/K +const double Astronomy::m_hydrogenLineFrequency = 1420405751.768; //!< Hydrogen line frequency in Hz +const double Astronomy::m_hydroxylLineFrequency = 1612231000.0; //!< Strongest hydroxyl line frequency in Hz +const double Astronomy::m_deuteriumLineFrequency = 327384000.0; //!< Deuterium line frequency in Hz - https://www.nap.edu/read/21774/chapter/11 +const double Astronomy::m_speedOfLight = 299792458.0; // Speed of light in m/s +const double Astronomy::m_hydrogenMass = 1.674e-27; // Mass of hydrogen atom in kg (same as proton) + +// Function prototypes +static void palRefz(double zu, double refa, double refb, double *zr); +static void palRefco (double hm, double tdk, double pmb, double rh, + double wl, double phi, double tlr, double eps, + double *refa, double *refb); +static void palRefro( double zobs, double hm, double tdk, double pmb, + double rh, double wl, double phi, double tlr, + double eps, double * ref); +static void palEpv( double date, double ph[3], double vh[3], + double pb[3], double vb[3] ); +static int eraEpv00(double date1, double date2, + double pvh[2][3], double pvb[2][3]); + + +// Calculate Julian date (days since January 1, 4713 BC) from Gregorian calendar date +double Astronomy::julianDate(int year, int month, int day, int hours, int minutes, int seconds) +{ + int julian_day; + double julian_date; + + // From: https://en.wikipedia.org/wiki/Julian_day + julian_day = (1461 * (year + 4800 + (month - 14)/12))/4 +(367 * (month - 2 - 12 * ((month - 14)/12)))/12 - (3 * ((year + 4900 + (month - 14)/12)/100))/4 + day - 32075; + julian_date = julian_day + (hours/24.0 - 0.5) + minutes/(24.0*60.0) + seconds/(24.0*60.0*60.0); + + return julian_date; +} + +// Calculate Julian date +double Astronomy::julianDate(QDateTime dt) +{ + QDateTime utc = dt.toUTC(); + QDate date = utc.date(); + QTime time = utc.time(); + return julianDate(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second()); +} + +// Calculate modified Julian date +double Astronomy::modifiedJulianDate(QDateTime dt) +{ + return Astronomy::julianDate(dt) - 2400000.5; +} + +// Convert Julian date to QDateTime +QDateTime Astronomy::julianDateToDateTime(double jd) +{ + return QDateTime::fromSecsSinceEpoch((jd - 2440587.5) * 24.0*60.0*60.0); +} + +// Get Julian date of J2000 Epoch +double Astronomy::jd_j2000(void) +{ + static double j2000 = 0.0; + + if (j2000 == 0.0) { + j2000 = julianDate(2000, 1, 1, 12, 0, 0); + } + return j2000; +} + +// Get Julian date of B1950 Epoch +double Astronomy::jd_b1950(void) +{ + static double b1950 = 0.0; + + if (b1950 == 0.0) { + b1950 = julianDate(1949, 12, 31, 22, 9, 0); + } + return b1950; +} + +// Get Julian date of current time Epoch +double Astronomy::jd_now(void) +{ + time_t system_time; + struct tm *utc_time; + + // Get current time in seconds since Unix Epoch (1970) + time(&system_time); + // Convert to UTC (GMT) + utc_time = gmtime(&system_time); + + // Convert to Julian date + return julianDate(utc_time->tm_year + 1900, utc_time->tm_mon + 1, utc_time->tm_mday, + utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec); +} + +// Precess a RA/DEC between two given Epochs +RADec Astronomy::precess(RADec rd_in, double jd_from, double jd_to) +{ + RADec rd_out; + double x, y, z; + double xp, yp, zp; + double ra_rad, dec_rad; + double rot[3][3]; // [row][col] + double ra_deg; + + double days_per_century = 36524.219878; + double t0 = (jd_from - jd_b1950())/days_per_century; // Tropical centuries since B1950.0 + double t = (jd_to - jd_from)/days_per_century; // Tropical centuries from starting epoch to ending epoch + + // From: https://www.cloudynights.com/topic/561254-ra-dec-epoch-conversion/ + rot[0][0] = 1.0 - ((29696.0 + 26.0*t0)*t*t - 13.0*t*t*t)*.00000001; + rot[1][0] = ((2234941.0 + 1355.0*t0)*t - 676.0*t*t + 221.0*t*t*t)*.00000001; + rot[2][0] = ((971690.0 - 414.0*t0)*t + 207.0*t*t + 96.0*t*t*t)*.00000001; + rot[0][1] = -rot[1][0]; + rot[1][1] = 1.0 - ((24975.0 + 30.0*t0)*t*t - 15.0*t*t*t)*.00000001; + rot[2][1] = -((10858.0 + 2.0*t0)*t*t)*.00000001; + rot[0][2] = -rot[2][0]; + rot[1][2] = rot[2][1]; + rot[2][2] = 1.0 - ((4721.0 - 4.0*t0)*t*t)*.00000001; + + // Hours to degrees + ra_deg = rd_in.ra*(360.0/24.0); + + // Convert to rectangular coordinates + ra_rad = Units::degreesToRadians(ra_deg); + dec_rad = Units::degreesToRadians(rd_in.dec); + x = cos(ra_rad) * cos(dec_rad); + y = sin(ra_rad) * cos(dec_rad); + z = sin(dec_rad); + + // Rotate + xp = rot[0][0]*x + rot[0][1]*y + rot[0][2]*z; + yp = rot[1][0]*x + rot[1][1]*y + rot[1][2]*z; + zp = rot[2][0]*x + rot[2][1]*y + rot[2][2]*z; + + // Convert back to spherical coordinates + rd_out.ra = Units::radiansToDegrees(atan(yp/xp)); + if (xp < 0.0) { + rd_out.ra += 180.0; + } else if ((yp < 0) && (xp > 0)) { + rd_out.ra += 360.0; + } + rd_out.dec = Units::radiansToDegrees(asin(zp)); + + // Degrees to hours + rd_out.ra /= (360.0/24.0); + + return rd_out; +} + +// Calculate local mean sidereal time (LMST) in degrees +double Astronomy::localSiderealTime(QDateTime dateTime, double longitude) +{ + double jd = julianDate(dateTime); + + double d = (jd - jd_j2000()); // Days since J2000 epoch (including fraction) + double f = fmod(jd, 1.0); // Fractional part is decimal days + double ut = (f+0.5)*24.0; // Universal time in decimal hours + + // https://astronomy.stackexchange.com/questions/24859/local-sidereal-time + // 100.46 is offset for GMST at 0h UT on 1 Jan 2000 + // 0.985647 is number of degrees per day over 360 the Earth rotates in one solar day + // Approx to 0.3 arcseconds + return fmod(100.46 + 0.985647 * d + longitude + (360/24) * ut, 360.0); +} + +// Convert from J2000 right ascension (decimal hours) and declination (decimal degrees) to altitude and azimuth, for location (decimal degrees) and time +AzAlt Astronomy::raDecToAzAlt(RADec rd, double latitude, double longitude, QDateTime dt, bool j2000) +{ + AzAlt aa; + double ra_deg; // Right ascension in degrees + double lst_deg; // Local sidereal time in degrees + double ha; // Hour angle + double a, az; + double dec_rad, lat_rad, ha_rad, alt_rad; // Corresponding variables as radians + + double jd = julianDate(dt); + + // Precess RA/DEC from J2000 Epoch to desired (typically current) Epoch + if (j2000) + rd = precess(rd, jd_j2000(), jd); + + // Calculate local mean sidereal time (LMST) in degrees + // https://astronomy.stackexchange.com/questions/24859/local-sidereal-time + // 100.46 is offset for GMST at 0h UT on 1 Jan 2000 + // 0.985647 is number of degrees per day over 360 the Earth rotates in one solar day + // Approx to 0.3 arcseconds + lst_deg = Astronomy::localSiderealTime(dt, longitude); + + // Convert right ascension from hours to degrees + ra_deg = rd.ra * (360.0/24.0); + + // Calculate hour angle + ha = fmod(lst_deg - ra_deg, 360.0); + + // Convert degrees to radians + dec_rad = Units::degreesToRadians(rd.dec); + lat_rad = Units::degreesToRadians(latitude); + ha_rad = Units::degreesToRadians(ha); + + // Calculate altitude and azimuth - no correction for atmospheric refraction + // From: http://www.convertalot.com/celestial_horizon_co-ordinates_calculator.html + alt_rad = asin(sin(dec_rad)*sin(lat_rad) + cos(dec_rad)*cos(lat_rad)*cos(ha_rad)); + a = Units::radiansToDegrees(acos((sin(dec_rad)-sin(alt_rad)*sin(lat_rad)) / (cos(alt_rad)*cos(lat_rad)))); + if (sin(ha_rad) < 0.0) { + az = a; + } else { + az = 360.0 - a; + } + + aa.alt = Units::radiansToDegrees(alt_rad); + aa.az = az; + return aa; +} + +// Convert from altitude and azimuth, for location (decimal degrees) and time, to Jnow right ascension (decimal hours) and declination (decimal degrees) +// See: http://jonvoisey.net/blog/2018/07/data-converting-alt-az-to-ra-dec-example/ appears to be wrong for HA +// See: https://www.cambridge.org/ca/academic/subjects/physics/amateur-and-popular-astronomy/practical-astronomy-your-calculator-or-spreadsheet-4th-edition?format=PB HOREQ.xls +RADec Astronomy::azAltToRaDec(AzAlt aa, double latitude, double longitude, QDateTime dt) +{ + RADec rd; + double lst_deg; // Local sidereal time + double ha_rad, ha_deg; // Hour angle + double alt_rad, az_rad, lat_rad, dec_rad; // Corresponding variables as radians + + // Calculate local mean sidereal time (LMST) in degrees (see raDecToAzAlt) + lst_deg = Astronomy::localSiderealTime(dt, longitude); + + // Convert degrees to radians + alt_rad = Units::degreesToRadians(aa.alt); + az_rad = Units::degreesToRadians(aa.az); + lat_rad = Units::degreesToRadians(latitude); + + // Calculate declination + double sin_dec_rad = sin(lat_rad)*sin(alt_rad)+cos(lat_rad)*cos(alt_rad)*cos(az_rad); + dec_rad = asin(sin_dec_rad); + + // Calculate hour angle + if (0) + { + double quotient = (sin(alt_rad)-sin(lat_rad)*sin(dec_rad))/(cos(lat_rad)*cos(dec_rad)); + // At extreme altitudes, we seem to get small numerical errors that causes values to be out of range, so clip to [-1,1] + if (quotient < -1.0) { + ha_rad = acos(-1.0); + } else if (quotient > 1.0) { + ha_rad = acos(1.0); + } else { + ha_rad = acos(quotient); + } + } + else + { + double y = -cos(alt_rad)*cos(lat_rad)*sin(az_rad); + double x = sin(alt_rad)-sin(lat_rad)*sin_dec_rad; + ha_rad = atan2(y, x); + } + + // Convert radians to degrees + rd.dec = Units::radiansToDegrees(dec_rad); + ha_deg = Units::radiansToDegrees(ha_rad); + + // Calculate right ascension in decimal hours + rd.ra = modulo((lst_deg - ha_deg) / (360.0/24.0), 24.0); + + return rd; +} + +// https://ntrs.nasa.gov/api/citations/19670030005/downloads/19670030005.pdf +// X85 is positive Southward +// Y85 is positive Eastward +// X30 is positive Eastward +// Y30 is positive Northward +// atan2 range is (-pi,pi], we want az (0,360], so need to add pi +void Astronomy::azAltToXY85(AzAlt aa, double& x, double& y) +{ + if (aa.alt == 90.0) + { + x = 0.0; + y = 0.0; + //qDebug() << "azAltToXY85" << aa.az << aa.alt << x << y; + return; + } + double az = aa.az; + double el = aa.alt; + if (az >= 360.0) { + az -= 360.0; + } + if (el > 90.0) + { + el = 180.0 - el; + if (az >= 180.0) { + az = az - 180.0; + } else { + az = az + 180.0; + } + } + double azr = Units::degreesToRadians(az); + double elr = Units::degreesToRadians(el); + y = Units::radiansToDegrees(asin(cos(elr) * sin(azr))); + if (az == 0.0) + { + // cot(0) == Inf + if ((az == 90.0) || (az == 270.0)) { + x = 0.0; + } else if ((az > 90.0f) && (az < 270.0)) { + x = 90.0; + } else { + x = -90.0; + } + } + else + { + // cot(x)=1/tan(x)=cos(x)/sin(x) + x = Units::radiansToDegrees(atan(-(cos(elr)/sin(elr)) * cos(azr))); + } + + //qDebug() << "azAltToXY85" << aa.az << aa.alt << x << y; +} + +void Astronomy::azAltToXY30(AzAlt aa, double& x, double& y) +{ + if (aa.alt == 90.0) + { + x = 0.0; + y = 0.0; + //qDebug() << "azAltToXY30" << aa.az << aa.alt << x << y; + return; + } + double az = aa.az; + double el = aa.alt; + if (az >= 360.0) { + az -= 360.0; + } + if (el > 90.0) + { + el = 180.0 - el; + if (az >= 180.0) { + az = az - 180.0; + } else { + az = az + 180.0; + } + } + double azr = Units::degreesToRadians(az); + double elr = Units::degreesToRadians(el); + y = Units::radiansToDegrees(asin(cos(elr) * cos(azr))); + if (el == 0.0) + { + if ((az == 0.0) || (az == 180.0)) { + x = 0.0; + } else if ((az >= 0.0f) && (az <= 180.0)) { + x = 90.0; + } else { + x = -90.0; + } + } + else + { + x = Units::radiansToDegrees(atan((cos(elr)/sin(elr)) * sin(azr))); + } + //qDebug() << "azAltToXY30" << aa.az << aa.alt << x << y; +} + +AzAlt Astronomy::xy85ToAzAlt(double x, double y) +{ + AzAlt aa; + if ((x == 0.0) && (y == 0.0)) + { + aa.az = 0.0; + aa.alt = 90.0; + //qDebug() << "xy85ToAzAlt" << x << y << aa.az << aa.alt; + return aa; + } + double xr = Units::degreesToRadians(x); + double yr = Units::degreesToRadians(y); + double elr = asin(cos(yr) * cos(xr)); + double azr; + if (x == 0.0) + { + // 1/sin(x) == Inf + azr = y >= 0.0 ? M_PI/2.0 : 2.0*M_PI*3.0/4.0; + } + else if (y == 90.0) + { + // tan(90) == Inf + azr = M_PI/2.0; + } + else if (y == -90.0) + { + // tan(-90) == Inf + azr = 2.0*M_PI*3.0/4.0; + } + else + { + // atan2(y,x) = atan(y/x) + azr = atan2(-tan(yr), sin(xr)) + M_PI; + } + aa.az = Units::radiansToDegrees(azr); + aa.alt = Units::radiansToDegrees(elr); + //qDebug() << "xy85ToAzAlt" << x << y << aa.az << aa.alt; + return aa; +} + +AzAlt Astronomy::xy30ToAzAlt(double x, double y) +{ + AzAlt aa; + if ((x == 0.0) && (y == 0.0)) + { + aa.az = 0.0; + aa.alt = 90.0; + //qDebug() << "xy30ToAzAlt" << x << y << aa.az << aa.alt; + return aa; + } + double xr = Units::degreesToRadians(x); + double yr = Units::degreesToRadians(y); + double elr = asin(cos(yr) * cos(xr)); + double azr; + if (y == 0.0) + { + // cot(0) == Inf + azr = x >= 0.0 ? M_PI/2.0 : 2.0*M_PI*3.0/4.0; + } + else if (y == 90.0) + { + // tan(90) == Inf + azr = 0.0; + } + else if (y == -90.0) + { + // tan(-90) == Inf + azr = M_PI; + } + else + { + azr = atan2(sin(xr), tan(yr)); + if (azr < 0.0) { + azr += 2.0*M_PI; + } + } + aa.az = Units::radiansToDegrees(azr); + aa.alt = Units::radiansToDegrees(elr); + //qDebug() << "xy30ToAzAlt" << x << y << aa.az << aa.alt; + return aa; +} + +// Needs to work for negative a +double Astronomy::modulo(double a, double b) +{ + return a - b * floor(a/b); +} + +// Calculate azimuth and altitude angles to the sun from the given latitude and longitude at the given time +// Refer to: +// https://en.wikipedia.org/wiki/Position_of_the_Sun +// https://www.aa.quae.nl/en/reken/zonpositie.html +// Said to be accurate to .01 degree (36") for dates between 1950 and 2050 +// although we use slightly more accurate constants and an extra term in the equation +// of centre from the second link +// For an alternative, see: http://www.psa.es/sdg/sunpos.htm +// Most accurate algorithm is supposedly: https://midcdmz.nrel.gov/spa/ +void Astronomy::sunPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt) +{ + double jd = julianDate(dt); + double n = (jd - jd_j2000()); // Days since J2000 epoch (including fraction) + + double l = 280.461 + 0.9856474 * n; // Mean longitude of the Sun, corrected for the aberration of light + double g = 357.5291 + 0.98560028 * n; // Mean anomaly of the Sun - how far around orbit from perihlion, in degrees + + l = modulo(l, 360.0); + g = modulo(g, 360.0); + + double gr = Units::degreesToRadians(g); + + double la = l + 1.9148 * sin(gr) + 0.0200 * sin(2.0*gr) + 0.0003 * sin(3.0*gr); // Ecliptic longitude (Ecliptic latitude b set to 0) + + // Convert la, b=0, which give the position of the Sun in the ecliptic coordinate system, to + // equatorial coordinates + + double e = 23.4393 - 3.563E-7 * n; // Obliquity of the ecliptic - tilt of Earth's axis of rotation + + double er = Units::degreesToRadians(e); + double lr = Units::degreesToRadians(la); + + double a = atan2(cos(er) * sin(lr), cos(lr)); // Right ascension, radians + double d = asin(sin(er) * sin(lr)); // Declination, radians + + rd.ra = modulo(Units::radiansToDegrees(a), 360.0) / (360.0/24.0); // Convert to hours + rd.dec = Units::radiansToDegrees(d); // Convert to degrees + + aa = raDecToAzAlt(rd, latitude, longitude, dt, false); +} + +double Astronomy::moonDays(QDateTime dt) +{ + QDateTime utc = dt.toUTC(); + QDate date = utc.date(); + QTime time = utc.time(); + + int y = date.year(); + int m = date.month(); + int D = date.day(); + int d = 367 * y - 7 * (y + (m+9)/12) / 4 - 3 * ((y + (m-9)/7) / 100 + 1) / 4 + 275*m/9 + D - 730515; + + return d + time.hour()/24.0 + time.minute()/(24.0*60.0) + time.second()/(24.0*60.0*60.0); +} + +// Calculate azimuth and altitude angles to the moon from the given latitude and longitude at the given time +// Refer to: https://stjarnhimlen.se/comp/ppcomp.html +// Accurate to 4 arcminute +void Astronomy::moonPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt) +{ + double d = moonDays(dt); + + double ecl = Units::degreesToRadians(23.4393 - 3.563E-7 * d); // Obliquity of the ecliptic - tilt of Earth's axis of rotation + + // Orbital elements for the Sun + double ws = Units::degreesToRadians(282.9404 + 4.70935E-5 * d); + double Ms = Units::degreesToRadians(356.0470 + 0.9856002585 * d); + + // Orbital elements for the Moon + double Nm = Units::degreesToRadians(125.1228 - 0.0529538083 * d); // longitude of the ascending node + double im = Units::degreesToRadians(5.1454); // inclination to the ecliptic (plane of the Earth's orbit) + double wm = Units::degreesToRadians(318.0634 + 0.1643573223 * d); // argument of perihelion + double am = 60.2666; // (Earth radii) semi-major axis, or mean distance from Sun + double em = 0.054900; // ecm // eccentricity (0=circle, 0-1=ellipse, 1=parabola) + double Mm = Units::degreesToRadians(115.3654 + 13.0649929509 * d); // mean anomaly (0 at perihelion; increases uniformly with time), degrees + + double Em = Mm + em * sin(Mm) * (1.0 + em * cos(Mm)); // Eccentric anomaly in radians + + double xv = am * (cos(Em) - em); + double yv = am * (sqrt(1.0 - em*em) * sin(Em)); + + double vm = atan2(yv, xv); // True anomaly + double rm = sqrt(xv*xv + yv*yv); // Distance + + // Compute position in space (for the Moon, this is geocentric) + double xh = rm * (cos(Nm) * cos(vm+wm) - sin(Nm) * sin(vm+wm) * cos(im)); + double yh = rm * (sin(Nm) * cos(vm+wm) + cos(Nm) * sin(vm+wm) * cos(im)); + double zh = rm * (sin(vm+wm) * sin(im)); + + // Convert to ecliptic longitude and latitude + double lonecl = atan2(yh, xh); + double latecl = atan2(zh, sqrt(xh*xh+yh*yh)); + + // Perturbations of the Moon + + double Ls = Ms + ws; // Mean Longitude of the Sun (Ns=0) + double Lm = Mm + wm + Nm; // Mean longitude of the Moon + double D = Lm - Ls; // Mean elongation of the Moon + double F = Lm - Nm; // Argument of latitude for the Moon + + double dlon; + dlon = -1.274 * sin(Mm - 2*D); // (the Evection) + dlon += +0.658 * sin(2*D); // (the Variation) + dlon += -0.186 * sin(Ms); // (the Yearly Equation) + dlon += -0.059 * sin(2*Mm - 2*D); + dlon += -0.057 * sin(Mm - 2*D + Ms); + dlon += +0.053 * sin(Mm + 2*D); + dlon += +0.046 * sin(2*D - Ms); + dlon += +0.041 * sin(Mm - Ms); + dlon += -0.035 * sin(D); // (the Parallactic Equation) + dlon += -0.031 * sin(Mm + Ms); + dlon += -0.015 * sin(2*F - 2*D); + dlon += +0.011 * sin(Mm - 4*D); + + double dlat; + dlat = -0.173 * sin(F - 2*D); + dlat += -0.055 * sin(Mm - F - 2*D); + dlat += -0.046 * sin(Mm + F - 2*D); + dlat += +0.033 * sin(F + 2*D); + dlat += +0.017 * sin(2*Mm + F); + + lonecl += Units::degreesToRadians(dlon); + latecl += Units::degreesToRadians(dlat); + + rm += -0.58 * cos(Mm - 2*D); + rm += -0.46 * cos(2*D); + + // Convert perturbed + xh = rm * cos(lonecl) * cos(latecl); + yh = rm * sin(lonecl) * cos(latecl); + zh = rm * sin(latecl); + + // Convert to geocentric coordinates (already the case for the Moon) + double xg = xh; + double yg = yh; + double zg = zh; + + // Convert to equatorial coordinates + double xe = xg; + double ye = yg * cos(ecl) - zg * sin(ecl); + double ze = yg * sin(ecl) + zg * cos(ecl); + + // Compute right ascension and declination + double ra = atan2(ye, xe); + double dec = atan2(ze, sqrt(xe*xe+ye*ye)); + + rd.ra = modulo(Units::radiansToDegrees(ra), 360.0) / (360.0/24.0); // Convert to hours + rd.dec = Units::radiansToDegrees(dec); // Convert to degrees + + // Convert from geocentric to topocentric + double mpar = asin(1/rm); + + double lat = Units::degreesToRadians(latitude); + double gclat = Units::degreesToRadians(latitude - 0.1924 * sin(2.0 * lat)); + double rho = 0.99833 + 0.00167 * cos(2*lat); + + QTime time = dt.toUTC().time(); + double UT = time.hour() + time.minute()/60.0 + time.second()/(60.0*60.0); + + double GMST0 = Units::radiansToDegrees(Ls)/15.0 + 12; // In hours + double GMST = GMST0 + UT; + double LST = GMST + longitude/15.0; + + double HA = Units::degreesToRadians(LST*15.0 - Units::radiansToDegrees(ra)); // Hour angle in radians + double g = atan(tan(gclat) / cos(HA)); + + double topRA = ra - mpar * rho * cos(gclat) * sin(HA) / cos(dec); + double topDec; + if (g != 0.0) + topDec = dec - mpar * rho * sin(gclat) * sin(g - dec) / sin(g); + else + topDec = dec - mpar * rho * sin(-dec) * cos(HA); + + rd.ra = modulo(Units::radiansToDegrees(topRA), 360.0) / (360.0/24.0); // Convert to hours + rd.dec = Units::radiansToDegrees(topDec); // Convert to degrees + aa = raDecToAzAlt(rd, latitude, longitude, dt, false); +} + +// Calculated adjustment to altitude angle from true to apparent due to atmospheric refraction using +// Saemundsson's formula (which is used by Stellarium and is primarily just for +// optical wavelengths) +// See: https://en.wikipedia.org/wiki/Atmospheric_refraction#Calculating_refraction +// Alt is in degrees. 90 = Zenith gives a factor of 0. +// Pressure in millibars +// Temperature in Celsius +// We divide by 60.0 to get a value in degrees (as original formula is in arcminutes) +double Astronomy::refractionSaemundsson(double alt, double pressure, double temperature) +{ + double pt = (pressure/1010.0) * (283.0/(273.0+temperature)); + + return pt * (1.02 / tan(Units::degreesToRadians(alt+10.3/(alt+5.11))) + 0.0019279) / 60.0; +} + +// Calculated adjustment to altitude angle from true to apparent due to atmospheric refraction using +// code from Starlink Positional Astronomy Library. This is more accurate for +// radio than Saemundsson's formula, but also more complex. +// See: https://github.com/Starlink/pal +// Alt is in degrees. 90 = Zenith gives a factor of 0. +// Pressure in millibars +// Temperature in Celsius +// Humdity in % +// Frequency in Hertz +// Latitude in decimal degrees +// HeightAboveSeaLevel in metres +// Temperature lapse rate in K/km +double Astronomy::refractionPAL(double alt, double pressure, double temperature, double humidity, double frequency, + double latitude, double heightAboveSeaLevel, double temperatureLapseRate) +{ + double tdk = Units::celsiusToKelvin(temperature); // Ambient temperature at the observer (K) + double wl = (299792458.0/frequency)*1000000.0; // Wavelength in micrometres + double rh = humidity/100.0; // Relative humidity in range 0-1 + double phi = Units::degreesToRadians(latitude); // Latitude of the observer (radian, astronomical) + double tlr = temperatureLapseRate/1000.0; // Temperature lapse rate in the troposphere (K/metre) + double refa, refb; + double z = 90.0-alt; + double zu = Units::degreesToRadians(z); + double zr; + + palRefco(heightAboveSeaLevel, tdk, pressure, rh, + wl, phi, tlr, 1E-10, + &refa, &refb); + palRefz(zu, refa, refb, &zr); + + return z-Units::radiansToDegrees(zr); +} + +double Astronomy::lstAndRAToLongitude(double lst, double raHours) +{ + double longitude = lst - (raHours * 15.0); // Convert hours to degrees + if (longitude < -180.0) + longitude += 360.0; + else if (longitude > 180.0) + longitude -= 360.0; + return -longitude; // East positive +} + +// Return right ascension and declination of North Galactic Pole in J2000 Epoch +// http://www.lsc-group.phys.uwm.edu/lal/lsd/node1777.html +void Astronomy::northGalacticPoleJ2000(double& ra, double& dec) +{ + ra = 192.8594813/15.0; + dec = 27.1282511; +} + +// Convert from equatorial to Galactic coordinates, J2000 Epoch +void Astronomy::equatorialToGalactic(double ra, double dec, double& l, double& b) +{ + const double raRad = Units::degreesToRadians(ra * 15.0); + const double decRad = Units::degreesToRadians(dec); + + // Calculate RA and dec for North Galactic pole, J2000 + double ngpRa, ngpDec; + northGalacticPoleJ2000(ngpRa, ngpDec); + const double ngpRaRad = Units::degreesToRadians(ngpRa * 15.0); + const double ngpDecRad = Units::degreesToRadians(ngpDec); + + // Calculate galactic longitude in radians + double bRad = asin(sin(ngpDecRad)*sin(decRad)+cos(ngpDecRad)*cos(decRad)*cos(raRad - ngpRaRad)); + + // Calculate galactic latitiude in radians + double lRad = atan2(sin(decRad)-sin(bRad)*sin(ngpDecRad), cos(decRad)*cos(ngpDecRad)*sin(raRad - ngpRaRad)); + + // Ascending node of the galactic plane in degrees + double lAscend = 33.0; + + // Convert to degrees in range -90,90 and 0,360 + b = Units::radiansToDegrees(bRad); + l = Units::radiansToDegrees(lRad) + lAscend; + if (l < 0.0) { + l += 360.0; + } + if (l > 360.0) { + l -= 360.0; + } +} + +// Convert from Galactic to equatorial coordinates, J2000 Epoch +void Astronomy::galacticToEquatorial(double l, double b, double& ra, double& dec) +{ + const double lRad = Units::degreesToRadians(l); + const double bRad = Units::degreesToRadians(b); + + // Calculate RA and dec for North Galactic pole, J2000 + double ngpRa, ngpDec; + northGalacticPoleJ2000(ngpRa, ngpDec); + const double ngpRaRad = Units::degreesToRadians(ngpRa * 15.0); + const double ngpDecRad = Units::degreesToRadians(ngpDec); + + // Galactic longitude of North Celestial Pole + const double ncpLRad = Units::degreesToRadians(122.93129); + + // Calculate declination in radians + double decRad = asin(sin(bRad)*sin(ngpDecRad)+cos(bRad)*cos(ngpDecRad)*cos(lRad - ncpLRad)); + + // Calculate right ascension in radians + double y = sin(lRad-ncpLRad); + double x = cos(lRad-ncpLRad)*sin(ngpDecRad)-tan(bRad)*cos(ngpDecRad); + double raRad = atan2(y, x) + (ngpRaRad - M_PI); + + // Convert to degrees + dec = Units::radiansToDegrees(decRad); + ra = Units::radiansToDegrees(raRad) / 15.0; + if (ra < 0.0) { + ra += 24.0; + } else if (ra >= 24.0) { + ra -= 24.0; + } +} + +// Calculate velocity in m/s given a Doppler shift from frequency f0 to frequency f in Hz +// Non-relativistic "radio definition" +// + velocity is approaching +double Astronomy::dopplerToVelocity(double f, double f0) +{ + return m_speedOfLight * f / f0 - m_speedOfLight; +} + +// Calculate Doppler shift in Hz given a velocity in m/s +double Astronomy::velocityToDoppler(double v, double f0) +{ + return f0 * (v + m_speedOfLight) / m_speedOfLight; +} + +// Get velocity (km/s) in a given direction due to the Earth's rotation +// Adapted from palRverot +double Astronomy::earthRotationVelocity(RADec rd, double latitude, double longitude, QDateTime dt) +{ + const double earthSpeed = 0.4655; // km/s (Earth's circumference / seconds in one sidereal day) + double latRad = Units::degreesToRadians(latitude); + double raRad = Units::degreesToRadians(rd.ra * (360.0/24.0)); + double decRad = Units::degreesToRadians(rd.dec); + double st = Astronomy::localSiderealTime(dt, longitude); + double a = Units::degreesToRadians(st) - raRad; + return -earthSpeed * cos(latRad) * sin(a) * cos(decRad); +} + +// Get velocity (km/s) in a given direction due to the Earth's orbit around the Sun in barcycentric reference frame +double Astronomy::earthOrbitVelocityBCRS(RADec rd, QDateTime dt) +{ + double ph[3]; + double vh[3]; + double pb[3]; + double vb[3]; + + double date = Astronomy::modifiedJulianDate(dt); + + // Get Earth position and velocity + palEpv(date, ph, vh, pb, vb); + + // Convert from AU/day to km/s + double vel[3]; + for (int i = 0; i < 3; i++) { + vel[i] = vb[i] * 1.731e3; + } + + // Equatorial to cartesian vector + double raRad = Units::degreesToRadians(rd.ra * (360.0/24.0)); + double decRad = Units::degreesToRadians(rd.dec); + double cp = cos(decRad); + double vd[3]; + vd[0] = cos(raRad) * cp; + vd[1] = sin(raRad) * cp; + vd[2] = sin(decRad); + + // Calculate component of velocity along target direction via dot product + return vel[0] * vd[0] + vel[1] * vd[1] + vel[2] * vd[2]; +} + +// Get velocity (km/s) in a given direction due to the Sun's motion w.r.t. Local Standard of Rest +// Adapted from palRvlsrk +// See: https://advlabwiki.johnshopkins.edu/images/Vlsr.pdf +double Astronomy::sunVelocityLSRK(RADec rd) +{ + // Solar motion - 20 km/s towards RA 18h Dec +30d (1900) + // expressed as J2000.0 equatorial cartesian vector + // Some other s/w uses 18h03m50.29s, +30:00:16.8 (J2000) -> {0.01934,-17.31968,10.001411} + const double va[3] = {0.29000, -17.31726, 10.00141}; + double vb[3]; + + // Equatorial to cartesian vector + double raRad = Units::degreesToRadians(rd.ra * (360.0/24.0)); + double decRad = Units::degreesToRadians(rd.dec); + double cp = cos(decRad); + vb[0] = cos(raRad) * cp; + vb[1] = sin(raRad) * cp; + vb[2] = sin(decRad); + + // Calculate component of velocity along target direction via dot product + return va[0] * vb[0] + va[1] * vb[1] + va[2] * vb[2]; +} + +// Get velocity (km/s) of Earth based observer in a given direction due to the Sun's and Earth's motion w.r.t. Local Standard of Rest +double Astronomy::observerVelocityLSRK(RADec rd, double latitude, double longitude, QDateTime dt) +{ + double vRot = Astronomy::earthRotationVelocity(rd, latitude, longitude, dt); + double vOrbit = Astronomy::earthOrbitVelocityBCRS(rd, dt); + double vSun = Astronomy::sunVelocityLSRK(rd); + return vRot + vOrbit + vSun; +} + +// Calculate sunrise and sunset time +// From: https://en.wikipedia.org/wiki/Sunrise_equation +// Probably accurate to within a couple of minutes +void Astronomy::sunrise(QDate date, double latitude, double longitude, QDateTime& rise, QDateTime& set) +{ + // Calculate Julian day + double n = std::ceil(Astronomy::julianDate(QDateTime(date, QTime(0, 0, 0))) - 2451545.0 + (69.184 / 86400.0)); + + // Mean solar time + double jStar = n - longitude / 360.0; + + // Solar mean anomaly + double m = Astronomy::modulo(357.5291 + 0.98560028 * jStar, 360.0); + double mRad = Units::degreesToRadians(m); + + // Equation of the center + double c = 1.9148 * sin(mRad) + 0.02 * sin(2.0 * mRad) + 0.0003 * sin(3 * mRad); + + // Ecliptic longitude + double lambda = Astronomy::modulo(m + c + 180.0 + 102.9372, 360.0); + double lambdaRad = Units::degreesToRadians(lambda); + + // Solar transit + double jTransit = 2451545.0 + jStar + 0.0053 * sin(mRad) - 0.0069 * sin(2.0 * lambdaRad); + + // Declination of the Sun + const double tilt = 23.4397; + const double tiltRad = Units::degreesToRadians(tilt); + double sunDecRad = asin(sin(lambdaRad) * sin(tiltRad)); + + // Hour angle + double latitudeRad = Units::degreesToRadians(latitude); + double omega0Rad = acos((sin(Units::degreesToRadians(-0.833)) - sin(latitudeRad) * sin(sunDecRad)) / (cos(latitudeRad) * cos(sunDecRad))); + double omega0 = Units::radiansToDegrees(omega0Rad); + + // Rise and set times + double jRise = jTransit - omega0 / 360.0; + double jSet = jTransit + omega0 / 360.0; + + rise = Astronomy::julianDateToDateTime(jRise); + set = Astronomy::julianDateToDateTime(jSet); +} + +// Calculate thermal noise power for a given temperature in Kelvin and bandwidth in Hz +double Astronomy::noisePowerdBm(double temp, double bw) +{ + return 10.0 * std::log10(m_boltzmann * temp * bw) + 30.0; +} + +// Calculate noise temperature for a given power in dBm and bandwidth in Hz +double Astronomy::noiseTemp(double dBm, double bw) +{ + return std::pow(10.0, ((dBm - 30.0) / 10.0)) / (m_boltzmann * bw); +} + +// The following functions are from Starlink Positional Astronomy Library +// https://github.com/Starlink/pal + +/* Pi */ +static const double PAL__DPI = 3.1415926535897932384626433832795028841971693993751; + +/* 2Pi */ +static const double PAL__D2PI = 6.2831853071795864769252867665590057683943387987502; + +/* pi/180: degrees to radians */ +static const double PAL__DD2R = 0.017453292519943295769236907684886127134428718885417; + +/* Radians to degrees */ +static const double PAL__DR2D = 57.295779513082320876798154814105170332405472466564; + +/* Start of SLA modified Julian date epoch */ +static const double PAL__MJD0 = 2400000.5; + +/* DMAX(A,B) - return maximum value - evaluates arguments multiple times */ +#define DMAX(A,B) ((A) > (B) ? (A) : (B) ) + +/* DMIN(A,B) - return minimum value - evaluates arguments multiple times */ +#define DMIN(A,B) ((A) < (B) ? (A) : (B) ) + +// Normalize angle into range +/- pi +double palDrange( double angle ) { + double result = fmod( angle, PAL__D2PI ); + if( result > PAL__DPI ) { + result -= PAL__D2PI; + } else if( result < -PAL__DPI ) { + result += PAL__D2PI; + } + return result; +} + +// Calculate stratosphere parameters +static void pal1Atms ( double rt, double tt, double dnt, double gamal, + double r, double * dn, double * rdndr ) { + + double b; + double w; + + b = gamal / tt; + w = (dnt - 1.0) * exp( -b * (r-rt) ); + *dn = 1.0 + w; + *rdndr = -r * b * w; + +} + +// Calculate troposphere parameters +static void pal1Atmt ( double r0, double t0, double alpha, double gamm2, + double delm2, double c1, double c2, double c3, double c4, + double c5, double c6, double r, + double *t, double *dn, double *rdndr ) { + + double tt0; + double tt0gm2; + double tt0dm2; + + *t = DMAX( DMIN( t0 - alpha*(r-r0), 320.0), 100.0 ); + tt0 = *t / t0; + tt0gm2 = pow( tt0, gamm2 ); + tt0dm2 = pow( tt0, delm2 ); + *dn = 1.0 + ( c1 * tt0gm2 - ( c2 - c5 / *t ) * tt0dm2 ) * tt0; + *rdndr = r * ( -c3 * tt0gm2 + ( c4 - c6 / tt0 ) * tt0dm2 ); + +} + +// Adjust unrefracted zenith distance +static void palRefz ( double zu, double refa, double refb, double *zr ) { + + /* Constants */ + + /* Largest usable ZD (deg) */ + const double D93 = 93.0; + + /* ZD at which one model hands over to the other (radians) */ + const double Z83 = 83.0 * PAL__DD2R; + + /* coefficients for high ZD model (used beyond ZD 83 deg) */ + const double C1 = +0.55445; + const double C2 = -0.01133; + const double C3 = +0.00202; + const double C4 = +0.28385; + const double C5 = +0.02390; + + /* High-ZD-model prefiction (deg) for that point */ + const double REF83 = (C1+C2*7.0+C3*49.0)/(1.0+C4*7.0+C5*49.0); + + double zu1,zl,s,c,t,tsq,tcu,ref,e,e2; + + /* perform calculations for zu or 83 deg, whichever is smaller */ + zu1 = DMIN(zu,Z83); + + /* functions of ZD */ + zl = zu1; + s = sin(zl); + c = cos(zl); + t = s/c; + tsq = t*t; + tcu = t*tsq; + + /* refracted zd (mathematically to better than 1 mas at 70 deg) */ + zl = zl-(refa*t+refb*tcu)/(1.0+(refa+3.0*refb*tsq)/(c*c)); + + /* further iteration */ + s = sin(zl); + c = cos(zl); + t = s/c; + tsq = t*t; + tcu = t*tsq; + ref = zu1-zl+ + (zl-zu1+refa*t+refb*tcu)/(1.0+(refa+3.0*refb*tsq)/(c*c)); + + /* special handling for large zu */ + if (zu > zu1) { + e = 90.0-DMIN(D93,zu*PAL__DR2D); + e2 = e*e; + ref = (ref/REF83)*(C1+C2*e+C3*e2)/(1.0+C4*e+C5*e2); + } + + /* return refracted zd */ + *zr = zu-ref; + +} + +// Determine constants in atmospheric refraction model +static void palRefco ( double hm, double tdk, double pmb, double rh, + double wl, double phi, double tlr, double eps, + double *refa, double *refb ) { + + double r1, r2; + + /* Sample zenith distances: arctan(1) and arctan(4) */ + const double ATN1 = 0.7853981633974483; + const double ATN4 = 1.325817663668033; + + /* Determine refraction for the two sample zenith distances */ + palRefro(ATN1,hm,tdk,pmb,rh,wl,phi,tlr,eps,&r1); + palRefro(ATN4,hm,tdk,pmb,rh,wl,phi,tlr,eps,&r2); + + /* Solve for refraction constants */ + *refa = (64.0*r1-r2)/60.0; + *refb = (r2-4.0*r1)/60.0; + +} + +// Atmospheric refraction for radio and optical/IR wavelengths +static void palRefro( double zobs, double hm, double tdk, double pmb, + double rh, double wl, double phi, double tlr, + double eps, double * ref ) { + + /* + * Fixed parameters + */ + + /* 93 degrees in radians */ + const double D93 = 1.623156204; + /* Universal gas constant */ + const double GCR = 8314.32; + /* Molecular weight of dry air */ + const double DMD = 28.9644; + /* Molecular weight of water vapour */ + const double DMW = 18.0152; + /* Mean Earth radius (metre) */ + const double S = 6378120.; + /* Exponent of temperature dependence of water vapour pressure */ + const double DELTA = 18.36; + /* Height of tropopause (metre) */ + const double HT = 11000.; + /* Upper limit for refractive effects (metre) */ + const double HS = 80000.; + /* Numerical integration: maximum number of strips. */ + const int ISMAX=16384l; + + /* Local variables */ + int is, k, n, i, j; + + int optic, loop; /* booleans */ + + double zobs1,zobs2,hmok,tdkok,pmbok,rhok,wlok,alpha, + tol,wlsq,gb,a,gamal,gamma,gamm2,delm2, + tdc,psat,pwo,w, + c1,c2,c3,c4,c5,c6,r0,tempo,dn0,rdndr0,sk0,f0, + rt,tt,dnt,rdndrt,sine,zt,ft,dnts,rdndrp,zts,fts, + rs,dns,rdndrs,zs,fs,refold,z0,zrange,fb,ff,fo,fe, + h,r,sz,rg,dr,tg,dn,rdndr,t,f,refp,reft; + + /* The refraction integrand */ +#define refi(DN,RDNDR) RDNDR/(DN+RDNDR) + + /* Transform ZOBS into the normal range. */ + zobs1 = palDrange(zobs); + zobs2 = DMIN(fabs(zobs1),D93); + + /* keep other arguments within safe bounds. */ + hmok = DMIN(DMAX(hm,-1e3),HS); + tdkok = DMIN(DMAX(tdk,100.0),500.0); + pmbok = DMIN(DMAX(pmb,0.0),10000.0); + rhok = DMIN(DMAX(rh,0.0),1.0); + wlok = DMAX(wl,0.1); + alpha = DMIN(DMAX(fabs(tlr),0.001),0.01); + + /* tolerance for iteration. */ + tol = DMIN(DMAX(fabs(eps),1e-12),0.1)/2.0; + + /* decide whether optical/ir or radio case - switch at 100 microns. */ + optic = wlok < 100.0; + + /* set up model atmosphere parameters defined at the observer. */ + wlsq = wlok*wlok; + gb = 9.784*(1.0-0.0026*cos(phi+phi)-0.00000028*hmok); + if (optic) { + a = (287.6155+(1.62887+0.01360/wlsq)/wlsq) * 273.15e-6/1013.25; + } else { + a = 77.6890e-6; + } + gamal = (gb*DMD)/GCR; + gamma = gamal/alpha; + gamm2 = gamma-2.0; + delm2 = DELTA-2.0; + tdc = tdkok-273.15; + psat = pow(10.0,(0.7859+0.03477*tdc)/(1.0+0.00412*tdc)) * + (1.0+pmbok*(4.5e-6+6.0e-10*tdc*tdc)); + if (pmbok > 0.0) { + pwo = rhok*psat/(1.0-(1.0-rhok)*psat/pmbok); + } else { + pwo = 0.0; + } + w = pwo*(1.0-DMW/DMD)*gamma/(DELTA-gamma); + c1 = a*(pmbok+w)/tdkok; + if (optic) { + c2 = (a*w+11.2684e-6*pwo)/tdkok; + } else { + c2 = (a*w+6.3938e-6*pwo)/tdkok; + } + c3 = (gamma-1.0)*alpha*c1/tdkok; + c4 = (DELTA-1.0)*alpha*c2/tdkok; + if (optic) { + c5 = 0.0; + c6 = 0.0; + } else { + c5 = 375463e-6*pwo/tdkok; + c6 = c5*delm2*alpha/(tdkok*tdkok); + } + + /* conditions at the observer. */ + r0 = S+hmok; + pal1Atmt(r0,tdkok,alpha,gamm2,delm2,c1,c2,c3,c4,c5,c6, + r0,&tempo,&dn0,&rdndr0); + sk0 = dn0*r0*sin(zobs2); + f0 = refi(dn0,rdndr0); + + /* conditions in the troposphere at the tropopause. */ + rt = S+DMAX(HT,hmok); + pal1Atmt(r0,tdkok,alpha,gamm2,delm2,c1,c2,c3,c4,c5,c6, + rt,&tt,&dnt,&rdndrt); + sine = sk0/(rt*dnt); + zt = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0))); + ft = refi(dnt,rdndrt); + + /* conditions in the stratosphere at the tropopause. */ + pal1Atms(rt,tt,dnt,gamal,rt,&dnts,&rdndrp); + sine = sk0/(rt*dnts); + zts = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0))); + fts = refi(dnts,rdndrp); + + /* conditions at the stratosphere limit. */ + rs = S+HS; + pal1Atms(rt,tt,dnt,gamal,rs,&dns,&rdndrs); + sine = sk0/(rs*dns); + zs = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0))); + fs = refi(dns,rdndrs); + + /* variable initialization to avoid compiler warning. */ + reft = 0.0; + + /* integrate the refraction integral in two parts; first in the + * troposphere (k=1), then in the stratosphere (k=2). */ + + for (k=1; k<=2; k++) { + + /* initialize previous refraction to ensure at least two iterations. */ + refold = 1.0; + + /* start off with 8 strips. */ + is = 8; + + /* start z, z range, and start and end values. */ + if (k==1) { + z0 = zobs2; + zrange = zt-z0; + fb = f0; + ff = ft; + } else { + z0 = zts; + zrange = zs-z0; + fb = fts; + ff = fs; + } + + /* sums of odd and even values. */ + fo = 0.0; + fe = 0.0; + + /* first time through the loop we have to do every point. */ + n = 1; + + /* start of iteration loop (terminates at specified precision). */ + loop = 1; + while (loop) { + + /* strip width. */ + h = zrange/((double)is); + + /* initialize distance from earth centre for quadrature pass. */ + if (k == 1) { + r = r0; + } else { + r = rt; + } + + /* one pass (no need to compute evens after first time). */ + for (i=1; i 1e-20) { + w = sk0/sz; + rg = r; + dr = 1.0e6; + j = 0; + while ( fabs(dr) > 1.0 && j < 4 ) { + j++; + if (k==1) { + pal1Atmt(r0,tdkok,alpha,gamm2,delm2, + c1,c2,c3,c4,c5,c6,rg,&tg,&dn,&rdndr); + } else { + pal1Atms(rt,tt,dnt,gamal,rg,&dn,&rdndr); + } + dr = (rg*dn-w)/(dn+rdndr); + rg = rg-dr; + } + r = rg; + } + + /* find the refractive index and integrand at r. */ + if (k==1) { + pal1Atmt(r0,tdkok,alpha,gamm2,delm2, + c1,c2,c3,c4,c5,c6,r,&t,&dn,&rdndr); + } else { + pal1Atms(rt,tt,dnt,gamal,r,&dn,&rdndr); + } + f = refi(dn,rdndr); + + /* accumulate odd and (first time only) even values. */ + if (n==1 && i%2 == 0) { + fe += f; + } else { + fo += f; + } + } + + /* evaluate the integrand using simpson's rule. */ + refp = h*(fb+4.0*fo+2.0*fe+ff)/3.0; + + /* has the required precision been achieved (or can't be)? */ + if (fabs(refp-refold) > tol && is < ISMAX) { + + /* no: prepare for next iteration.*/ + + /* save current value for convergence test. */ + refold = refp; + + /* double the number of strips. */ + is += is; + + /* sum of all current values = sum of next pass's even values. */ + fe += fo; + + /* prepare for new odd values. */ + fo = 0.0; + + /* skip even values next time. */ + n = 2; + } else { + + /* yes: save troposphere component and terminate the loop. */ + if (k==1) reft = refp; + loop = 0; + } + } + } + + /* result. */ + *ref = reft+refp; + if (zobs1 < 0.0) *ref = -(*ref); + +} + +// Earth position and velocity with respect to the BCRS +static void palEpv( double date, double ph[3], double vh[3], + double pb[3], double vb[3] ) { + + int i; + double pvh[2][3]; + double pvb[2][3]; + + eraEpv00( PAL__MJD0, date, pvh, pvb ); + + /* Copy into output arrays */ + for (i=0; i<3; i++) { + ph[i] = pvh[0][i]; + vh[i] = pvh[1][i]; + pb[i] = pvb[0][i]; + vb[i] = pvb[1][i]; + } + +} + +// The following functions are from ERFA (Essential Routines for Fundamental Astronomy) +// https://github.com/liberfa/erfa.git + +/* Days per Julian year */ +#define ERFA_DJY (365.25) + +/* Reference epoch (J2000.0), Julian Date */ +#define ERFA_DJ00 (2451545.0) + +static int eraEpv00(double date1, double date2, + double pvh[2][3], double pvb[2][3]) +/* +** - - - - - - - - - +** e r a E p v 0 0 +** - - - - - - - - - +** +** Earth position and velocity, heliocentric and barycentric, with +** respect to the Barycentric Celestial Reference System. +** +** Given: +** date1,date2 double TDB date (Note 1) +** +** Returned: +** pvh double[2][3] heliocentric Earth position/velocity +** pvb double[2][3] barycentric Earth position/velocity +** +** Returned (function value): +** int status: 0 = OK +** +1 = warning: date outside +** the range 1900-2100 AD +** +** Notes: +** +** 1) The TDB date date1+date2 is a Julian Date, apportioned in any +** convenient way between the two arguments. For example, +** JD(TDB)=2450123.7 could be expressed in any of these ways, among +** others: +** +** date1 date2 +** +** 2450123.7 0.0 (JD method) +** 2451545.0 -1421.3 (J2000 method) +** 2400000.5 50123.2 (MJD method) +** 2450123.5 0.2 (date & time method) +** +** The JD method is the most natural and convenient to use in cases +** where the loss of several decimal digits of resolution is +** acceptable. The J2000 method is best matched to the way the +** argument is handled internally and will deliver the optimum +** resolution. The MJD method and the date & time methods are both +** good compromises between resolution and convenience. However, +** the accuracy of the result is more likely to be limited by the +** algorithm itself than the way the date has been expressed. +** +** n.b. TT can be used instead of TDB in most applications. +** +** 2) On return, the arrays pvh and pvb contain the following: +** +** pvh[0][0] x } +** pvh[0][1] y } heliocentric position, au +** pvh[0][2] z } +** +** pvh[1][0] xdot } +** pvh[1][1] ydot } heliocentric velocity, au/d +** pvh[1][2] zdot } +** +** pvb[0][0] x } +** pvb[0][1] y } barycentric position, au +** pvb[0][2] z } +** +** pvb[1][0] xdot } +** pvb[1][1] ydot } barycentric velocity, au/d +** pvb[1][2] zdot } +** +** The vectors are with respect to the Barycentric Celestial +** Reference System. The time unit is one day in TDB. +** +** 3) The function is a SIMPLIFIED SOLUTION from the planetary theory +** VSOP2000 (X. Moisson, P. Bretagnon, 2001, Celes. Mechanics & +** Dyn. Astron., 80, 3/4, 205-213) and is an adaptation of original +** Fortran code supplied by P. Bretagnon (private comm., 2000). +** +** 4) Comparisons over the time span 1900-2100 with this simplified +** solution and the JPL DE405 ephemeris give the following results: +** +** RMS max +** Heliocentric: +** position error 3.7 11.2 km +** velocity error 1.4 5.0 mm/s +** +** Barycentric: +** position error 4.6 13.4 km +** velocity error 1.4 4.9 mm/s +** +** Comparisons with the JPL DE406 ephemeris show that by 1800 and +** 2200 the position errors are approximately double their 1900-2100 +** size. By 1500 and 2500 the deterioration is a factor of 10 and +** by 1000 and 3000 a factor of 60. The velocity accuracy falls off +** at about half that rate. +** +** 5) It is permissible to use the same array for pvh and pvb, which +** will receive the barycentric values. +** +** This revision: 2021 May 11 +** +** Copyright (C) 2013-2021, NumFOCUS Foundation. +** Derived, with permission, from the SOFA library. See notes at end of file. +*/ +{ +/* +** Matrix elements for orienting the analytical model to DE405. +** +** The corresponding Euler angles are: +** +** d ' " +** 1st rotation - 23 26 21.4091 about the x-axis (obliquity) +** 2nd rotation + 0.0475 about the z-axis (RA offset) +** +** These were obtained empirically, by comparisons with DE405 over +** 1900-2100. +*/ + static const double am12 = 0.000000211284, + am13 = -0.000000091603, + am21 = -0.000000230286, + am22 = 0.917482137087, + am23 = -0.397776982902, + am32 = 0.397776982902, + am33 = 0.917482137087; + +/* +** ---------------------- +** Ephemeris Coefficients +** ---------------------- +** +** The ephemeris consists of harmonic terms for predicting (i) the Sun +** to Earth vector and (ii) the Solar-System-barycenter to Sun vector +** respectively. The coefficients are stored in arrays which, although +** 1-demensional, contain groups of three. Each triplet of +** coefficients is the amplitude, phase and frequency for one term in +** the model, and each array contains the number of terms called for by +** the model. +** +** There are eighteen such arrays, named as follows: +** +** array model power of T component +** +** e0x Sun-to-Earth 0 x +** e0y Sun-to-Earth 0 y +** e0z Sun-to-Earth 0 z +** +** e1x Sun-to-Earth 1 x +** e1y Sun-to-Earth 1 y +** e1z Sun-to-Earth 1 z +** +** e2x Sun-to-Earth 2 x +** e2y Sun-to-Earth 2 y +** e2z Sun-to-Earth 2 z +** +** s0x SSB-to-Sun 0 x +** s0y SSB-to-Sun 0 y +** s0z SSB-to-Sun 0 z +** +** s1x SSB-to-Sun 1 x +** s1y SSB-to-Sun 1 y +** s1z SSB-to-Sun 1 z +** +** s2x SSB-to-Sun 2 x +** s2y SSB-to-Sun 2 y +** s2z SSB-to-Sun 2 z +*/ + +/* Sun-to-Earth, T^0, X */ + static const double e0x[] = { + 0.9998292878132e+00, 0.1753485171504e+01, 0.6283075850446e+01, + 0.8352579567414e-02, 0.1710344404582e+01, 0.1256615170089e+02, + 0.5611445335148e-02, 0.0000000000000e+00, 0.0000000000000e+00, + 0.1046664295572e-03, 0.1667225416770e+01, 0.1884922755134e+02, + 0.3110842534677e-04, 0.6687513390251e+00, 0.8399684731857e+02, + 0.2552413503550e-04, 0.5830637358413e+00, 0.5296909721118e+00, + 0.2137207845781e-04, 0.1092330954011e+01, 0.1577343543434e+01, + 0.1680240182951e-04, 0.4955366134987e+00, 0.6279552690824e+01, + 0.1679012370795e-04, 0.6153014091901e+01, 0.6286599010068e+01, + 0.1445526946777e-04, 0.3472744100492e+01, 0.2352866153506e+01, + + 0.1091038246184e-04, 0.3689845786119e+01, 0.5223693906222e+01, + 0.9344399733932e-05, 0.6073934645672e+01, 0.1203646072878e+02, + 0.8993182910652e-05, 0.3175705249069e+01, 0.1021328554739e+02, + 0.5665546034116e-05, 0.2152484672246e+01, 0.1059381944224e+01, + 0.6844146703035e-05, 0.1306964099750e+01, 0.5753384878334e+01, + 0.7346610905565e-05, 0.4354980070466e+01, 0.3981490189893e+00, + 0.6815396474414e-05, 0.2218229211267e+01, 0.4705732307012e+01, + 0.6112787253053e-05, 0.5384788425458e+01, 0.6812766822558e+01, + 0.4518120711239e-05, 0.6087604012291e+01, 0.5884926831456e+01, + 0.4521963430706e-05, 0.1279424524906e+01, 0.6256777527156e+01, + + 0.4497426764085e-05, 0.5369129144266e+01, 0.6309374173736e+01, + 0.4062190566959e-05, 0.5436473303367e+00, 0.6681224869435e+01, + 0.5412193480192e-05, 0.7867838528395e+00, 0.7755226100720e+00, + 0.5469839049386e-05, 0.1461440311134e+01, 0.1414349524433e+02, + 0.5205264083477e-05, 0.4432944696116e+01, 0.7860419393880e+01, + 0.2149759935455e-05, 0.4502237496846e+01, 0.1150676975667e+02, + 0.2279109618501e-05, 0.1239441308815e+01, 0.7058598460518e+01, + 0.2259282939683e-05, 0.3272430985331e+01, 0.4694002934110e+01, + 0.2558950271319e-05, 0.2265471086404e+01, 0.1216800268190e+02, + 0.2561581447555e-05, 0.1454740653245e+01, 0.7099330490126e+00, + + 0.1781441115440e-05, 0.2962068630206e+01, 0.7962980379786e+00, + 0.1612005874644e-05, 0.1473255041006e+01, 0.5486777812467e+01, + 0.1818630667105e-05, 0.3743903293447e+00, 0.6283008715021e+01, + 0.1818601377529e-05, 0.6274174354554e+01, 0.6283142985870e+01, + 0.1554475925257e-05, 0.1624110906816e+01, 0.2513230340178e+02, + 0.2090948029241e-05, 0.5852052276256e+01, 0.1179062909082e+02, + 0.2000176345460e-05, 0.4072093298513e+01, 0.1778984560711e+02, + 0.1289535917759e-05, 0.5217019331069e+01, 0.7079373888424e+01, + 0.1281135307881e-05, 0.4802054538934e+01, 0.3738761453707e+01, + 0.1518229005692e-05, 0.8691914742502e+00, 0.2132990797783e+00, + + 0.9450128579027e-06, 0.4601859529950e+01, 0.1097707878456e+02, + 0.7781119494996e-06, 0.1844352816694e+01, 0.8827390247185e+01, + 0.7733407759912e-06, 0.3582790154750e+01, 0.5507553240374e+01, + 0.7350644318120e-06, 0.2695277788230e+01, 0.1589072916335e+01, + 0.6535928827023e-06, 0.3651327986142e+01, 0.1176985366291e+02, + 0.6324624183656e-06, 0.2241302375862e+01, 0.6262300422539e+01, + 0.6298565300557e-06, 0.4407122406081e+01, 0.6303851278352e+01, + 0.8587037089179e-06, 0.3024307223119e+01, 0.1672837615881e+03, + 0.8299954491035e-06, 0.6192539428237e+01, 0.3340612434717e+01, + 0.6311263503401e-06, 0.2014758795416e+01, 0.7113454667900e-02, + + 0.6005646745452e-06, 0.3399500503397e+01, 0.4136910472696e+01, + 0.7917715109929e-06, 0.2493386877837e+01, 0.6069776770667e+01, + 0.7556958099685e-06, 0.4159491740143e+01, 0.6496374930224e+01, + 0.6773228244949e-06, 0.4034162934230e+01, 0.9437762937313e+01, + 0.5370708577847e-06, 0.1562219163734e+01, 0.1194447056968e+01, + 0.5710804266203e-06, 0.2662730803386e+01, 0.6282095334605e+01, + 0.5709824583726e-06, 0.3985828430833e+01, 0.6284056366286e+01, + 0.5143950896447e-06, 0.1308144688689e+01, 0.6290189305114e+01, + 0.5088010604546e-06, 0.5352817214804e+01, 0.6275962395778e+01, + 0.4960369085172e-06, 0.2644267922349e+01, 0.6127655567643e+01, + + 0.4803137891183e-06, 0.4008844192080e+01, 0.6438496133249e+01, + 0.5731747768225e-06, 0.3794550174597e+01, 0.3154687086868e+01, + 0.4735947960579e-06, 0.6107118308982e+01, 0.3128388763578e+01, + 0.4808348796625e-06, 0.4771458618163e+01, 0.8018209333619e+00, + 0.4115073743137e-06, 0.3327111335159e+01, 0.8429241228195e+01, + 0.5230575889287e-06, 0.5305708551694e+01, 0.1336797263425e+02, + 0.5133977889215e-06, 0.5784230738814e+01, 0.1235285262111e+02, + 0.5065815825327e-06, 0.2052064793679e+01, 0.1185621865188e+02, + 0.4339831593868e-06, 0.3644994195830e+01, 0.1726015463500e+02, + 0.3952928638953e-06, 0.4930376436758e+01, 0.5481254917084e+01, + + 0.4898498111942e-06, 0.4542084219731e+00, 0.9225539266174e+01, + 0.4757490209328e-06, 0.3161126388878e+01, 0.5856477690889e+01, + 0.4727701669749e-06, 0.6214993845446e+00, 0.2544314396739e+01, + 0.3800966681863e-06, 0.3040132339297e+01, 0.4265981595566e+00, + 0.3257301077939e-06, 0.8064977360087e+00, 0.3930209696940e+01, + 0.3255810528674e-06, 0.1974147981034e+01, 0.2146165377750e+01, + 0.3252029748187e-06, 0.2845924913135e+01, 0.4164311961999e+01, + 0.3255505635308e-06, 0.3017900824120e+01, 0.5088628793478e+01, + 0.2801345211990e-06, 0.6109717793179e+01, 0.1256967486051e+02, + 0.3688987740970e-06, 0.2911550235289e+01, 0.1807370494127e+02, + + 0.2475153429458e-06, 0.2179146025856e+01, 0.2629832328990e-01, + 0.3033457749150e-06, 0.1994161050744e+01, 0.4535059491685e+01, + 0.2186743763110e-06, 0.5125687237936e+01, 0.1137170464392e+02, + 0.2764777032774e-06, 0.4822646860252e+00, 0.1256262854127e+02, + 0.2199028768592e-06, 0.4637633293831e+01, 0.1255903824622e+02, + 0.2046482824760e-06, 0.1467038733093e+01, 0.7084896783808e+01, + 0.2611209147507e-06, 0.3044718783485e+00, 0.7143069561767e+02, + 0.2286079656818e-06, 0.4764220356805e+01, 0.8031092209206e+01, + 0.1855071202587e-06, 0.3383637774428e+01, 0.1748016358760e+01, + 0.2324669506784e-06, 0.6189088449251e+01, 0.1831953657923e+02, + + 0.1709528015688e-06, 0.5874966729774e+00, 0.4933208510675e+01, + 0.2168156875828e-06, 0.4302994009132e+01, 0.1044738781244e+02, + 0.2106675556535e-06, 0.3800475419891e+01, 0.7477522907414e+01, + 0.1430213830465e-06, 0.1294660846502e+01, 0.2942463415728e+01, + 0.1388396901944e-06, 0.4594797202114e+01, 0.8635942003952e+01, + 0.1922258844190e-06, 0.4943044543591e+00, 0.1729818233119e+02, + 0.1888460058292e-06, 0.2426943912028e+01, 0.1561374759853e+03, + 0.1789449386107e-06, 0.1582973303499e+00, 0.1592596075957e+01, + 0.1360803685374e-06, 0.5197240440504e+01, 0.1309584267300e+02, + 0.1504038014709e-06, 0.3120360916217e+01, 0.1649636139783e+02, + + 0.1382769533389e-06, 0.6164702888205e+01, 0.7632943190217e+01, + 0.1438059769079e-06, 0.1437423770979e+01, 0.2042657109477e+02, + 0.1326303260037e-06, 0.3609688799679e+01, 0.1213955354133e+02, + 0.1159244950540e-06, 0.5463018167225e+01, 0.5331357529664e+01, + 0.1433118149136e-06, 0.6028909912097e+01, 0.7342457794669e+01, + 0.1234623148594e-06, 0.3109645574997e+01, 0.6279485555400e+01, + 0.1233949875344e-06, 0.3539359332866e+01, 0.6286666145492e+01, + 0.9927196061299e-07, 0.1259321569772e+01, 0.7234794171227e+01, + 0.1242302191316e-06, 0.1065949392609e+01, 0.1511046609763e+02, + 0.1098402195201e-06, 0.2192508743837e+01, 0.1098880815746e+02, + + 0.1158191395315e-06, 0.4054411278650e+01, 0.5729506548653e+01, + 0.9048475596241e-07, 0.5429764748518e+01, 0.9623688285163e+01, + 0.8889853269023e-07, 0.5046586206575e+01, 0.6148010737701e+01, + 0.1048694242164e-06, 0.2628858030806e+01, 0.6836645152238e+01, + 0.1112308378646e-06, 0.4177292719907e+01, 0.1572083878776e+02, + 0.8631729709901e-07, 0.1601345232557e+01, 0.6418140963190e+01, + 0.8527816951664e-07, 0.2463888997513e+01, 0.1471231707864e+02, + 0.7892139456991e-07, 0.3154022088718e+01, 0.2118763888447e+01, + 0.1051782905236e-06, 0.4795035816088e+01, 0.1349867339771e+01, + 0.1048219943164e-06, 0.2952983395230e+01, 0.5999216516294e+01, + + 0.7435760775143e-07, 0.5420547991464e+01, 0.6040347114260e+01, + 0.9869574106949e-07, 0.3695646753667e+01, 0.6566935184597e+01, + 0.9156886364226e-07, 0.3922675306609e+01, 0.5643178611111e+01, + 0.7006834356188e-07, 0.1233968624861e+01, 0.6525804586632e+01, + 0.9806170182601e-07, 0.1919542280684e+01, 0.2122839202813e+02, + 0.9052289673607e-07, 0.4615902724369e+01, 0.4690479774488e+01, + 0.7554200867893e-07, 0.1236863719072e+01, 0.1253985337760e+02, + 0.8215741286498e-07, 0.3286800101559e+00, 0.1097355562493e+02, + 0.7185178575397e-07, 0.5880942158367e+01, 0.6245048154254e+01, + 0.7130726476180e-07, 0.7674871987661e+00, 0.6321103546637e+01, + + 0.6650894461162e-07, 0.6987129150116e+00, 0.5327476111629e+01, + 0.7396888823688e-07, 0.3576824794443e+01, 0.5368044267797e+00, + 0.7420588884775e-07, 0.5033615245369e+01, 0.2354323048545e+02, + 0.6141181642908e-07, 0.9449927045673e+00, 0.1296430071988e+02, + 0.6373557924058e-07, 0.6206342280341e+01, 0.9517183207817e+00, + 0.6359474329261e-07, 0.5036079095757e+01, 0.1990745094947e+01, + 0.5740173582646e-07, 0.6105106371350e+01, 0.9555997388169e+00, + 0.7019864084602e-07, 0.7237747359018e+00, 0.5225775174439e+00, + 0.6398054487042e-07, 0.3976367969666e+01, 0.2407292145756e+02, + 0.7797092650498e-07, 0.4305423910623e+01, 0.2200391463820e+02, + + 0.6466760000900e-07, 0.3500136825200e+01, 0.5230807360890e+01, + 0.7529417043890e-07, 0.3514779246100e+01, 0.1842262939178e+02, + 0.6924571140892e-07, 0.2743457928679e+01, 0.1554202828031e+00, + 0.6220798650222e-07, 0.2242598118209e+01, 0.1845107853235e+02, + 0.5870209391853e-07, 0.2332832707527e+01, 0.6398972393349e+00, + 0.6263953473888e-07, 0.2191105358956e+01, 0.6277552955062e+01, + 0.6257781390012e-07, 0.4457559396698e+01, 0.6288598745829e+01, + 0.5697304945123e-07, 0.3499234761404e+01, 0.1551045220144e+01, + 0.6335438746791e-07, 0.6441691079251e+00, 0.5216580451554e+01, + 0.6377258441152e-07, 0.2252599151092e+01, 0.5650292065779e+01, + + 0.6484841818165e-07, 0.1992812417646e+01, 0.1030928125552e+00, + 0.4735551485250e-07, 0.3744672082942e+01, 0.1431416805965e+02, + 0.4628595996170e-07, 0.1334226211745e+01, 0.5535693017924e+00, + 0.6258152336933e-07, 0.4395836159154e+01, 0.2608790314060e+02, + 0.6196171366594e-07, 0.2587043007997e+01, 0.8467247584405e+02, + 0.6159556952126e-07, 0.4782499769128e+01, 0.2394243902548e+03, + 0.4987741172394e-07, 0.7312257619924e+00, 0.7771377146812e+02, + 0.5459280703142e-07, 0.3001376372532e+01, 0.6179983037890e+01, + 0.4863461189999e-07, 0.3767222128541e+01, 0.9027992316901e+02, + 0.5349912093158e-07, 0.3663594450273e+01, 0.6386168663001e+01, + + 0.5673725607806e-07, 0.4331187919049e+01, 0.6915859635113e+01, + 0.4745485060512e-07, 0.5816195745518e+01, 0.6282970628506e+01, + 0.4745379005326e-07, 0.8323672435672e+00, 0.6283181072386e+01, + 0.4049002796321e-07, 0.3785023976293e+01, 0.6254626709878e+01, + 0.4247084014515e-07, 0.2378220728783e+01, 0.7875671926403e+01, + 0.4026912363055e-07, 0.2864103423269e+01, 0.6311524991013e+01, + 0.4062935011774e-07, 0.2415408595975e+01, 0.3634620989887e+01, + 0.5347771048509e-07, 0.3343479309801e+01, 0.2515860172507e+02, + 0.4829494136505e-07, 0.2821742398262e+01, 0.5760498333002e+01, + 0.4342554404599e-07, 0.5624662458712e+01, 0.7238675589263e+01, + + 0.4021599184361e-07, 0.5557250275009e+00, 0.1101510648075e+02, + 0.4104900474558e-07, 0.3296691780005e+01, 0.6709674010002e+01, + 0.4376532905131e-07, 0.3814443999443e+01, 0.6805653367890e+01, + 0.3314590480650e-07, 0.3560229189250e+01, 0.1259245002418e+02, + 0.3232421839643e-07, 0.5185389180568e+01, 0.1066495398892e+01, + 0.3541176318876e-07, 0.3921381909679e+01, 0.9917696840332e+01, + 0.3689831242681e-07, 0.4190658955386e+01, 0.1192625446156e+02, + 0.3890605376774e-07, 0.5546023371097e+01, 0.7478166569050e-01, + 0.3038559339780e-07, 0.6231032794494e+01, 0.1256621883632e+02, + 0.3137083969782e-07, 0.6207063419190e+01, 0.4292330755499e+01, + + 0.4024004081854e-07, 0.1195257375713e+01, 0.1334167431096e+02, + 0.3300234879283e-07, 0.1804694240998e+01, 0.1057540660594e+02, + 0.3635399155575e-07, 0.5597811343500e+01, 0.6208294184755e+01, + 0.3032668691356e-07, 0.3191059366530e+01, 0.1805292951336e+02, + 0.2809652069058e-07, 0.4094348032570e+01, 0.3523159621801e-02, + 0.3696955383823e-07, 0.5219282738794e+01, 0.5966683958112e+01, + 0.3562894142503e-07, 0.1037247544554e+01, 0.6357857516136e+01, + 0.3510598524148e-07, 0.1430020816116e+01, 0.6599467742779e+01, + 0.3617736142953e-07, 0.3002911403677e+01, 0.6019991944201e+01, + 0.2624524910730e-07, 0.2437046757292e+01, 0.6702560555334e+01, + + 0.2535824204490e-07, 0.1581594689647e+01, 0.3141537925223e+02, + 0.3519787226257e-07, 0.5379863121521e+01, 0.2505706758577e+03, + 0.2578406709982e-07, 0.4904222639329e+01, 0.1673046366289e+02, + 0.3423887981473e-07, 0.3646448997315e+01, 0.6546159756691e+01, + 0.2776083886467e-07, 0.3307829300144e+01, 0.1272157198369e+02, + 0.3379592818379e-07, 0.1747541251125e+01, 0.1494531617769e+02, + 0.3050255426284e-07, 0.1784689432607e-01, 0.4732030630302e+01, + 0.2652378350236e-07, 0.4420055276260e+01, 0.5863591145557e+01, + 0.2374498173768e-07, 0.3629773929208e+01, 0.2388894113936e+01, + 0.2716451255140e-07, 0.3079623706780e+01, 0.1202934727411e+02, + + 0.3038583699229e-07, 0.3312487903507e+00, 0.1256608456547e+02, + 0.2220681228760e-07, 0.5265520401774e+01, 0.1336244973887e+02, + 0.3044156540912e-07, 0.4766664081250e+01, 0.2908881142201e+02, + 0.2731859923561e-07, 0.5069146530691e+01, 0.1391601904066e+02, + 0.2285603018171e-07, 0.5954935112271e+01, 0.6076890225335e+01, + 0.2025006454555e-07, 0.4061789589267e+01, 0.4701116388778e+01, + 0.2012597519804e-07, 0.2485047705241e+01, 0.6262720680387e+01, + 0.2003406962258e-07, 0.4163779209320e+01, 0.6303431020504e+01, + 0.2207863441371e-07, 0.6923839133828e+00, 0.6489261475556e+01, + 0.2481374305624e-07, 0.5944173595676e+01, 0.1204357418345e+02, + + 0.2130923288870e-07, 0.4641013671967e+01, 0.5746271423666e+01, + 0.2446370543391e-07, 0.6125796518757e+01, 0.1495633313810e+00, + 0.1932492759052e-07, 0.2234572324504e+00, 0.1352175143971e+02, + 0.2600122568049e-07, 0.4281012405440e+01, 0.4590910121555e+01, + 0.2431754047488e-07, 0.1429943874870e+00, 0.1162474756779e+01, + 0.1875902869209e-07, 0.9781803816948e+00, 0.6279194432410e+01, + 0.1874381139426e-07, 0.5670368130173e+01, 0.6286957268481e+01, + 0.2156696047173e-07, 0.2008985006833e+01, 0.1813929450232e+02, + 0.1965076182484e-07, 0.2566186202453e+00, 0.4686889479442e+01, + 0.2334816372359e-07, 0.4408121891493e+01, 0.1002183730415e+02, + + 0.1869937408802e-07, 0.5272745038656e+01, 0.2427287361862e+00, + 0.2436236460883e-07, 0.4407720479029e+01, 0.9514313292143e+02, + 0.1761365216611e-07, 0.1943892315074e+00, 0.1351787002167e+02, + 0.2156289480503e-07, 0.1418570924545e+01, 0.6037244212485e+01, + 0.2164748979255e-07, 0.4724603439430e+01, 0.2301353951334e+02, + 0.2222286670853e-07, 0.2400266874598e+01, 0.1266924451345e+02, + 0.2070901414929e-07, 0.5230348028732e+01, 0.6528907488406e+01, + 0.1792745177020e-07, 0.2099190328945e+01, 0.6819880277225e+01, + 0.1841802068445e-07, 0.3467527844848e+00, 0.6514761976723e+02, + 0.1578401631718e-07, 0.7098642356340e+00, 0.2077542790660e-01, + + 0.1561690152531e-07, 0.5943349620372e+01, 0.6272439236156e+01, + 0.1558591045463e-07, 0.7040653478980e+00, 0.6293712464735e+01, + 0.1737356469576e-07, 0.4487064760345e+01, 0.1765478049437e+02, + 0.1434755619991e-07, 0.2993391570995e+01, 0.1102062672231e+00, + 0.1482187806654e-07, 0.2278049198251e+01, 0.1052268489556e+01, + 0.1424812827089e-07, 0.1682114725827e+01, 0.1311972100268e+02, + 0.1380282448623e-07, 0.3262668602579e+01, 0.1017725758696e+02, + 0.1811481244566e-07, 0.3187771221777e+01, 0.1887552587463e+02, + 0.1504446185696e-07, 0.5650162308647e+01, 0.7626583626240e-01, + 0.1740776154137e-07, 0.5487068607507e+01, 0.1965104848470e+02, + + 0.1374339536251e-07, 0.5745688172201e+01, 0.6016468784579e+01, + 0.1761377477704e-07, 0.5748060203659e+01, 0.2593412433514e+02, + 0.1535138225795e-07, 0.6226848505790e+01, 0.9411464614024e+01, + 0.1788140543676e-07, 0.6189318878563e+01, 0.3301902111895e+02, + 0.1375002807996e-07, 0.5371812884394e+01, 0.6327837846670e+00, + 0.1242115758632e-07, 0.1471687569712e+01, 0.3894181736510e+01, + 0.1450977333938e-07, 0.4143836662127e+01, 0.1277945078067e+02, + 0.1297579575023e-07, 0.9003477661957e+00, 0.6549682916313e+01, + 0.1462667934821e-07, 0.5760505536428e+01, 0.1863592847156e+02, + 0.1381774374799e-07, 0.1085471729463e+01, 0.2379164476796e+01, + + 0.1682333169307e-07, 0.5409870870133e+01, 0.1620077269078e+02, + 0.1190812918837e-07, 0.1397205174601e+01, 0.1149965630200e+02, + 0.1221434762106e-07, 0.9001804809095e+00, 0.1257326515556e+02, + 0.1549934644860e-07, 0.4262528275544e+01, 0.1820933031200e+02, + 0.1252138953050e-07, 0.1411642012027e+01, 0.6993008899458e+01, + 0.1237078905387e-07, 0.2844472403615e+01, 0.2435678079171e+02, + 0.1446953389615e-07, 0.5295835522223e+01, 0.3813291813120e-01, + 0.1388446457170e-07, 0.4969428135497e+01, 0.2458316379602e+00, + 0.1019339179228e-07, 0.2491369561806e+01, 0.6112403035119e+01, + 0.1258880815343e-07, 0.4679426248976e+01, 0.5429879531333e+01, + + 0.1297768238261e-07, 0.1074509953328e+01, 0.1249137003520e+02, + 0.9913505718094e-08, 0.4735097918224e+01, 0.6247047890016e+01, + 0.9830453155969e-08, 0.4158649187338e+01, 0.6453748665772e+01, + 0.1192615865309e-07, 0.3438208613699e+01, 0.6290122169689e+01, + 0.9835874798277e-08, 0.1913300781229e+01, 0.6319103810876e+01, + 0.9639087569277e-08, 0.9487683644125e+00, 0.8273820945392e+01, + 0.1175716107001e-07, 0.3228141664287e+01, 0.6276029531202e+01, + 0.1018926508678e-07, 0.2216607854300e+01, 0.1254537627298e+02, + 0.9500087869225e-08, 0.2625116459733e+01, 0.1256517118505e+02, + 0.9664192916575e-08, 0.5860562449214e+01, 0.6259197520765e+01, + + 0.9612858712203e-08, 0.7885682917381e+00, 0.6306954180126e+01, + 0.1117645675413e-07, 0.3932148831189e+01, 0.1779695906178e+02, + 0.1158864052160e-07, 0.9995605521691e+00, 0.1778273215245e+02, + 0.9021043467028e-08, 0.5263769742673e+01, 0.6172869583223e+01, + 0.8836134773563e-08, 0.1496843220365e+01, 0.1692165728891e+01, + 0.1045872200691e-07, 0.7009039517214e+00, 0.2204125344462e+00, + 0.1211463487798e-07, 0.4041544938511e+01, 0.8257698122054e+02, + 0.8541990804094e-08, 0.1447586692316e+01, 0.6393282117669e+01, + 0.1038720703636e-07, 0.4594249718112e+00, 0.1550861511662e+02, + 0.1126722351445e-07, 0.3925550579036e+01, 0.2061856251104e+00, + + 0.8697373859631e-08, 0.4411341856037e+01, 0.9491756770005e+00, + 0.8869380028441e-08, 0.2402659724813e+01, 0.3903911373650e+01, + 0.9247014693258e-08, 0.1401579743423e+01, 0.6267823317922e+01, + 0.9205062930950e-08, 0.5245978000814e+01, 0.6298328382969e+01, + 0.8000745038049e-08, 0.3590803356945e+01, 0.2648454860559e+01, + 0.9168973650819e-08, 0.2470150501679e+01, 0.1498544001348e+03, + 0.1075444949238e-07, 0.1328606161230e+01, 0.3694923081589e+02, + 0.7817298525817e-08, 0.6162256225998e+01, 0.4804209201333e+01, + 0.9541469226356e-08, 0.3942568967039e+01, 0.1256713221673e+02, + 0.9821910122027e-08, 0.2360246287233e+00, 0.1140367694411e+02, + + 0.9897822023777e-08, 0.4619805634280e+01, 0.2280573557157e+02, + 0.7737289283765e-08, 0.3784727847451e+01, 0.7834121070590e+01, + 0.9260204034710e-08, 0.2223352487601e+01, 0.2787043132925e+01, + 0.7320252888486e-08, 0.1288694636874e+01, 0.6282655592598e+01, + 0.7319785780946e-08, 0.5359869567774e+01, 0.6283496108294e+01, + 0.7147219933778e-08, 0.5516616675856e+01, 0.1725663147538e+02, + 0.7946502829878e-08, 0.2630459984567e+01, 0.1241073141809e+02, + 0.9001711808932e-08, 0.2849815827227e+01, 0.6281591679874e+01, + 0.8994041507257e-08, 0.3795244450750e+01, 0.6284560021018e+01, + 0.8298582787358e-08, 0.5236413127363e+00, 0.1241658836951e+02, + + 0.8526596520710e-08, 0.4794605424426e+01, 0.1098419223922e+02, + 0.8209822103197e-08, 0.1578752370328e+01, 0.1096996532989e+02, + 0.6357049861094e-08, 0.5708926113761e+01, 0.1596186371003e+01, + 0.7370473179049e-08, 0.3842402530241e+01, 0.4061219149443e+01, + 0.7232154664726e-08, 0.3067548981535e+01, 0.1610006857377e+03, + 0.6328765494903e-08, 0.1313930030069e+01, 0.1193336791622e+02, + 0.8030064908595e-08, 0.3488500408886e+01, 0.8460828644453e+00, + 0.6275464259232e-08, 0.1532061626198e+01, 0.8531963191132e+00, + 0.7051897446325e-08, 0.3285859929993e+01, 0.5849364236221e+01, + 0.6161593705428e-08, 0.1477341999464e+01, 0.5573142801433e+01, + + 0.7754683957278e-08, 0.1586118663096e+01, 0.8662240327241e+01, + 0.5889928990701e-08, 0.1304887868803e+01, 0.1232342296471e+02, + 0.5705756047075e-08, 0.4555333589350e+01, 0.1258692712880e+02, + 0.5964178808332e-08, 0.3001762842062e+01, 0.5333900173445e+01, + 0.6712446027467e-08, 0.4886780007595e+01, 0.1171295538178e+02, + 0.5941809275464e-08, 0.4701509603824e+01, 0.9779108567966e+01, + 0.5466993627395e-08, 0.4588357817278e+01, 0.1884211409667e+02, + 0.6340512090980e-08, 0.1164543038893e+01, 0.5217580628120e+02, + 0.6325505710045e-08, 0.3919171259645e+01, 0.1041998632314e+02, + 0.6164789509685e-08, 0.2143828253542e+01, 0.6151533897323e+01, + + 0.5263330812430e-08, 0.6066564434241e+01, 0.1885275071096e+02, + 0.5597087780221e-08, 0.2926316429472e+01, 0.4337116142245e+00, + 0.5396556236817e-08, 0.3244303591505e+01, 0.6286362197481e+01, + 0.5396615148223e-08, 0.3404304703662e+01, 0.6279789503410e+01, + 0.7091832443341e-08, 0.8532377803192e+00, 0.4907302013889e+01, + 0.6572352589782e-08, 0.4901966774419e+01, 0.1176433076753e+02, + 0.5960236060795e-08, 0.1874672315797e+01, 0.1422690933580e-01, + 0.5125480043511e-08, 0.3735726064334e+01, 0.1245594543367e+02, + 0.5928241866410e-08, 0.4502033899935e+01, 0.6414617803568e+01, + 0.5249600357424e-08, 0.4372334799878e+01, 0.1151388321134e+02, + + 0.6059171276087e-08, 0.2581617302908e+01, 0.6062663316000e+01, + 0.5295235081662e-08, 0.2974811513158e+01, 0.3496032717521e+01, + 0.5820561875933e-08, 0.1796073748244e+00, 0.2838593341516e+00, + 0.4754696606440e-08, 0.1981998136973e+01, 0.3104930017775e+01, + 0.6385053548955e-08, 0.2559174171605e+00, 0.6133512519065e+01, + 0.6589828273941e-08, 0.2750967106776e+01, 0.4087944051283e+02, + 0.5383376567189e-08, 0.6325947523578e+00, 0.2248384854122e+02, + 0.5928941683538e-08, 0.1672304519067e+01, 0.1581959461667e+01, + 0.4816060709794e-08, 0.3512566172575e+01, 0.9388005868221e+01, + 0.6003381586512e-08, 0.5610932219189e+01, 0.5326786718777e+01, + + 0.5504225393105e-08, 0.4037501131256e+01, 0.6503488384892e+01, + 0.5353772620129e-08, 0.6122774968240e+01, 0.1735668374386e+03, + 0.5786253768544e-08, 0.5527984999515e+01, 0.1350651127443e+00, + 0.5065706702002e-08, 0.9980765573624e+00, 0.1248988586463e+02, + 0.5972838885276e-08, 0.6044489493203e+01, 0.2673594526851e+02, + 0.5323585877961e-08, 0.3924265998147e+01, 0.4171425416666e+01, + 0.5210772682858e-08, 0.6220111376901e+01, 0.2460261242967e+02, + 0.4726549040535e-08, 0.3716043206862e+01, 0.7232251527446e+01, + 0.6029425105059e-08, 0.8548704071116e+00, 0.3227113045244e+03, + 0.4481542826513e-08, 0.1426925072829e+01, 0.5547199253223e+01, + + 0.5836024505068e-08, 0.7135651752625e-01, 0.7285056171570e+02, + 0.4137046613272e-08, 0.5330767643283e+01, 0.1087398597200e+02, + 0.5171977473924e-08, 0.4494262335353e+00, 0.1884570439172e+02, + 0.5694429833732e-08, 0.2952369582215e+01, 0.9723862754494e+02, + 0.4009158925298e-08, 0.3500003416535e+01, 0.6244942932314e+01, + 0.4784939596873e-08, 0.6196709413181e+01, 0.2929661536378e+02, + 0.3983725022610e-08, 0.5103690031897e+01, 0.4274518229222e+01, + 0.3870535232462e-08, 0.3187569587401e+01, 0.6321208768577e+01, + 0.5140501213951e-08, 0.1668924357457e+01, 0.1232032006293e+02, + 0.3849034819355e-08, 0.4445722510309e+01, 0.1726726808967e+02, + + 0.4002383075060e-08, 0.5226224152423e+01, 0.7018952447668e+01, + 0.3890719543549e-08, 0.4371166550274e+01, 0.1491901785440e+02, + 0.4887084607881e-08, 0.5973556689693e+01, 0.1478866649112e+01, + 0.3739939287592e-08, 0.2089084714600e+01, 0.6922973089781e+01, + 0.5031925918209e-08, 0.4658371936827e+01, 0.1715706182245e+02, + 0.4387748764954e-08, 0.4825580552819e+01, 0.2331413144044e+03, + 0.4147398098865e-08, 0.3739003524998e+01, 0.1376059875786e+02, + 0.3719089993586e-08, 0.1148941386536e+01, 0.6297302759782e+01, + 0.3934238461056e-08, 0.1559893008343e+01, 0.7872148766781e+01, + 0.3672471375622e-08, 0.5516145383612e+01, 0.6268848941110e+01, + + 0.3768911277583e-08, 0.6116053700563e+01, 0.4157198507331e+01, + 0.4033388417295e-08, 0.5076821746017e+01, 0.1567108171867e+02, + 0.3764194617832e-08, 0.8164676232075e+00, 0.3185192151914e+01, + 0.4840628226284e-08, 0.1360479453671e+01, 0.1252801878276e+02, + 0.4949443923785e-08, 0.2725622229926e+01, 0.1617106187867e+03, + 0.4117393089971e-08, 0.6054459628492e+00, 0.5642198095270e+01, + 0.3925754020428e-08, 0.8570462135210e+00, 0.2139354194808e+02, + 0.3630551757923e-08, 0.3552067338279e+01, 0.6294805223347e+01, + 0.3627274802357e-08, 0.3096565085313e+01, 0.6271346477544e+01, + 0.3806143885093e-08, 0.6367751709777e+00, 0.1725304118033e+02, + + 0.4433254641565e-08, 0.4848461503937e+01, 0.7445550607224e+01, + 0.3712319846576e-08, 0.1331950643655e+01, 0.4194847048887e+00, + 0.3849847534783e-08, 0.4958368297746e+00, 0.9562891316684e+00, + 0.3483955430165e-08, 0.2237215515707e+01, 0.1161697602389e+02, + 0.3961912730982e-08, 0.3332402188575e+01, 0.2277943724828e+02, + 0.3419978244481e-08, 0.5785600576016e+01, 0.1362553364512e+02, + 0.3329417758177e-08, 0.9812676559709e-01, 0.1685848245639e+02, + 0.4207206893193e-08, 0.9494780468236e+00, 0.2986433403208e+02, + 0.3268548976410e-08, 0.1739332095686e+00, 0.5749861718712e+01, + 0.3321880082685e-08, 0.1423354800666e+01, 0.6279143387820e+01, + + 0.4503173010852e-08, 0.2314972675293e+00, 0.1385561574497e+01, + 0.4316599090954e-08, 0.1012646782616e+00, 0.4176041334900e+01, + 0.3283493323850e-08, 0.5233306881265e+01, 0.6287008313071e+01, + 0.3164033542343e-08, 0.4005597257511e+01, 0.2099539292909e+02, + 0.4159720956725e-08, 0.5365676242020e+01, 0.5905702259363e+01, + 0.3565176892217e-08, 0.4284440620612e+01, 0.3932462625300e-02, + 0.3514440950221e-08, 0.4270562636575e+01, 0.7335344340001e+01, + 0.3540596871909e-08, 0.5953553201060e+01, 0.1234573916645e+02, + 0.2960769905118e-08, 0.1115180417718e+01, 0.2670964694522e+02, + 0.2962213739684e-08, 0.3863811918186e+01, 0.6408777551755e+00, + + 0.3883556700251e-08, 0.1268617928302e+01, 0.6660449441528e+01, + 0.2919225516346e-08, 0.4908605223265e+01, 0.1375773836557e+01, + 0.3115158863370e-08, 0.3744519976885e+01, 0.3802769619140e-01, + 0.4099438144212e-08, 0.4173244670532e+01, 0.4480965020977e+02, + 0.2899531858964e-08, 0.5910601428850e+01, 0.2059724391010e+02, + 0.3289733429855e-08, 0.2488050078239e+01, 0.1081813534213e+02, + 0.3933075612875e-08, 0.1122363652883e+01, 0.3773735910827e+00, + 0.3021403764467e-08, 0.4951973724904e+01, 0.2982630633589e+02, + 0.2798598949757e-08, 0.5117057845513e+01, 0.1937891852345e+02, + 0.3397421302707e-08, 0.6104159180476e+01, 0.6923953605621e+01, + + 0.3720398002179e-08, 0.1184933429829e+01, 0.3066615496545e+02, + 0.3598484186267e-08, 0.3505282086105e+01, 0.6147450479709e+01, + 0.3694594027310e-08, 0.2286651088141e+01, 0.2636725487657e+01, + 0.2680444152969e-08, 0.1871816775482e+00, 0.6816289982179e+01, + 0.3497574865641e-08, 0.3143251755431e+01, 0.6418701221183e+01, + 0.3130274129494e-08, 0.2462167316018e+01, 0.1235996607578e+02, + 0.3241119069551e-08, 0.4256374004686e+01, 0.1652265972112e+02, + 0.2601960842061e-08, 0.4970362941425e+01, 0.1045450126711e+02, + 0.2690601527504e-08, 0.2372657824898e+01, 0.3163918923335e+00, + 0.2908688152664e-08, 0.4232652627721e+01, 0.2828699048865e+02, + + 0.3120456131875e-08, 0.3925747001137e+00, 0.2195415756911e+02, + 0.3148855423384e-08, 0.3093478330445e+01, 0.1172006883645e+02, + 0.3051044261017e-08, 0.5560948248212e+01, 0.6055599646783e+01, + 0.2826006876660e-08, 0.5072790310072e+01, 0.5120601093667e+01, + 0.3100034191711e-08, 0.4998530231096e+01, 0.1799603123222e+02, + 0.2398771640101e-08, 0.2561739802176e+01, 0.6255674361143e+01, + 0.2384002842728e-08, 0.4087420284111e+01, 0.6310477339748e+01, + 0.2842146517568e-08, 0.2515048217955e+01, 0.5469525544182e+01, + 0.2847674371340e-08, 0.5235326497443e+01, 0.1034429499989e+02, + 0.2903722140764e-08, 0.1088200795797e+01, 0.6510552054109e+01, + + 0.3187610710605e-08, 0.4710624424816e+01, 0.1693792562116e+03, + 0.3048869992813e-08, 0.2857975896445e+00, 0.8390110365991e+01, + 0.2860216950984e-08, 0.2241619020815e+01, 0.2243449970715e+00, + 0.2701117683113e-08, 0.6651573305272e-01, 0.6129297044991e+01, + 0.2509891590152e-08, 0.1285135324585e+01, 0.1044027435778e+02, + 0.2623200252223e-08, 0.2981229834530e+00, 0.6436854655901e+01, + 0.2622541669202e-08, 0.6122470726189e+01, 0.9380959548977e+01, + 0.2818435667099e-08, 0.4251087148947e+01, 0.5934151399930e+01, + 0.2365196797465e-08, 0.3465070460790e+01, 0.2470570524223e+02, + 0.2358704646143e-08, 0.5791603815350e+01, 0.8671969964381e+01, + + 0.2388299481390e-08, 0.4142483772941e+01, 0.7096626156709e+01, + 0.1996041217224e-08, 0.2101901889496e+01, 0.1727188400790e+02, + 0.2687593060336e-08, 0.1526689456959e+01, 0.7075506709219e+02, + 0.2618913670810e-08, 0.2397684236095e+01, 0.6632000300961e+01, + 0.2571523050364e-08, 0.5751929456787e+00, 0.6206810014183e+01, + 0.2582135006946e-08, 0.5595464352926e+01, 0.4873985990671e+02, + 0.2372530190361e-08, 0.5092689490655e+01, 0.1590676413561e+02, + 0.2357178484712e-08, 0.4444363527851e+01, 0.3097883698531e+01, + 0.2451590394723e-08, 0.3108251687661e+01, 0.6612329252343e+00, + 0.2370045949608e-08, 0.2608133861079e+01, 0.3459636466239e+02, + + 0.2268997267358e-08, 0.3639717753384e+01, 0.2844914056730e-01, + 0.1731432137906e-08, 0.1741898445707e+00, 0.2019909489111e+02, + 0.1629869741622e-08, 0.3902225646724e+01, 0.3035599730800e+02, + 0.2206215801974e-08, 0.4971131250731e+01, 0.6281667977667e+01, + 0.2205469554680e-08, 0.1677462357110e+01, 0.6284483723224e+01, + 0.2148792362509e-08, 0.4236259604006e+01, 0.1980482729015e+02, + 0.1873733657847e-08, 0.5926814998687e+01, 0.2876692439167e+02, + 0.2026573758959e-08, 0.4349643351962e+01, 0.2449240616245e+02, + 0.1807770325110e-08, 0.5700940482701e+01, 0.2045286941806e+02, + 0.1881174408581e-08, 0.6601286363430e+00, 0.2358125818164e+02, + + 0.1368023671690e-08, 0.2211098592752e+01, 0.2473415438279e+02, + 0.1720017916280e-08, 0.4942488551129e+01, 0.1679593901136e+03, + 0.1702427665131e-08, 0.1452233856386e+01, 0.3338575901272e+03, + 0.1414032510054e-08, 0.5525357721439e+01, 0.1624205518357e+03, + 0.1652626045364e-08, 0.4108794283624e+01, 0.8956999012000e+02, + 0.1642957769686e-08, 0.7344335209984e+00, 0.5267006960365e+02, + 0.1614952403624e-08, 0.3541213951363e+01, 0.3332657872986e+02, + 0.1535988291188e-08, 0.4031094072151e+01, 0.3852657435933e+02, + 0.1593193738177e-08, 0.4185136203609e+01, 0.2282781046519e+03, + 0.1074569126382e-08, 0.1720485636868e+01, 0.8397383534231e+02, + + 0.1074408214509e-08, 0.2758613420318e+01, 0.8401985929482e+02, + 0.9700199670465e-09, 0.4216686842097e+01, 0.7826370942180e+02, + 0.1258433517061e-08, 0.2575068876639e+00, 0.3115650189215e+03, + 0.1240303229539e-08, 0.4800844956756e+00, 0.1784300471910e+03, + 0.9018345948127e-09, 0.3896756361552e+00, 0.5886454391678e+02, + 0.1135301432805e-08, 0.3700805023550e+00, 0.7842370451713e+02, + 0.9215887951370e-09, 0.4364579276638e+01, 0.1014262087719e+03, + 0.1055401054147e-08, 0.2156564222111e+01, 0.5660027930059e+02, + 0.1008725979831e-08, 0.5454015785234e+01, 0.4245678405627e+02, + 0.7217398104321e-09, 0.1597772562175e+01, 0.2457074661053e+03, + + 0.6912033134447e-09, 0.5824090621461e+01, 0.1679936946371e+03, + 0.6833881523549e-09, 0.3578778482835e+01, 0.6053048899753e+02, + 0.4887304205142e-09, 0.3724362812423e+01, 0.9656299901946e+02, + 0.5173709754788e-09, 0.5422427507933e+01, 0.2442876000072e+03, + 0.4671353097145e-09, 0.2396106924439e+01, 0.1435713242844e+03, + 0.5652608439480e-09, 0.2804028838685e+01, 0.8365903305582e+02, + 0.5604061331253e-09, 0.1638816006247e+01, 0.8433466158131e+02, + 0.4712723365400e-09, 0.8979003224474e+00, 0.3164282286739e+03, + 0.4909967465112e-09, 0.3210426725516e+01, 0.4059982187939e+03, + 0.4771358267658e-09, 0.5308027211629e+01, 0.1805255418145e+03, + + 0.3943451445989e-09, 0.2195145341074e+01, 0.2568537517081e+03, + 0.3952109120244e-09, 0.5081189491586e+01, 0.2449975330562e+03, + 0.3788134594789e-09, 0.4345171264441e+01, 0.1568131045107e+03, + 0.3738330190479e-09, 0.2613062847997e+01, 0.3948519331910e+03, + 0.3099866678136e-09, 0.2846760817689e+01, 0.1547176098872e+03, + 0.2002962716768e-09, 0.4921360989412e+01, 0.2268582385539e+03, + 0.2198291338754e-09, 0.1130360117454e+00, 0.1658638954901e+03, + 0.1491958330784e-09, 0.4228195232278e+01, 0.2219950288015e+03, + 0.1475384076173e-09, 0.3005721811604e+00, 0.3052819430710e+03, + 0.1661626624624e-09, 0.7830125621203e+00, 0.2526661704812e+03, + + 0.9015823460025e-10, 0.3807792942715e+01, 0.4171445043968e+03 }; + +/* Sun-to-Earth, T^0, Y */ + static const double e0y[] = { + 0.9998921098898e+00, 0.1826583913846e+00, 0.6283075850446e+01, + -0.2442700893735e-01, 0.0000000000000e+00, 0.0000000000000e+00, + 0.8352929742915e-02, 0.1395277998680e+00, 0.1256615170089e+02, + 0.1046697300177e-03, 0.9641423109763e-01, 0.1884922755134e+02, + 0.3110841876663e-04, 0.5381140401712e+01, 0.8399684731857e+02, + 0.2570269094593e-04, 0.5301016407128e+01, 0.5296909721118e+00, + 0.2147389623610e-04, 0.2662510869850e+01, 0.1577343543434e+01, + 0.1680344384050e-04, 0.5207904119704e+01, 0.6279552690824e+01, + 0.1679117312193e-04, 0.4582187486968e+01, 0.6286599010068e+01, + 0.1440512068440e-04, 0.1900688517726e+01, 0.2352866153506e+01, + + 0.1135139664999e-04, 0.5273108538556e+01, 0.5223693906222e+01, + 0.9345482571018e-05, 0.4503047687738e+01, 0.1203646072878e+02, + 0.9007418719568e-05, 0.1605621059637e+01, 0.1021328554739e+02, + 0.5671536712314e-05, 0.5812849070861e+00, 0.1059381944224e+01, + 0.7451401861666e-05, 0.2807346794836e+01, 0.3981490189893e+00, + 0.6393470057114e-05, 0.6029224133855e+01, 0.5753384878334e+01, + 0.6814275881697e-05, 0.6472990145974e+00, 0.4705732307012e+01, + 0.6113705628887e-05, 0.3813843419700e+01, 0.6812766822558e+01, + 0.4503851367273e-05, 0.4527804370996e+01, 0.5884926831456e+01, + 0.4522249141926e-05, 0.5991783029224e+01, 0.6256777527156e+01, + + 0.4501794307018e-05, 0.3798703844397e+01, 0.6309374173736e+01, + 0.5514927480180e-05, 0.3961257833388e+01, 0.5507553240374e+01, + 0.4062862799995e-05, 0.5256247296369e+01, 0.6681224869435e+01, + 0.5414900429712e-05, 0.5499032014097e+01, 0.7755226100720e+00, + 0.5463153987424e-05, 0.6173092454097e+01, 0.1414349524433e+02, + 0.5071611859329e-05, 0.2870244247651e+01, 0.7860419393880e+01, + 0.2195112094455e-05, 0.2952338617201e+01, 0.1150676975667e+02, + 0.2279139233919e-05, 0.5951775132933e+01, 0.7058598460518e+01, + 0.2278386100876e-05, 0.4845456398785e+01, 0.4694002934110e+01, + 0.2559088003308e-05, 0.6945321117311e+00, 0.1216800268190e+02, + + 0.2561079286856e-05, 0.6167224608301e+01, 0.7099330490126e+00, + 0.1792755796387e-05, 0.1400122509632e+01, 0.7962980379786e+00, + 0.1818715656502e-05, 0.4703347611830e+01, 0.6283142985870e+01, + 0.1818744924791e-05, 0.5086748900237e+01, 0.6283008715021e+01, + 0.1554518791390e-05, 0.5331008042713e-01, 0.2513230340178e+02, + 0.2063265737239e-05, 0.4283680484178e+01, 0.1179062909082e+02, + 0.1497613520041e-05, 0.6074207826073e+01, 0.5486777812467e+01, + 0.2000617940427e-05, 0.2501426281450e+01, 0.1778984560711e+02, + 0.1289731195580e-05, 0.3646340599536e+01, 0.7079373888424e+01, + 0.1282657998934e-05, 0.3232864804902e+01, 0.3738761453707e+01, + + 0.1528915968658e-05, 0.5581433416669e+01, 0.2132990797783e+00, + 0.1187304098432e-05, 0.5453576453694e+01, 0.9437762937313e+01, + 0.7842782928118e-06, 0.2823953922273e+00, 0.8827390247185e+01, + 0.7352892280868e-06, 0.1124369580175e+01, 0.1589072916335e+01, + 0.6570189360797e-06, 0.2089154042840e+01, 0.1176985366291e+02, + 0.6324967590410e-06, 0.6704855581230e+00, 0.6262300422539e+01, + 0.6298289872283e-06, 0.2836414855840e+01, 0.6303851278352e+01, + 0.6476686465855e-06, 0.4852433866467e+00, 0.7113454667900e-02, + 0.8587034651234e-06, 0.1453511005668e+01, 0.1672837615881e+03, + 0.8068948788113e-06, 0.9224087798609e+00, 0.6069776770667e+01, + + 0.8353786011661e-06, 0.4631707184895e+01, 0.3340612434717e+01, + 0.6009324532132e-06, 0.1829498827726e+01, 0.4136910472696e+01, + 0.7558158559566e-06, 0.2588596800317e+01, 0.6496374930224e+01, + 0.5809279504503e-06, 0.5516818853476e+00, 0.1097707878456e+02, + 0.5374131950254e-06, 0.6275674734960e+01, 0.1194447056968e+01, + 0.5711160507326e-06, 0.1091905956872e+01, 0.6282095334605e+01, + 0.5710183170746e-06, 0.2415001635090e+01, 0.6284056366286e+01, + 0.5144373590610e-06, 0.6020336443438e+01, 0.6290189305114e+01, + 0.5103108927267e-06, 0.3775634564605e+01, 0.6275962395778e+01, + 0.4960654697891e-06, 0.1073450946756e+01, 0.6127655567643e+01, + + 0.4786385689280e-06, 0.2431178012310e+01, 0.6438496133249e+01, + 0.6109911263665e-06, 0.5343356157914e+01, 0.3154687086868e+01, + 0.4839898944024e-06, 0.5830833594047e-01, 0.8018209333619e+00, + 0.4734822623919e-06, 0.4536080134821e+01, 0.3128388763578e+01, + 0.4834741473290e-06, 0.2585090489754e+00, 0.7084896783808e+01, + 0.5134858581156e-06, 0.4213317172603e+01, 0.1235285262111e+02, + 0.5064004264978e-06, 0.4814418806478e+00, 0.1185621865188e+02, + 0.3753476772761e-06, 0.1599953399788e+01, 0.8429241228195e+01, + 0.4935264014283e-06, 0.2157417556873e+01, 0.2544314396739e+01, + 0.3950929600897e-06, 0.3359394184254e+01, 0.5481254917084e+01, + + 0.4895849789777e-06, 0.5165704376558e+01, 0.9225539266174e+01, + 0.4215241688886e-06, 0.2065368800993e+01, 0.1726015463500e+02, + 0.3796773731132e-06, 0.1468606346612e+01, 0.4265981595566e+00, + 0.3114178142515e-06, 0.3615638079474e+01, 0.2146165377750e+01, + 0.3260664220838e-06, 0.4417134922435e+01, 0.4164311961999e+01, + 0.3976996123008e-06, 0.4700866883004e+01, 0.5856477690889e+01, + 0.2801459672924e-06, 0.4538902060922e+01, 0.1256967486051e+02, + 0.3638931868861e-06, 0.1334197991475e+01, 0.1807370494127e+02, + 0.2487013269476e-06, 0.3749275558275e+01, 0.2629832328990e-01, + 0.3034165481994e-06, 0.4236622030873e+00, 0.4535059491685e+01, + + 0.2676278825586e-06, 0.5970848007811e+01, 0.3930209696940e+01, + 0.2764903818918e-06, 0.5194636754501e+01, 0.1256262854127e+02, + 0.2485149930507e-06, 0.1002434207846e+01, 0.5088628793478e+01, + 0.2199305540941e-06, 0.3066773098403e+01, 0.1255903824622e+02, + 0.2571106500435e-06, 0.7588312459063e+00, 0.1336797263425e+02, + 0.2049751817158e-06, 0.3444977434856e+01, 0.1137170464392e+02, + 0.2599707296297e-06, 0.1873128542205e+01, 0.7143069561767e+02, + 0.1785018072217e-06, 0.5015891306615e+01, 0.1748016358760e+01, + 0.2324833891115e-06, 0.4618271239730e+01, 0.1831953657923e+02, + 0.1709711119545e-06, 0.5300003455669e+01, 0.4933208510675e+01, + + 0.2107159351716e-06, 0.2229819815115e+01, 0.7477522907414e+01, + 0.1750333080295e-06, 0.6161485880008e+01, 0.1044738781244e+02, + 0.2000598210339e-06, 0.2967357299999e+01, 0.8031092209206e+01, + 0.1380920248681e-06, 0.3027007923917e+01, 0.8635942003952e+01, + 0.1412460470299e-06, 0.6037597163798e+01, 0.2942463415728e+01, + 0.1888459803001e-06, 0.8561476243374e+00, 0.1561374759853e+03, + 0.1788370542585e-06, 0.4869736290209e+01, 0.1592596075957e+01, + 0.1360893296167e-06, 0.3626411886436e+01, 0.1309584267300e+02, + 0.1506846530160e-06, 0.1550975377427e+01, 0.1649636139783e+02, + 0.1800913376176e-06, 0.2075826033190e+01, 0.1729818233119e+02, + + 0.1436261390649e-06, 0.6148876420255e+01, 0.2042657109477e+02, + 0.1220227114151e-06, 0.4382583879906e+01, 0.7632943190217e+01, + 0.1337883603592e-06, 0.2036644327361e+01, 0.1213955354133e+02, + 0.1159326650738e-06, 0.3892276994687e+01, 0.5331357529664e+01, + 0.1352853128569e-06, 0.1447950649744e+01, 0.1673046366289e+02, + 0.1433408296083e-06, 0.4457854692961e+01, 0.7342457794669e+01, + 0.1234701666518e-06, 0.1538818147151e+01, 0.6279485555400e+01, + 0.1234027192007e-06, 0.1968523220760e+01, 0.6286666145492e+01, + 0.1244024091797e-06, 0.5779803499985e+01, 0.1511046609763e+02, + 0.1097934945516e-06, 0.6210975221388e+00, 0.1098880815746e+02, + + 0.1254611329856e-06, 0.2591963807998e+01, 0.1572083878776e+02, + 0.1158247286784e-06, 0.2483612812670e+01, 0.5729506548653e+01, + 0.9039078252960e-07, 0.3857554579796e+01, 0.9623688285163e+01, + 0.9108024978836e-07, 0.5826368512984e+01, 0.7234794171227e+01, + 0.8887068108436e-07, 0.3475694573987e+01, 0.6148010737701e+01, + 0.8632374035438e-07, 0.3059070488983e-01, 0.6418140963190e+01, + 0.7893186992967e-07, 0.1583194837728e+01, 0.2118763888447e+01, + 0.8297650201172e-07, 0.8519770534637e+00, 0.1471231707864e+02, + 0.1019759578988e-06, 0.1319598738732e+00, 0.1349867339771e+01, + 0.1010037696236e-06, 0.9937860115618e+00, 0.6836645152238e+01, + + 0.1047727548266e-06, 0.1382138405399e+01, 0.5999216516294e+01, + 0.7351993881086e-07, 0.3833397851735e+01, 0.6040347114260e+01, + 0.9868771092341e-07, 0.2124913814390e+01, 0.6566935184597e+01, + 0.7007321959390e-07, 0.5946305343763e+01, 0.6525804586632e+01, + 0.6861411679709e-07, 0.4574654977089e+01, 0.7238675589263e+01, + 0.7554519809614e-07, 0.5949232686844e+01, 0.1253985337760e+02, + 0.9541880448335e-07, 0.3495242990564e+01, 0.2122839202813e+02, + 0.7185606722155e-07, 0.4310113471661e+01, 0.6245048154254e+01, + 0.7131360871710e-07, 0.5480309323650e+01, 0.6321103546637e+01, + 0.6651142021039e-07, 0.5411097713654e+01, 0.5327476111629e+01, + + 0.8538618213667e-07, 0.1827849973951e+01, 0.1101510648075e+02, + 0.8634954288044e-07, 0.5443584943349e+01, 0.5643178611111e+01, + 0.7449415051484e-07, 0.2011535459060e+01, 0.5368044267797e+00, + 0.7421047599169e-07, 0.3464562529249e+01, 0.2354323048545e+02, + 0.6140694354424e-07, 0.5657556228815e+01, 0.1296430071988e+02, + 0.6353525143033e-07, 0.3463816593821e+01, 0.1990745094947e+01, + 0.6221964013447e-07, 0.1532259498697e+01, 0.9517183207817e+00, + 0.5852480257244e-07, 0.1375396598875e+01, 0.9555997388169e+00, + 0.6398637498911e-07, 0.2405645801972e+01, 0.2407292145756e+02, + 0.7039744069878e-07, 0.5397541799027e+01, 0.5225775174439e+00, + + 0.6977997694382e-07, 0.4762347105419e+01, 0.1097355562493e+02, + 0.7460629558396e-07, 0.2711944692164e+01, 0.2200391463820e+02, + 0.5376577536101e-07, 0.2352980430239e+01, 0.1431416805965e+02, + 0.7530607893556e-07, 0.1943940180699e+01, 0.1842262939178e+02, + 0.6822928971605e-07, 0.4337651846959e+01, 0.1554202828031e+00, + 0.6220772380094e-07, 0.6716871369278e+00, 0.1845107853235e+02, + 0.6586950799043e-07, 0.2229714460505e+01, 0.5216580451554e+01, + 0.5873800565771e-07, 0.7627013920580e+00, 0.6398972393349e+00, + 0.6264346929745e-07, 0.6202785478961e+00, 0.6277552955062e+01, + 0.6257929115669e-07, 0.2886775596668e+01, 0.6288598745829e+01, + + 0.5343536033409e-07, 0.1977241012051e+01, 0.4690479774488e+01, + 0.5587849781714e-07, 0.1922923484825e+01, 0.1551045220144e+01, + 0.6905100845603e-07, 0.3570757164631e+01, 0.1030928125552e+00, + 0.6178957066649e-07, 0.5197558947765e+01, 0.5230807360890e+01, + 0.6187270224331e-07, 0.8193497368922e+00, 0.5650292065779e+01, + 0.5385664291426e-07, 0.5406336665586e+01, 0.7771377146812e+02, + 0.6329363917926e-07, 0.2837760654536e+01, 0.2608790314060e+02, + 0.4546018761604e-07, 0.2933580297050e+01, 0.5535693017924e+00, + 0.6196091049375e-07, 0.4157871494377e+01, 0.8467247584405e+02, + 0.6159555108218e-07, 0.3211703561703e+01, 0.2394243902548e+03, + + 0.4995340539317e-07, 0.1459098102922e+01, 0.4732030630302e+01, + 0.5457031243572e-07, 0.1430457676136e+01, 0.6179983037890e+01, + 0.4863461418397e-07, 0.2196425916730e+01, 0.9027992316901e+02, + 0.5342947626870e-07, 0.2086612890268e+01, 0.6386168663001e+01, + 0.5674296648439e-07, 0.2760204966535e+01, 0.6915859635113e+01, + 0.4745783120161e-07, 0.4245368971862e+01, 0.6282970628506e+01, + 0.4745676961198e-07, 0.5544725787016e+01, 0.6283181072386e+01, + 0.4049796869973e-07, 0.2213984363586e+01, 0.6254626709878e+01, + 0.4248333596940e-07, 0.8075781952896e+00, 0.7875671926403e+01, + 0.4027178070205e-07, 0.1293268540378e+01, 0.6311524991013e+01, + + 0.4066543943476e-07, 0.3986141175804e+01, 0.3634620989887e+01, + 0.4858863787880e-07, 0.1276112738231e+01, 0.5760498333002e+01, + 0.5277398263530e-07, 0.4916111741527e+01, 0.2515860172507e+02, + 0.4105635656559e-07, 0.1725805864426e+01, 0.6709674010002e+01, + 0.4376781925772e-07, 0.2243642442106e+01, 0.6805653367890e+01, + 0.3235827894693e-07, 0.3614135118271e+01, 0.1066495398892e+01, + 0.3073244740308e-07, 0.2460873393460e+01, 0.5863591145557e+01, + 0.3088609271373e-07, 0.5678431771790e+01, 0.9917696840332e+01, + 0.3393022279836e-07, 0.3814017477291e+01, 0.1391601904066e+02, + 0.3038686508802e-07, 0.4660216229171e+01, 0.1256621883632e+02, + + 0.4019677752497e-07, 0.5906906243735e+01, 0.1334167431096e+02, + 0.3288834998232e-07, 0.9536146445882e+00, 0.1620077269078e+02, + 0.3889973794631e-07, 0.3942205097644e+01, 0.7478166569050e-01, + 0.3050438987141e-07, 0.1624810271286e+01, 0.1805292951336e+02, + 0.3601142564638e-07, 0.4030467142575e+01, 0.6208294184755e+01, + 0.3689015557141e-07, 0.3648878818694e+01, 0.5966683958112e+01, + 0.3563471893565e-07, 0.5749584017096e+01, 0.6357857516136e+01, + 0.2776183170667e-07, 0.2630124187070e+01, 0.3523159621801e-02, + 0.2922350530341e-07, 0.1790346403629e+01, 0.1272157198369e+02, + 0.3511076917302e-07, 0.6142198301611e+01, 0.6599467742779e+01, + + 0.3619351007632e-07, 0.1432421386492e+01, 0.6019991944201e+01, + 0.2561254711098e-07, 0.2302822475792e+01, 0.1259245002418e+02, + 0.2626903942920e-07, 0.8660470994571e+00, 0.6702560555334e+01, + 0.2550187397083e-07, 0.6069721995383e+01, 0.1057540660594e+02, + 0.2535873526138e-07, 0.1079020331795e-01, 0.3141537925223e+02, + 0.3519786153847e-07, 0.3809066902283e+01, 0.2505706758577e+03, + 0.3424651492873e-07, 0.2075435114417e+01, 0.6546159756691e+01, + 0.2372676630861e-07, 0.2057803120154e+01, 0.2388894113936e+01, + 0.2710980779541e-07, 0.1510068488010e+01, 0.1202934727411e+02, + 0.3038710889704e-07, 0.5043617528901e+01, 0.1256608456547e+02, + + 0.2220364130585e-07, 0.3694793218205e+01, 0.1336244973887e+02, + 0.3025880825460e-07, 0.5450618999049e-01, 0.2908881142201e+02, + 0.2784493486864e-07, 0.3381164084502e+01, 0.1494531617769e+02, + 0.2294414142438e-07, 0.4382309025210e+01, 0.6076890225335e+01, + 0.2012723294724e-07, 0.9142212256518e+00, 0.6262720680387e+01, + 0.2036357831958e-07, 0.5676172293154e+01, 0.4701116388778e+01, + 0.2003474823288e-07, 0.2592767977625e+01, 0.6303431020504e+01, + 0.2207144900109e-07, 0.5404976271180e+01, 0.6489261475556e+01, + 0.2481664905135e-07, 0.4373284587027e+01, 0.1204357418345e+02, + 0.2674949182295e-07, 0.5859182188482e+01, 0.4590910121555e+01, + + 0.2450554720322e-07, 0.4555381557451e+01, 0.1495633313810e+00, + 0.2601975986457e-07, 0.3933165584959e+01, 0.1965104848470e+02, + 0.2199860022848e-07, 0.5227977189087e+01, 0.1351787002167e+02, + 0.2448121172316e-07, 0.4858060353949e+01, 0.1162474756779e+01, + 0.1876014864049e-07, 0.5690546553605e+01, 0.6279194432410e+01, + 0.1874513219396e-07, 0.4099539297446e+01, 0.6286957268481e+01, + 0.2156380842559e-07, 0.4382594769913e+00, 0.1813929450232e+02, + 0.1981691240061e-07, 0.1829784152444e+01, 0.4686889479442e+01, + 0.2329992648539e-07, 0.2836254278973e+01, 0.1002183730415e+02, + 0.1765184135302e-07, 0.2803494925833e+01, 0.4292330755499e+01, + + 0.2436368366085e-07, 0.2836897959677e+01, 0.9514313292143e+02, + 0.2164089203889e-07, 0.6127522446024e+01, 0.6037244212485e+01, + 0.1847755034221e-07, 0.3683163635008e+01, 0.2427287361862e+00, + 0.1674798769966e-07, 0.3316993867246e+00, 0.1311972100268e+02, + 0.2222542124356e-07, 0.8294097805480e+00, 0.1266924451345e+02, + 0.2071074505925e-07, 0.3659492220261e+01, 0.6528907488406e+01, + 0.1608224471835e-07, 0.4774492067182e+01, 0.1352175143971e+02, + 0.1857583439071e-07, 0.2873120597682e+01, 0.8662240327241e+01, + 0.1793018836159e-07, 0.5282441177929e+00, 0.6819880277225e+01, + 0.1575391221692e-07, 0.1320789654258e+01, 0.1102062672231e+00, + + 0.1840132009557e-07, 0.1917110916256e+01, 0.6514761976723e+02, + 0.1760917288281e-07, 0.2972635937132e+01, 0.5746271423666e+01, + 0.1561779518516e-07, 0.4372569261981e+01, 0.6272439236156e+01, + 0.1558687885205e-07, 0.5416424926425e+01, 0.6293712464735e+01, + 0.1951359382579e-07, 0.3094448898752e+01, 0.2301353951334e+02, + 0.1569144275614e-07, 0.2802103689808e+01, 0.1765478049437e+02, + 0.1479130389462e-07, 0.2136435020467e+01, 0.2077542790660e-01, + 0.1467828510764e-07, 0.7072627435674e+00, 0.1052268489556e+01, + 0.1627627337440e-07, 0.3947607143237e+01, 0.6327837846670e+00, + 0.1503498479758e-07, 0.4079248909190e+01, 0.7626583626240e-01, + + 0.1297967708237e-07, 0.6269637122840e+01, 0.1149965630200e+02, + 0.1374416896634e-07, 0.4175657970702e+01, 0.6016468784579e+01, + 0.1783812325219e-07, 0.1476540547560e+01, 0.3301902111895e+02, + 0.1525884228756e-07, 0.4653477715241e+01, 0.9411464614024e+01, + 0.1451067396763e-07, 0.2573001128225e+01, 0.1277945078067e+02, + 0.1297713111950e-07, 0.5612799618771e+01, 0.6549682916313e+01, + 0.1462784012820e-07, 0.4189661623870e+01, 0.1863592847156e+02, + 0.1384185980007e-07, 0.2656915472196e+01, 0.2379164476796e+01, + 0.1221497599801e-07, 0.5612515760138e+01, 0.1257326515556e+02, + 0.1560574525896e-07, 0.4783414317919e+01, 0.1887552587463e+02, + + 0.1544598372036e-07, 0.2694431138063e+01, 0.1820933031200e+02, + 0.1531678928696e-07, 0.4105103489666e+01, 0.2593412433514e+02, + 0.1349321503795e-07, 0.3082437194015e+00, 0.5120601093667e+01, + 0.1252030290917e-07, 0.6124072334087e+01, 0.6993008899458e+01, + 0.1459243816687e-07, 0.3733103981697e+01, 0.3813291813120e-01, + 0.1226103625262e-07, 0.1267127706817e+01, 0.2435678079171e+02, + 0.1019449641504e-07, 0.4367790112269e+01, 0.1725663147538e+02, + 0.1380789433607e-07, 0.3387201768700e+01, 0.2458316379602e+00, + 0.1019453421658e-07, 0.9204143073737e+00, 0.6112403035119e+01, + 0.1297929434405e-07, 0.5786874896426e+01, 0.1249137003520e+02, + + 0.9912677786097e-08, 0.3164232870746e+01, 0.6247047890016e+01, + 0.9829386098599e-08, 0.2586762413351e+01, 0.6453748665772e+01, + 0.1226807746104e-07, 0.6239068436607e+01, 0.5429879531333e+01, + 0.1192691755997e-07, 0.1867380051424e+01, 0.6290122169689e+01, + 0.9836499227081e-08, 0.3424716293727e+00, 0.6319103810876e+01, + 0.9642862564285e-08, 0.5661372990657e+01, 0.8273820945392e+01, + 0.1165184404862e-07, 0.5768367239093e+01, 0.1778273215245e+02, + 0.1175794418818e-07, 0.1657351222943e+01, 0.6276029531202e+01, + 0.1018948635601e-07, 0.6458292350865e+00, 0.1254537627298e+02, + 0.9500383606676e-08, 0.1054306140741e+01, 0.1256517118505e+02, + + 0.1227512202906e-07, 0.2505278379114e+01, 0.2248384854122e+02, + 0.9664792009993e-08, 0.4289737277000e+01, 0.6259197520765e+01, + 0.9613285666331e-08, 0.5500597673141e+01, 0.6306954180126e+01, + 0.1117906736211e-07, 0.2361405953468e+01, 0.1779695906178e+02, + 0.9611378640782e-08, 0.2851310576269e+01, 0.2061856251104e+00, + 0.8845354852370e-08, 0.6208777705343e+01, 0.1692165728891e+01, + 0.1054046966600e-07, 0.5413091423934e+01, 0.2204125344462e+00, + 0.1215539124483e-07, 0.5613969479755e+01, 0.8257698122054e+02, + 0.9932460955209e-08, 0.1106124877015e+01, 0.1017725758696e+02, + 0.8785804715043e-08, 0.2869224476477e+01, 0.9491756770005e+00, + + 0.8538084097562e-08, 0.6159640899344e+01, 0.6393282117669e+01, + 0.8648994369529e-08, 0.1374901198784e+01, 0.4804209201333e+01, + 0.1039063219067e-07, 0.5171080641327e+01, 0.1550861511662e+02, + 0.8867983926439e-08, 0.8317320304902e+00, 0.3903911373650e+01, + 0.8327495955244e-08, 0.3605591969180e+01, 0.6172869583223e+01, + 0.9243088356133e-08, 0.6114299196843e+01, 0.6267823317922e+01, + 0.9205657357835e-08, 0.3675153683737e+01, 0.6298328382969e+01, + 0.1033269714606e-07, 0.3313328813024e+01, 0.5573142801433e+01, + 0.8001706275552e-08, 0.2019980960053e+01, 0.2648454860559e+01, + 0.9171858254191e-08, 0.8992015524177e+00, 0.1498544001348e+03, + + 0.1075327150242e-07, 0.2898669963648e+01, 0.3694923081589e+02, + 0.9884866689828e-08, 0.4946715904478e+01, 0.1140367694411e+02, + 0.9541835576677e-08, 0.2371787888469e+01, 0.1256713221673e+02, + 0.7739903376237e-08, 0.2213775190612e+01, 0.7834121070590e+01, + 0.7311962684106e-08, 0.3429378787739e+01, 0.1192625446156e+02, + 0.9724904869624e-08, 0.6195878564404e+01, 0.2280573557157e+02, + 0.9251628983612e-08, 0.6511509527390e+00, 0.2787043132925e+01, + 0.7320763787842e-08, 0.6001083639421e+01, 0.6282655592598e+01, + 0.7320296650962e-08, 0.3789073265087e+01, 0.6283496108294e+01, + 0.7947032271039e-08, 0.1059659582204e+01, 0.1241073141809e+02, + + 0.9005277053115e-08, 0.1280315624361e+01, 0.6281591679874e+01, + 0.8995601652048e-08, 0.2224439106766e+01, 0.6284560021018e+01, + 0.8288040568796e-08, 0.5234914433867e+01, 0.1241658836951e+02, + 0.6359381347255e-08, 0.4137989441490e+01, 0.1596186371003e+01, + 0.8699572228626e-08, 0.1758411009497e+01, 0.6133512519065e+01, + 0.6456797542736e-08, 0.5919285089994e+01, 0.1685848245639e+02, + 0.7424573475452e-08, 0.5414616938827e+01, 0.4061219149443e+01, + 0.7235671196168e-08, 0.1496516557134e+01, 0.1610006857377e+03, + 0.8104015182733e-08, 0.1919918242764e+01, 0.8460828644453e+00, + 0.8098576535937e-08, 0.3819615855458e+01, 0.3894181736510e+01, + + 0.6275292346625e-08, 0.6244264115141e+01, 0.8531963191132e+00, + 0.6052432989112e-08, 0.5037731872610e+00, 0.1567108171867e+02, + 0.5705651535817e-08, 0.2984557271995e+01, 0.1258692712880e+02, + 0.5789650115138e-08, 0.6087038140697e+01, 0.1193336791622e+02, + 0.5512132153377e-08, 0.5855668994076e+01, 0.1232342296471e+02, + 0.7388890819102e-08, 0.2443128574740e+01, 0.4907302013889e+01, + 0.5467593991798e-08, 0.3017561234194e+01, 0.1884211409667e+02, + 0.6388519802999e-08, 0.5887386712935e+01, 0.5217580628120e+02, + 0.6106777149944e-08, 0.3483461059895e+00, 0.1422690933580e-01, + 0.7383420275489e-08, 0.5417387056707e+01, 0.2358125818164e+02, + + 0.5505208141738e-08, 0.2848193644783e+01, 0.1151388321134e+02, + 0.6310757462877e-08, 0.2349882520828e+01, 0.1041998632314e+02, + 0.6166904929691e-08, 0.5728575944077e+00, 0.6151533897323e+01, + 0.5263442042754e-08, 0.4495796125937e+01, 0.1885275071096e+02, + 0.5591828082629e-08, 0.1355441967677e+01, 0.4337116142245e+00, + 0.5397051680497e-08, 0.1673422864307e+01, 0.6286362197481e+01, + 0.5396992745159e-08, 0.1833502206373e+01, 0.6279789503410e+01, + 0.6572913000726e-08, 0.3331122065824e+01, 0.1176433076753e+02, + 0.5123421866413e-08, 0.2165327142679e+01, 0.1245594543367e+02, + 0.5930495725999e-08, 0.2931146089284e+01, 0.6414617803568e+01, + + 0.6431797403933e-08, 0.4134407994088e+01, 0.1350651127443e+00, + 0.5003182207604e-08, 0.3805420303749e+01, 0.1096996532989e+02, + 0.5587731032504e-08, 0.1082469260599e+01, 0.6062663316000e+01, + 0.5935263407816e-08, 0.8384333678401e+00, 0.5326786718777e+01, + 0.4756019827760e-08, 0.3552588749309e+01, 0.3104930017775e+01, + 0.6599951172637e-08, 0.4320826409528e+01, 0.4087944051283e+02, + 0.5902606868464e-08, 0.4811879454445e+01, 0.5849364236221e+01, + 0.5921147809031e-08, 0.9942628922396e-01, 0.1581959461667e+01, + 0.5505382581266e-08, 0.2466557607764e+01, 0.6503488384892e+01, + 0.5353771071862e-08, 0.4551978748683e+01, 0.1735668374386e+03, + + 0.5063282210946e-08, 0.5710812312425e+01, 0.1248988586463e+02, + 0.5926120403383e-08, 0.1333998428358e+01, 0.2673594526851e+02, + 0.5211016176149e-08, 0.4649315360760e+01, 0.2460261242967e+02, + 0.5347075084894e-08, 0.5512754081205e+01, 0.4171425416666e+01, + 0.4872609773574e-08, 0.1308025299938e+01, 0.5333900173445e+01, + 0.4727711321420e-08, 0.2144908368062e+01, 0.7232251527446e+01, + 0.6029426018652e-08, 0.5567259412084e+01, 0.3227113045244e+03, + 0.4321485284369e-08, 0.5230667156451e+01, 0.9388005868221e+01, + 0.4476406760553e-08, 0.6134081115303e+01, 0.5547199253223e+01, + 0.5835268277420e-08, 0.4783808492071e+01, 0.7285056171570e+02, + + 0.5172183602748e-08, 0.5161817911099e+01, 0.1884570439172e+02, + 0.5693571465184e-08, 0.1381646203111e+01, 0.9723862754494e+02, + 0.4060634965349e-08, 0.3876705259495e+00, 0.4274518229222e+01, + 0.3967398770473e-08, 0.5029491776223e+01, 0.3496032717521e+01, + 0.3943754005255e-08, 0.1923162955490e+01, 0.6244942932314e+01, + 0.4781323427824e-08, 0.4633332586423e+01, 0.2929661536378e+02, + 0.3871483781204e-08, 0.1616650009743e+01, 0.6321208768577e+01, + 0.5141741733997e-08, 0.9817316704659e-01, 0.1232032006293e+02, + 0.4002385978497e-08, 0.3656161212139e+01, 0.7018952447668e+01, + 0.4901092604097e-08, 0.4404098713092e+01, 0.1478866649112e+01, + + 0.3740932630345e-08, 0.5181188732639e+00, 0.6922973089781e+01, + 0.4387283718538e-08, 0.3254859566869e+01, 0.2331413144044e+03, + 0.5019197802033e-08, 0.3086773224677e+01, 0.1715706182245e+02, + 0.3834931695175e-08, 0.2797882673542e+01, 0.1491901785440e+02, + 0.3760413942497e-08, 0.2892676280217e+01, 0.1726726808967e+02, + 0.3719717204628e-08, 0.5861046025739e+01, 0.6297302759782e+01, + 0.4145623530149e-08, 0.2168239627033e+01, 0.1376059875786e+02, + 0.3932788425380e-08, 0.6271811124181e+01, 0.7872148766781e+01, + 0.3686377476857e-08, 0.3936853151404e+01, 0.6268848941110e+01, + 0.3779077950339e-08, 0.1404148734043e+01, 0.4157198507331e+01, + + 0.4091334550598e-08, 0.2452436180854e+01, 0.9779108567966e+01, + 0.3926694536146e-08, 0.6102292739040e+01, 0.1098419223922e+02, + 0.4841000253289e-08, 0.6072760457276e+01, 0.1252801878276e+02, + 0.4949340130240e-08, 0.1154832815171e+01, 0.1617106187867e+03, + 0.3761557737360e-08, 0.5527545321897e+01, 0.3185192151914e+01, + 0.3647396268188e-08, 0.1525035688629e+01, 0.6271346477544e+01, + 0.3932405074189e-08, 0.5570681040569e+01, 0.2139354194808e+02, + 0.3631322501141e-08, 0.1981240601160e+01, 0.6294805223347e+01, + 0.4130007425139e-08, 0.2050060880201e+01, 0.2195415756911e+02, + 0.4433905965176e-08, 0.3277477970321e+01, 0.7445550607224e+01, + + 0.3851814176947e-08, 0.5210690074886e+01, 0.9562891316684e+00, + 0.3485807052785e-08, 0.6653274904611e+00, 0.1161697602389e+02, + 0.3979772816991e-08, 0.1767941436148e+01, 0.2277943724828e+02, + 0.3402607460500e-08, 0.3421746306465e+01, 0.1087398597200e+02, + 0.4049993000926e-08, 0.1127144787547e+01, 0.3163918923335e+00, + 0.3420511182382e-08, 0.4214794779161e+01, 0.1362553364512e+02, + 0.3640772365012e-08, 0.5324905497687e+01, 0.1725304118033e+02, + 0.3323037987501e-08, 0.6135761838271e+01, 0.6279143387820e+01, + 0.4503141663637e-08, 0.1802305450666e+01, 0.1385561574497e+01, + 0.4314560055588e-08, 0.4812299731574e+01, 0.4176041334900e+01, + + 0.3294226949110e-08, 0.3657547059723e+01, 0.6287008313071e+01, + 0.3215657197281e-08, 0.4866676894425e+01, 0.5749861718712e+01, + 0.4129362656266e-08, 0.3809342558906e+01, 0.5905702259363e+01, + 0.3137762976388e-08, 0.2494635174443e+01, 0.2099539292909e+02, + 0.3514010952384e-08, 0.2699961831678e+01, 0.7335344340001e+01, + 0.3327607571530e-08, 0.3318457714816e+01, 0.5436992986000e+01, + 0.3541066946675e-08, 0.4382703582466e+01, 0.1234573916645e+02, + 0.3216179847052e-08, 0.5271066317054e+01, 0.3802769619140e-01, + 0.2959045059570e-08, 0.5819591585302e+01, 0.2670964694522e+02, + 0.3884040326665e-08, 0.5980934960428e+01, 0.6660449441528e+01, + + 0.2922027539886e-08, 0.3337290282483e+01, 0.1375773836557e+01, + 0.4110846382042e-08, 0.5742978187327e+01, 0.4480965020977e+02, + 0.2934508411032e-08, 0.2278075804200e+01, 0.6408777551755e+00, + 0.3966896193000e-08, 0.5835747858477e+01, 0.3773735910827e+00, + 0.3286695827610e-08, 0.5838898193902e+01, 0.3932462625300e-02, + 0.3720643094196e-08, 0.1122212337858e+01, 0.1646033343740e+02, + 0.3285508906174e-08, 0.9182250996416e+00, 0.1081813534213e+02, + 0.3753880575973e-08, 0.5174761973266e+01, 0.5642198095270e+01, + 0.3022129385587e-08, 0.3381611020639e+01, 0.2982630633589e+02, + 0.2798569205621e-08, 0.3546193723922e+01, 0.1937891852345e+02, + + 0.3397872070505e-08, 0.4533203197934e+01, 0.6923953605621e+01, + 0.3708099772977e-08, 0.2756168198616e+01, 0.3066615496545e+02, + 0.3599283541510e-08, 0.1934395469918e+01, 0.6147450479709e+01, + 0.3688702753059e-08, 0.7149920971109e+00, 0.2636725487657e+01, + 0.2681084724003e-08, 0.4899819493154e+01, 0.6816289982179e+01, + 0.3495993460759e-08, 0.1572418915115e+01, 0.6418701221183e+01, + 0.3130770324995e-08, 0.8912190180489e+00, 0.1235996607578e+02, + 0.2744353821941e-08, 0.3800821940055e+01, 0.2059724391010e+02, + 0.2842732906341e-08, 0.2644717440029e+01, 0.2828699048865e+02, + 0.3046882682154e-08, 0.3987793020179e+01, 0.6055599646783e+01, + + 0.2399072455143e-08, 0.9908826440764e+00, 0.6255674361143e+01, + 0.2384306274204e-08, 0.2516149752220e+01, 0.6310477339748e+01, + 0.2977324500559e-08, 0.5849195642118e+01, 0.1652265972112e+02, + 0.3062835258972e-08, 0.1681660100162e+01, 0.1172006883645e+02, + 0.3109682589231e-08, 0.5804143987737e+00, 0.2751146787858e+02, + 0.2903920355299e-08, 0.5800768280123e+01, 0.6510552054109e+01, + 0.2823221989212e-08, 0.9241118370216e+00, 0.5469525544182e+01, + 0.3187949696649e-08, 0.3139776445735e+01, 0.1693792562116e+03, + 0.2922559771655e-08, 0.3549440782984e+01, 0.2630839062450e+00, + 0.2436302066603e-08, 0.4735540696319e+01, 0.3946258593675e+00, + + 0.3049473043606e-08, 0.4998289124561e+01, 0.8390110365991e+01, + 0.2863682575784e-08, 0.6709515671102e+00, 0.2243449970715e+00, + 0.2641750517966e-08, 0.5410978257284e+01, 0.2986433403208e+02, + 0.2704093466243e-08, 0.4778317207821e+01, 0.6129297044991e+01, + 0.2445522177011e-08, 0.6009020662222e+01, 0.1171295538178e+02, + 0.2623608810230e-08, 0.5010449777147e+01, 0.6436854655901e+01, + 0.2079259704053e-08, 0.5980943768809e+01, 0.2019909489111e+02, + 0.2820225596771e-08, 0.2679965110468e+01, 0.5934151399930e+01, + 0.2365221950927e-08, 0.1894231148810e+01, 0.2470570524223e+02, + 0.2359682077149e-08, 0.4220752950780e+01, 0.8671969964381e+01, + + 0.2387577137206e-08, 0.2571783940617e+01, 0.7096626156709e+01, + 0.1982102089816e-08, 0.5169765997119e+00, 0.1727188400790e+02, + 0.2687502389925e-08, 0.6239078264579e+01, 0.7075506709219e+02, + 0.2207751669135e-08, 0.2031184412677e+01, 0.4377611041777e+01, + 0.2618370214274e-08, 0.8266079985979e+00, 0.6632000300961e+01, + 0.2591951887361e-08, 0.8819350522008e+00, 0.4873985990671e+02, + 0.2375055656248e-08, 0.3520944177789e+01, 0.1590676413561e+02, + 0.2472019978911e-08, 0.1551431908671e+01, 0.6612329252343e+00, + 0.2368157127199e-08, 0.4178610147412e+01, 0.3459636466239e+02, + 0.1764846605693e-08, 0.1506764000157e+01, 0.1980094587212e+02, + + 0.2291769608798e-08, 0.2118250611782e+01, 0.2844914056730e-01, + 0.2209997316943e-08, 0.3363255261678e+01, 0.2666070658668e+00, + 0.2292699097923e-08, 0.4200423956460e+00, 0.1484170571900e-02, + 0.1629683015329e-08, 0.2331362582487e+01, 0.3035599730800e+02, + 0.2206492862426e-08, 0.3400274026992e+01, 0.6281667977667e+01, + 0.2205746568257e-08, 0.1066051230724e+00, 0.6284483723224e+01, + 0.2026310767991e-08, 0.2779066487979e+01, 0.2449240616245e+02, + 0.1762977622163e-08, 0.9951450691840e+00, 0.2045286941806e+02, + 0.1368535049606e-08, 0.6402447365817e+00, 0.2473415438279e+02, + 0.1720598775450e-08, 0.2303524214705e+00, 0.1679593901136e+03, + + 0.1702429015449e-08, 0.6164622655048e+01, 0.3338575901272e+03, + 0.1414033197685e-08, 0.3954561185580e+01, 0.1624205518357e+03, + 0.1573768958043e-08, 0.2028286308984e+01, 0.3144167757552e+02, + 0.1650705184447e-08, 0.2304040666128e+01, 0.5267006960365e+02, + 0.1651087618855e-08, 0.2538461057280e+01, 0.8956999012000e+02, + 0.1616409518983e-08, 0.5111054348152e+01, 0.3332657872986e+02, + 0.1537175173581e-08, 0.5601130666603e+01, 0.3852657435933e+02, + 0.1593191980553e-08, 0.2614340453411e+01, 0.2282781046519e+03, + 0.1499480170643e-08, 0.3624721577264e+01, 0.2823723341956e+02, + 0.1493807843235e-08, 0.4214569879008e+01, 0.2876692439167e+02, + + 0.1074571199328e-08, 0.1496911744704e+00, 0.8397383534231e+02, + 0.1074406983417e-08, 0.1187817671922e+01, 0.8401985929482e+02, + 0.9757576855851e-09, 0.2655703035858e+01, 0.7826370942180e+02, + 0.1258432887565e-08, 0.4969896184844e+01, 0.3115650189215e+03, + 0.1240336343282e-08, 0.5192460776926e+01, 0.1784300471910e+03, + 0.9016107005164e-09, 0.1960356923057e+01, 0.5886454391678e+02, + 0.1135392360918e-08, 0.5082427809068e+01, 0.7842370451713e+02, + 0.9216046089565e-09, 0.2793775037273e+01, 0.1014262087719e+03, + 0.1061276615030e-08, 0.3726144311409e+01, 0.5660027930059e+02, + 0.1010110596263e-08, 0.7404080708937e+00, 0.4245678405627e+02, + + 0.7217424756199e-09, 0.2697449980577e-01, 0.2457074661053e+03, + 0.6912003846756e-09, 0.4253296276335e+01, 0.1679936946371e+03, + 0.6871814664847e-09, 0.5148072412354e+01, 0.6053048899753e+02, + 0.4887158016343e-09, 0.2153581148294e+01, 0.9656299901946e+02, + 0.5161802866314e-09, 0.3852750634351e+01, 0.2442876000072e+03, + 0.5652599559057e-09, 0.1233233356270e+01, 0.8365903305582e+02, + 0.4710812608586e-09, 0.5610486976767e+01, 0.3164282286739e+03, + 0.4909977500324e-09, 0.1639629524123e+01, 0.4059982187939e+03, + 0.4772641839378e-09, 0.3737100368583e+01, 0.1805255418145e+03, + 0.4487562567153e-09, 0.1158417054478e+00, 0.8433466158131e+02, + + 0.3943441230497e-09, 0.6243502862796e+00, 0.2568537517081e+03, + 0.3952236913598e-09, 0.3510377382385e+01, 0.2449975330562e+03, + 0.3788898363417e-09, 0.5916128302299e+01, 0.1568131045107e+03, + 0.3738329328831e-09, 0.1042266763456e+01, 0.3948519331910e+03, + 0.2451199165151e-09, 0.1166788435700e+01, 0.1435713242844e+03, + 0.2436734402904e-09, 0.3254726114901e+01, 0.2268582385539e+03, + 0.2213605274325e-09, 0.1687210598530e+01, 0.1658638954901e+03, + 0.1491521204829e-09, 0.2657541786794e+01, 0.2219950288015e+03, + 0.1474995329744e-09, 0.5013089805819e+01, 0.3052819430710e+03, + 0.1661939475656e-09, 0.5495315428418e+01, 0.2526661704812e+03, + + 0.9015946748003e-10, 0.2236989966505e+01, 0.4171445043968e+03 }; + +/* Sun-to-Earth, T^0, Z */ + static const double e0z[] = { + 0.2796207639075e-05, 0.3198701560209e+01, 0.8433466158131e+02, + 0.1016042198142e-05, 0.5422360395913e+01, 0.5507553240374e+01, + 0.8044305033647e-06, 0.3880222866652e+01, 0.5223693906222e+01, + 0.4385347909274e-06, 0.3704369937468e+01, 0.2352866153506e+01, + 0.3186156414906e-06, 0.3999639363235e+01, 0.1577343543434e+01, + 0.2272412285792e-06, 0.3984738315952e+01, 0.1047747311755e+01, + 0.1645620103007e-06, 0.3565412516841e+01, 0.5856477690889e+01, + 0.1815836921166e-06, 0.4984507059020e+01, 0.6283075850446e+01, + 0.1447461676364e-06, 0.3702753570108e+01, 0.9437762937313e+01, + 0.1430760876382e-06, 0.3409658712357e+01, 0.1021328554739e+02, + + 0.1120445753226e-06, 0.4829561570246e+01, 0.1414349524433e+02, + 0.1090232840797e-06, 0.2080729178066e+01, 0.6812766822558e+01, + 0.9715727346551e-07, 0.3476295881948e+01, 0.4694002934110e+01, + 0.1036267136217e-06, 0.4056639536648e+01, 0.7109288135493e+02, + 0.8752665271340e-07, 0.4448159519911e+01, 0.5753384878334e+01, + 0.8331864956004e-07, 0.4991704044208e+01, 0.7084896783808e+01, + 0.6901658670245e-07, 0.4325358994219e+01, 0.6275962395778e+01, + 0.9144536848998e-07, 0.1141826375363e+01, 0.6620890113188e+01, + 0.7205085037435e-07, 0.3624344170143e+01, 0.5296909721118e+00, + 0.7697874654176e-07, 0.5554257458998e+01, 0.1676215758509e+03, + + 0.5197545738384e-07, 0.6251760961735e+01, 0.1807370494127e+02, + 0.5031345378608e-07, 0.2497341091913e+01, 0.4705732307012e+01, + 0.4527110205840e-07, 0.2335079920992e+01, 0.6309374173736e+01, + 0.4753355798089e-07, 0.7094148987474e+00, 0.5884926831456e+01, + 0.4296951977516e-07, 0.1101916352091e+01, 0.6681224869435e+01, + 0.3855341568387e-07, 0.1825495405486e+01, 0.5486777812467e+01, + 0.5253930970990e-07, 0.4424740687208e+01, 0.7860419393880e+01, + 0.4024630496471e-07, 0.5120498157053e+01, 0.1336797263425e+02, + 0.4061069791453e-07, 0.6029771435451e+01, 0.3930209696940e+01, + 0.3797883804205e-07, 0.4435193600836e+00, 0.3154687086868e+01, + + 0.2933033225587e-07, 0.5124157356507e+01, 0.1059381944224e+01, + 0.3503000930426e-07, 0.5421830162065e+01, 0.6069776770667e+01, + 0.3670096214050e-07, 0.4582101667297e+01, 0.1219403291462e+02, + 0.2905609437008e-07, 0.1926566420072e+01, 0.1097707878456e+02, + 0.2466827821713e-07, 0.6090174539834e+00, 0.6496374930224e+01, + 0.2691647295332e-07, 0.1393432595077e+01, 0.2200391463820e+02, + 0.2150554667946e-07, 0.4308671715951e+01, 0.5643178611111e+01, + 0.2237481922680e-07, 0.8133968269414e+00, 0.8635942003952e+01, + 0.1817741038157e-07, 0.3755205127454e+01, 0.3340612434717e+01, + 0.2227820762132e-07, 0.2759558596664e+01, 0.1203646072878e+02, + + 0.1944713772307e-07, 0.5699645869121e+01, 0.1179062909082e+02, + 0.1527340520662e-07, 0.1986749091746e+01, 0.3981490189893e+00, + 0.1577282574914e-07, 0.3205017217983e+01, 0.5088628793478e+01, + 0.1424738825424e-07, 0.6256747903666e+01, 0.2544314396739e+01, + 0.1616563121701e-07, 0.2601671259394e+00, 0.1729818233119e+02, + 0.1401210391692e-07, 0.4686939173506e+01, 0.7058598460518e+01, + 0.1488726974214e-07, 0.2815862451372e+01, 0.2593412433514e+02, + 0.1692626442388e-07, 0.4956894109797e+01, 0.1564752902480e+03, + 0.1123571582910e-07, 0.2381192697696e+01, 0.3738761453707e+01, + 0.9903308606317e-08, 0.4294851657684e+01, 0.9225539266174e+01, + + 0.9174533187191e-08, 0.3075171510642e+01, 0.4164311961999e+01, + 0.8645985631457e-08, 0.5477534821633e+00, 0.8429241228195e+01, + -0.1085876492688e-07, 0.0000000000000e+00, 0.0000000000000e+00, + 0.9264309077815e-08, 0.5968571670097e+01, 0.7079373888424e+01, + 0.8243116984954e-08, 0.1489098777643e+01, 0.1044738781244e+02, + 0.8268102113708e-08, 0.3512977691983e+01, 0.1150676975667e+02, + 0.9043613988227e-08, 0.1290704408221e+00, 0.1101510648075e+02, + 0.7432912038789e-08, 0.1991086893337e+01, 0.2608790314060e+02, + 0.8586233727285e-08, 0.4238357924414e+01, 0.2986433403208e+02, + 0.7612230060131e-08, 0.2911090150166e+01, 0.4732030630302e+01, + + 0.7097787751408e-08, 0.1908938392390e+01, 0.8031092209206e+01, + 0.7640237040175e-08, 0.6129219000168e+00, 0.7962980379786e+00, + 0.7070445688081e-08, 0.1380417036651e+01, 0.2146165377750e+01, + 0.7690770957702e-08, 0.1680504249084e+01, 0.2122839202813e+02, + 0.8051292542594e-08, 0.5127423484511e+01, 0.2942463415728e+01, + 0.5902709104515e-08, 0.2020274190917e+01, 0.7755226100720e+00, + 0.5134567496462e-08, 0.2606778676418e+01, 0.1256615170089e+02, + 0.5525802046102e-08, 0.1613011769663e+01, 0.8018209333619e+00, + 0.5880724784221e-08, 0.4604483417236e+01, 0.4690479774488e+01, + 0.5211699081370e-08, 0.5718964114193e+01, 0.8827390247185e+01, + + 0.4891849573562e-08, 0.3689658932196e+01, 0.2132990797783e+00, + 0.5150246069997e-08, 0.4099769855122e+01, 0.6480980550449e+02, + 0.5102434319633e-08, 0.5660834602509e+01, 0.3379454372902e+02, + 0.5083405254252e-08, 0.9842221218974e+00, 0.4136910472696e+01, + 0.4206562585682e-08, 0.1341363634163e+00, 0.3128388763578e+01, + 0.4663249683579e-08, 0.8130132735866e+00, 0.5216580451554e+01, + 0.4099474416530e-08, 0.5791497770644e+01, 0.4265981595566e+00, + 0.4628251220767e-08, 0.1249802769331e+01, 0.1572083878776e+02, + 0.5024068728142e-08, 0.4795684802743e+01, 0.6290189305114e+01, + 0.5120234327758e-08, 0.3810420387208e+01, 0.5230807360890e+01, + + 0.5524029815280e-08, 0.1029264714351e+01, 0.2397622045175e+03, + 0.4757415718860e-08, 0.3528044781779e+01, 0.1649636139783e+02, + 0.3915786131127e-08, 0.5593889282646e+01, 0.1589072916335e+01, + 0.4869053149991e-08, 0.3299636454433e+01, 0.7632943190217e+01, + 0.3649365703729e-08, 0.1286049002584e+01, 0.6206810014183e+01, + 0.3992493949002e-08, 0.3100307589464e+01, 0.2515860172507e+02, + 0.3320247477418e-08, 0.6212683940807e+01, 0.1216800268190e+02, + 0.3287123739696e-08, 0.4699118445928e+01, 0.7234794171227e+01, + 0.3472776811103e-08, 0.2630507142004e+01, 0.7342457794669e+01, + 0.3423253294767e-08, 0.2946432844305e+01, 0.9623688285163e+01, + + 0.3896173898244e-08, 0.1224834179264e+01, 0.6438496133249e+01, + 0.3388455337924e-08, 0.1543807616351e+01, 0.1494531617769e+02, + 0.3062704716523e-08, 0.1191777572310e+01, 0.8662240327241e+01, + 0.3270075600400e-08, 0.5483498767737e+01, 0.1194447056968e+01, + 0.3101209215259e-08, 0.8000833804348e+00, 0.3772475342596e+02, + 0.2780883347311e-08, 0.4077980721888e+00, 0.5863591145557e+01, + 0.2903605931824e-08, 0.2617490302147e+01, 0.1965104848470e+02, + 0.2682014743119e-08, 0.2634703158290e+01, 0.7238675589263e+01, + 0.2534360108492e-08, 0.6102446114873e+01, 0.6836645152238e+01, + 0.2392564882509e-08, 0.3681820208691e+01, 0.5849364236221e+01, + + 0.2656667254856e-08, 0.6216045388886e+01, 0.6133512519065e+01, + 0.2331242096773e-08, 0.5864949777744e+01, 0.4535059491685e+01, + 0.2287898363668e-08, 0.4566628532802e+01, 0.7477522907414e+01, + 0.2336944521306e-08, 0.2442722126930e+01, 0.1137170464392e+02, + 0.3156632236269e-08, 0.1626628050682e+01, 0.2509084901204e+03, + 0.2982612402766e-08, 0.2803604512609e+01, 0.1748016358760e+01, + 0.2774031674807e-08, 0.4654002897158e+01, 0.8223916695780e+02, + 0.2295236548638e-08, 0.4326518333253e+01, 0.3378142627421e+00, + 0.2190714699873e-08, 0.4519614578328e+01, 0.2908881142201e+02, + 0.2191495845045e-08, 0.3012626912549e+01, 0.1673046366289e+02, + + 0.2492901628386e-08, 0.1290101424052e+00, 0.1543797956245e+03, + 0.1993778064319e-08, 0.3864046799414e+01, 0.1778984560711e+02, + 0.1898146479022e-08, 0.5053777235891e+01, 0.2042657109477e+02, + 0.1918280127634e-08, 0.2222470192548e+01, 0.4165496312290e+02, + 0.1916351061607e-08, 0.8719067257774e+00, 0.7737595720538e+02, + 0.1834720181466e-08, 0.4031491098040e+01, 0.2358125818164e+02, + 0.1249201523806e-08, 0.5938379466835e+01, 0.3301902111895e+02, + 0.1477304050539e-08, 0.6544722606797e+00, 0.9548094718417e+02, + 0.1264316431249e-08, 0.2059072853236e+01, 0.8399684731857e+02, + 0.1203526495039e-08, 0.3644813532605e+01, 0.4558517281984e+02, + + 0.9221681059831e-09, 0.3241815055602e+01, 0.7805158573086e+02, + 0.7849278367646e-09, 0.5043812342457e+01, 0.5217580628120e+02, + 0.7983392077387e-09, 0.5000024502753e+01, 0.1501922143975e+03, + 0.7925395431654e-09, 0.1398734871821e-01, 0.9061773743175e+02, + 0.7640473285886e-09, 0.5067111723130e+01, 0.4951538251678e+02, + 0.5398937754482e-09, 0.5597382200075e+01, 0.1613385000004e+03, + 0.5626247550193e-09, 0.2601338209422e+01, 0.7318837597844e+02, + 0.5525197197855e-09, 0.5814832109256e+01, 0.1432335100216e+03, + 0.5407629837898e-09, 0.3384820609076e+01, 0.3230491187871e+03, + 0.3856739119801e-09, 0.1072391840473e+01, 0.2334791286671e+03, + + 0.3856425239987e-09, 0.2369540393327e+01, 0.1739046517013e+03, + 0.4350867755983e-09, 0.5255575751082e+01, 0.1620484330494e+03, + 0.3844113924996e-09, 0.5482356246182e+01, 0.9757644180768e+02, + 0.2854869155431e-09, 0.9573634763143e+00, 0.1697170704744e+03, + 0.1719227671416e-09, 0.1887203025202e+01, 0.2265204242912e+03, + 0.1527846879755e-09, 0.3982183931157e+01, 0.3341954043900e+03, + 0.1128229264847e-09, 0.2787457156298e+01, 0.3119028331842e+03 }; + +/* Sun-to-Earth, T^1, X */ + static const double e1x[] = { + 0.1234046326004e-05, 0.0000000000000e+00, 0.0000000000000e+00, + 0.5150068824701e-06, 0.6002664557501e+01, 0.1256615170089e+02, + 0.1290743923245e-07, 0.5959437664199e+01, 0.1884922755134e+02, + 0.1068615564952e-07, 0.2015529654209e+01, 0.6283075850446e+01, + 0.2079619142538e-08, 0.1732960531432e+01, 0.6279552690824e+01, + 0.2078009243969e-08, 0.4915604476996e+01, 0.6286599010068e+01, + 0.6206330058856e-09, 0.3616457953824e+00, 0.4705732307012e+01, + 0.5989335313746e-09, 0.3802607304474e+01, 0.6256777527156e+01, + 0.5958495663840e-09, 0.2845866560031e+01, 0.6309374173736e+01, + 0.4866923261539e-09, 0.5213203771824e+01, 0.7755226100720e+00, + + 0.4267785823142e-09, 0.4368189727818e+00, 0.1059381944224e+01, + 0.4610675141648e-09, 0.1837249181372e-01, 0.7860419393880e+01, + 0.3626989993973e-09, 0.2161590545326e+01, 0.5753384878334e+01, + 0.3563071194389e-09, 0.1452631954746e+01, 0.5884926831456e+01, + 0.3557015642807e-09, 0.4470593393054e+01, 0.6812766822558e+01, + 0.3210412089122e-09, 0.5195926078314e+01, 0.6681224869435e+01, + 0.2875473577986e-09, 0.5916256610193e+01, 0.2513230340178e+02, + 0.2842913681629e-09, 0.1149902426047e+01, 0.6127655567643e+01, + 0.2751248215916e-09, 0.5502088574662e+01, 0.6438496133249e+01, + 0.2481432881127e-09, 0.2921989846637e+01, 0.5486777812467e+01, + + 0.2059885976560e-09, 0.3718070376585e+01, 0.7079373888424e+01, + 0.2015522342591e-09, 0.5979395259740e+01, 0.6290189305114e+01, + 0.1995364084253e-09, 0.6772087985494e+00, 0.6275962395778e+01, + 0.1957436436943e-09, 0.2899210654665e+01, 0.5507553240374e+01, + 0.1651609818948e-09, 0.6228206482192e+01, 0.1150676975667e+02, + 0.1822980550699e-09, 0.1469348746179e+01, 0.1179062909082e+02, + 0.1675223159760e-09, 0.3813910555688e+01, 0.7058598460518e+01, + 0.1706491764745e-09, 0.3004380506684e+00, 0.7113454667900e-02, + 0.1392952362615e-09, 0.1440393973406e+01, 0.7962980379786e+00, + 0.1209868266342e-09, 0.4150425791727e+01, 0.4694002934110e+01, + + 0.1009827202611e-09, 0.3290040429843e+01, 0.3738761453707e+01, + 0.1047261388602e-09, 0.4229590090227e+01, 0.6282095334605e+01, + 0.1047006652004e-09, 0.2418967680575e+01, 0.6284056366286e+01, + 0.9609993143095e-10, 0.4627943659201e+01, 0.6069776770667e+01, + 0.9590900593873e-10, 0.1894393939924e+01, 0.4136910472696e+01, + 0.9146249188071e-10, 0.2010647519562e+01, 0.6496374930224e+01, + 0.8545274480290e-10, 0.5529846956226e-01, 0.1194447056968e+01, + 0.8224377881194e-10, 0.1254304102174e+01, 0.1589072916335e+01, + 0.6183529510410e-10, 0.3360862168815e+01, 0.8827390247185e+01, + 0.6259255147141e-10, 0.4755628243179e+01, 0.8429241228195e+01, + + 0.5539291694151e-10, 0.5371746955142e+01, 0.4933208510675e+01, + 0.7328259466314e-10, 0.4927699613906e+00, 0.4535059491685e+01, + 0.6017835843560e-10, 0.5776682001734e-01, 0.1255903824622e+02, + 0.7079827775243e-10, 0.4395059432251e+01, 0.5088628793478e+01, + 0.5170358878213e-10, 0.5154062619954e+01, 0.1176985366291e+02, + 0.4872301838682e-10, 0.6289611648973e+00, 0.6040347114260e+01, + 0.5249869411058e-10, 0.5617272046949e+01, 0.3154687086868e+01, + 0.4716172354411e-10, 0.3965901800877e+01, 0.5331357529664e+01, + 0.4871214940964e-10, 0.4627507050093e+01, 0.1256967486051e+02, + 0.4598076850751e-10, 0.6023631226459e+01, 0.6525804586632e+01, + + 0.4562196089485e-10, 0.4138562084068e+01, 0.3930209696940e+01, + 0.4325493872224e-10, 0.1330845906564e+01, 0.7632943190217e+01, + 0.5673781176748e-10, 0.2558752615657e+01, 0.5729506548653e+01, + 0.3961436642503e-10, 0.2728071734630e+01, 0.7234794171227e+01, + 0.5101868209058e-10, 0.4113444965144e+01, 0.6836645152238e+01, + 0.5257043167676e-10, 0.6195089830590e+01, 0.8031092209206e+01, + 0.5076613989393e-10, 0.2305124132918e+01, 0.7477522907414e+01, + 0.3342169352778e-10, 0.5415998155071e+01, 0.1097707878456e+02, + 0.3545881983591e-10, 0.3727160564574e+01, 0.4164311961999e+01, + 0.3364063738599e-10, 0.2901121049204e+00, 0.1137170464392e+02, + + 0.3357039670776e-10, 0.1652229354331e+01, 0.5223693906222e+01, + 0.4307412268687e-10, 0.4938909587445e+01, 0.1592596075957e+01, + 0.3405769115435e-10, 0.2408890766511e+01, 0.3128388763578e+01, + 0.3001926198480e-10, 0.4862239006386e+01, 0.1748016358760e+01, + 0.2778264787325e-10, 0.5241168661353e+01, 0.7342457794669e+01, + 0.2676159480666e-10, 0.3423593942199e+01, 0.2146165377750e+01, + 0.2954273399939e-10, 0.1881721265406e+01, 0.5368044267797e+00, + 0.3309362888795e-10, 0.1931525677349e+01, 0.8018209333619e+00, + 0.2810283608438e-10, 0.2414659495050e+01, 0.5225775174439e+00, + 0.3378045637764e-10, 0.4238019163430e+01, 0.1554202828031e+00, + + 0.2558134979840e-10, 0.1828225235805e+01, 0.5230807360890e+01, + 0.2273755578447e-10, 0.5858184283998e+01, 0.7084896783808e+01, + 0.2294176037690e-10, 0.4514589779057e+01, 0.1726015463500e+02, + 0.2533506099435e-10, 0.2355717851551e+01, 0.5216580451554e+01, + 0.2716685375812e-10, 0.2221003625100e+01, 0.8635942003952e+01, + 0.2419043435198e-10, 0.5955704951635e+01, 0.4690479774488e+01, + 0.2521232544812e-10, 0.1395676848521e+01, 0.5481254917084e+01, + 0.2630195021491e-10, 0.5727468918743e+01, 0.2629832328990e-01, + 0.2548395840944e-10, 0.2628351859400e-03, 0.1349867339771e+01 }; + +/* Sun-to-Earth, T^1, Y */ + static const double e1y[] = { + 0.9304690546528e-06, 0.0000000000000e+00, 0.0000000000000e+00, + 0.5150715570663e-06, 0.4431807116294e+01, 0.1256615170089e+02, + 0.1290825411056e-07, 0.4388610039678e+01, 0.1884922755134e+02, + 0.4645466665386e-08, 0.5827263376034e+01, 0.6283075850446e+01, + 0.2079625310718e-08, 0.1621698662282e+00, 0.6279552690824e+01, + 0.2078189850907e-08, 0.3344713435140e+01, 0.6286599010068e+01, + 0.6207190138027e-09, 0.5074049319576e+01, 0.4705732307012e+01, + 0.5989826532569e-09, 0.2231842216620e+01, 0.6256777527156e+01, + 0.5961360812618e-09, 0.1274975769045e+01, 0.6309374173736e+01, + 0.4874165471016e-09, 0.3642277426779e+01, 0.7755226100720e+00, + + 0.4283834034360e-09, 0.5148765510106e+01, 0.1059381944224e+01, + 0.4652389287529e-09, 0.4715794792175e+01, 0.7860419393880e+01, + 0.3751707476401e-09, 0.6617207370325e+00, 0.5753384878334e+01, + 0.3559998806198e-09, 0.6155548875404e+01, 0.5884926831456e+01, + 0.3558447558857e-09, 0.2898827297664e+01, 0.6812766822558e+01, + 0.3211116927106e-09, 0.3625813502509e+01, 0.6681224869435e+01, + 0.2875609914672e-09, 0.4345435813134e+01, 0.2513230340178e+02, + 0.2843109704069e-09, 0.5862263940038e+01, 0.6127655567643e+01, + 0.2744676468427e-09, 0.3926419475089e+01, 0.6438496133249e+01, + 0.2481285237789e-09, 0.1351976572828e+01, 0.5486777812467e+01, + + 0.2060338481033e-09, 0.2147556998591e+01, 0.7079373888424e+01, + 0.2015822358331e-09, 0.4408358972216e+01, 0.6290189305114e+01, + 0.2001195944195e-09, 0.5385829822531e+01, 0.6275962395778e+01, + 0.1953667642377e-09, 0.1304933746120e+01, 0.5507553240374e+01, + 0.1839744078713e-09, 0.6173567228835e+01, 0.1179062909082e+02, + 0.1643334294845e-09, 0.4635942997523e+01, 0.1150676975667e+02, + 0.1768051018652e-09, 0.5086283558874e+01, 0.7113454667900e-02, + 0.1674874205489e-09, 0.2243332137241e+01, 0.7058598460518e+01, + 0.1421445397609e-09, 0.6186899771515e+01, 0.7962980379786e+00, + 0.1255163958267e-09, 0.5730238465658e+01, 0.4694002934110e+01, + + 0.1013945281961e-09, 0.1726055228402e+01, 0.3738761453707e+01, + 0.1047294335852e-09, 0.2658801228129e+01, 0.6282095334605e+01, + 0.1047103879392e-09, 0.8481047835035e+00, 0.6284056366286e+01, + 0.9530343962826e-10, 0.3079267149859e+01, 0.6069776770667e+01, + 0.9604637611690e-10, 0.3258679792918e+00, 0.4136910472696e+01, + 0.9153518537177e-10, 0.4398599886584e+00, 0.6496374930224e+01, + 0.8562458214922e-10, 0.4772686794145e+01, 0.1194447056968e+01, + 0.8232525360654e-10, 0.5966220721679e+01, 0.1589072916335e+01, + 0.6150223411438e-10, 0.1780985591923e+01, 0.8827390247185e+01, + 0.6272087858000e-10, 0.3184305429012e+01, 0.8429241228195e+01, + + 0.5540476311040e-10, 0.3801260595433e+01, 0.4933208510675e+01, + 0.7331901699361e-10, 0.5205948591865e+01, 0.4535059491685e+01, + 0.6018528702791e-10, 0.4770139083623e+01, 0.1255903824622e+02, + 0.5150530724804e-10, 0.3574796899585e+01, 0.1176985366291e+02, + 0.6471933741811e-10, 0.2679787266521e+01, 0.5088628793478e+01, + 0.5317460644174e-10, 0.9528763345494e+00, 0.3154687086868e+01, + 0.4832187748783e-10, 0.5329322498232e+01, 0.6040347114260e+01, + 0.4716763555110e-10, 0.2395235316466e+01, 0.5331357529664e+01, + 0.4871509139861e-10, 0.3056663648823e+01, 0.1256967486051e+02, + 0.4598417696768e-10, 0.4452762609019e+01, 0.6525804586632e+01, + + 0.5674189533175e-10, 0.9879680872193e+00, 0.5729506548653e+01, + 0.4073560328195e-10, 0.5939127696986e+01, 0.7632943190217e+01, + 0.5040994945359e-10, 0.4549875824510e+01, 0.8031092209206e+01, + 0.5078185134679e-10, 0.7346659893982e+00, 0.7477522907414e+01, + 0.3769343537061e-10, 0.1071317188367e+01, 0.7234794171227e+01, + 0.4980331365299e-10, 0.2500345341784e+01, 0.6836645152238e+01, + 0.3458236594757e-10, 0.3825159450711e+01, 0.1097707878456e+02, + 0.3578859493602e-10, 0.5299664791549e+01, 0.4164311961999e+01, + 0.3370504646419e-10, 0.5002316301593e+01, 0.1137170464392e+02, + 0.3299873338428e-10, 0.2526123275282e+01, 0.3930209696940e+01, + + 0.4304917318409e-10, 0.3368078557132e+01, 0.1592596075957e+01, + 0.3402418753455e-10, 0.8385495425800e+00, 0.3128388763578e+01, + 0.2778460572146e-10, 0.3669905203240e+01, 0.7342457794669e+01, + 0.2782710128902e-10, 0.2691664812170e+00, 0.1748016358760e+01, + 0.2711725179646e-10, 0.4707487217718e+01, 0.5296909721118e+00, + 0.2981760946340e-10, 0.3190260867816e+00, 0.5368044267797e+00, + 0.2811672977772e-10, 0.3196532315372e+01, 0.7084896783808e+01, + 0.2863454474467e-10, 0.2263240324780e+00, 0.5223693906222e+01, + 0.3333464634051e-10, 0.3498451685065e+01, 0.8018209333619e+00, + 0.3312991747609e-10, 0.5839154477412e+01, 0.1554202828031e+00, + + 0.2813255564006e-10, 0.8268044346621e+00, 0.5225775174439e+00, + 0.2665098083966e-10, 0.3934021725360e+01, 0.5216580451554e+01, + 0.2349795705216e-10, 0.5197620913779e+01, 0.2146165377750e+01, + 0.2330352293961e-10, 0.2984999231807e+01, 0.1726015463500e+02, + 0.2728001683419e-10, 0.6521679638544e+00, 0.8635942003952e+01, + 0.2484061007669e-10, 0.3468955561097e+01, 0.5230807360890e+01, + 0.2646328768427e-10, 0.1013724533516e+01, 0.2629832328990e-01, + 0.2518630264831e-10, 0.6108081057122e+01, 0.5481254917084e+01, + 0.2421901455384e-10, 0.1651097776260e+01, 0.1349867339771e+01, + 0.6348533267831e-11, 0.3220226560321e+01, 0.8433466158131e+02 }; + +/* Sun-to-Earth, T^1, Z */ + static const double e1z[] = { + 0.2278290449966e-05, 0.3413716033863e+01, 0.6283075850446e+01, + 0.5429458209830e-07, 0.0000000000000e+00, 0.0000000000000e+00, + 0.1903240492525e-07, 0.3370592358297e+01, 0.1256615170089e+02, + 0.2385409276743e-09, 0.3327914718416e+01, 0.1884922755134e+02, + 0.8676928342573e-10, 0.1824006811264e+01, 0.5223693906222e+01, + 0.7765442593544e-10, 0.3888564279247e+01, 0.5507553240374e+01, + 0.7066158332715e-10, 0.5194267231944e+01, 0.2352866153506e+01, + 0.7092175288657e-10, 0.2333246960021e+01, 0.8399684731857e+02, + 0.5357582213535e-10, 0.2224031176619e+01, 0.5296909721118e+00, + 0.3828035865021e-10, 0.2156710933584e+01, 0.6279552690824e+01, + + 0.3824857220427e-10, 0.1529755219915e+01, 0.6286599010068e+01, + 0.3286995181628e-10, 0.4879512900483e+01, 0.1021328554739e+02 }; + +/* Sun-to-Earth, T^2, X */ + static const double e2x[] = { + -0.4143818297913e-10, 0.0000000000000e+00, 0.0000000000000e+00, + 0.2171497694435e-10, 0.4398225628264e+01, 0.1256615170089e+02, + 0.9845398442516e-11, 0.2079720838384e+00, 0.6283075850446e+01, + 0.9256833552682e-12, 0.4191264694361e+01, 0.1884922755134e+02, + 0.1022049384115e-12, 0.5381133195658e+01, 0.8399684731857e+02 }; + +/* Sun-to-Earth, T^2, Y */ + static const double e2y[] = { + 0.5063375872532e-10, 0.0000000000000e+00, 0.0000000000000e+00, + 0.2173815785980e-10, 0.2827805833053e+01, 0.1256615170089e+02, + 0.1010231999920e-10, 0.4634612377133e+01, 0.6283075850446e+01, + 0.9259745317636e-12, 0.2620612076189e+01, 0.1884922755134e+02, + 0.1022202095812e-12, 0.3809562326066e+01, 0.8399684731857e+02 }; + +/* Sun-to-Earth, T^2, Z */ + static const double e2z[] = { + 0.9722666114891e-10, 0.5152219582658e+01, 0.6283075850446e+01, + -0.3494819171909e-11, 0.0000000000000e+00, 0.0000000000000e+00, + 0.6713034376076e-12, 0.6440188750495e+00, 0.1256615170089e+02 }; + +/* SSB-to-Sun, T^0, X */ + static const double s0x[] = { + 0.4956757536410e-02, 0.3741073751789e+01, 0.5296909721118e+00, + 0.2718490072522e-02, 0.4016011511425e+01, 0.2132990797783e+00, + 0.1546493974344e-02, 0.2170528330642e+01, 0.3813291813120e-01, + 0.8366855276341e-03, 0.2339614075294e+01, 0.7478166569050e-01, + 0.2936777942117e-03, 0.0000000000000e+00, 0.0000000000000e+00, + 0.1201317439469e-03, 0.4090736353305e+01, 0.1059381944224e+01, + 0.7578550887230e-04, 0.3241518088140e+01, 0.4265981595566e+00, + 0.1941787367773e-04, 0.1012202064330e+01, 0.2061856251104e+00, + 0.1889227765991e-04, 0.3892520416440e+01, 0.2204125344462e+00, + 0.1937896968613e-04, 0.4797779441161e+01, 0.1495633313810e+00, + + 0.1434506110873e-04, 0.3868960697933e+01, 0.5225775174439e+00, + 0.1406659911580e-04, 0.4759766557397e+00, 0.5368044267797e+00, + 0.1179022300202e-04, 0.7774961520598e+00, 0.7626583626240e-01, + 0.8085864460959e-05, 0.3254654471465e+01, 0.3664874755930e-01, + 0.7622752967615e-05, 0.4227633103489e+01, 0.3961708870310e-01, + 0.6209171139066e-05, 0.2791828325711e+00, 0.7329749511860e-01, + 0.4366435633970e-05, 0.4440454875925e+01, 0.1589072916335e+01, + 0.3792124889348e-05, 0.5156393842356e+01, 0.7113454667900e-02, + 0.3154548963402e-05, 0.6157005730093e+01, 0.4194847048887e+00, + 0.3088359882942e-05, 0.2494567553163e+01, 0.6398972393349e+00, + + 0.2788440902136e-05, 0.4934318747989e+01, 0.1102062672231e+00, + 0.3039928456376e-05, 0.4895077702640e+01, 0.6283075850446e+01, + 0.2272258457679e-05, 0.5278394064764e+01, 0.1030928125552e+00, + 0.2162007057957e-05, 0.5802978019099e+01, 0.3163918923335e+00, + 0.1767632855737e-05, 0.3415346595193e-01, 0.1021328554739e+02, + 0.1349413459362e-05, 0.2001643230755e+01, 0.1484170571900e-02, + 0.1170141900476e-05, 0.2424750491620e+01, 0.6327837846670e+00, + 0.1054355266820e-05, 0.3123311487576e+01, 0.4337116142245e+00, + 0.9800822461610e-06, 0.3026258088130e+01, 0.1052268489556e+01, + 0.1091203749931e-05, 0.3157811670347e+01, 0.1162474756779e+01, + + 0.6960236715913e-06, 0.8219570542313e+00, 0.1066495398892e+01, + 0.5689257296909e-06, 0.1323052375236e+01, 0.9491756770005e+00, + 0.6613172135802e-06, 0.2765348881598e+00, 0.8460828644453e+00, + 0.6277702517571e-06, 0.5794064466382e+01, 0.1480791608091e+00, + 0.6304884066699e-06, 0.7323555380787e+00, 0.2243449970715e+00, + 0.4897850467382e-06, 0.3062464235399e+01, 0.3340612434717e+01, + 0.3759148598786e-06, 0.4588290469664e+01, 0.3516457698740e-01, + 0.3110520548195e-06, 0.1374299536572e+01, 0.6373574839730e-01, + 0.3064708359780e-06, 0.4222267485047e+01, 0.1104591729320e-01, + 0.2856347168241e-06, 0.3714202944973e+01, 0.1510475019529e+00, + + 0.2840945514288e-06, 0.2847972875882e+01, 0.4110125927500e-01, + 0.2378951599405e-06, 0.3762072563388e+01, 0.2275259891141e+00, + 0.2714229481417e-06, 0.1036049980031e+01, 0.2535050500000e-01, + 0.2323551717307e-06, 0.4682388599076e+00, 0.8582758298370e-01, + 0.1881790512219e-06, 0.4790565425418e+01, 0.2118763888447e+01, + 0.2261353968371e-06, 0.1669144912212e+01, 0.7181332454670e-01, + 0.2214546389848e-06, 0.3937717281614e+01, 0.2968341143800e-02, + 0.2184915594933e-06, 0.1129169845099e+00, 0.7775000683430e-01, + 0.2000164937936e-06, 0.4030009638488e+01, 0.2093666171530e+00, + 0.1966105136719e-06, 0.8745955786834e+00, 0.2172315424036e+00, + + 0.1904742332624e-06, 0.5919743598964e+01, 0.2022531624851e+00, + 0.1657399705031e-06, 0.2549141484884e+01, 0.7358765972222e+00, + 0.1574070533987e-06, 0.5277533020230e+01, 0.7429900518901e+00, + 0.1832261651039e-06, 0.3064688127777e+01, 0.3235053470014e+00, + 0.1733615346569e-06, 0.3011432799094e+01, 0.1385174140878e+00, + 0.1549124014496e-06, 0.4005569132359e+01, 0.5154640627760e+00, + 0.1637044713838e-06, 0.1831375966632e+01, 0.8531963191132e+00, + 0.1123420082383e-06, 0.1180270407578e+01, 0.1990721704425e+00, + 0.1083754165740e-06, 0.3414101320863e+00, 0.5439178814476e+00, + 0.1156638012655e-06, 0.6130479452594e+00, 0.5257585094865e+00, + + 0.1142548785134e-06, 0.3724761948846e+01, 0.5336234347371e+00, + 0.7921463895965e-07, 0.2435425589361e+01, 0.1478866649112e+01, + 0.7428600285231e-07, 0.3542144398753e+01, 0.2164800718209e+00, + 0.8323211246747e-07, 0.3525058072354e+01, 0.1692165728891e+01, + 0.7257595116312e-07, 0.1364299431982e+01, 0.2101180877357e+00, + 0.7111185833236e-07, 0.2460478875808e+01, 0.4155522422634e+00, + 0.6868090383716e-07, 0.4397327670704e+01, 0.1173197218910e+00, + 0.7226419974175e-07, 0.4042647308905e+01, 0.1265567569334e+01, + 0.6955642383177e-07, 0.2865047906085e+01, 0.9562891316684e+00, + 0.7492139296331e-07, 0.5014278994215e+01, 0.1422690933580e-01, + + 0.6598363128857e-07, 0.2376730020492e+01, 0.6470106940028e+00, + 0.7381147293385e-07, 0.3272990384244e+01, 0.1581959461667e+01, + 0.6402909624032e-07, 0.5302290955138e+01, 0.9597935788730e-01, + 0.6237454263857e-07, 0.5444144425332e+01, 0.7084920306520e-01, + 0.5241198544016e-07, 0.4215359579205e+01, 0.5265099800692e+00, + 0.5144463853918e-07, 0.1218916689916e+00, 0.5328719641544e+00, + 0.5868164772299e-07, 0.2369402002213e+01, 0.7871412831580e-01, + 0.6233195669151e-07, 0.1254922242403e+01, 0.2608790314060e+02, + 0.6068463791422e-07, 0.5679713760431e+01, 0.1114304132498e+00, + 0.4359361135065e-07, 0.6097219641646e+00, 0.1375773836557e+01, + + 0.4686510366826e-07, 0.4786231041431e+01, 0.1143987543936e+00, + 0.3758977287225e-07, 0.1167368068139e+01, 0.1596186371003e+01, + 0.4282051974778e-07, 0.1519471064319e+01, 0.2770348281756e+00, + 0.5153765386113e-07, 0.1860532322984e+01, 0.2228608264996e+00, + 0.4575129387188e-07, 0.7632857887158e+00, 0.1465949902372e+00, + 0.3326844933286e-07, 0.1298219485285e+01, 0.5070101000000e-01, + 0.3748617450984e-07, 0.1046510321062e+01, 0.4903339079539e+00, + 0.2816756661499e-07, 0.3434522346190e+01, 0.2991266627620e+00, + 0.3412750405039e-07, 0.2523766270318e+01, 0.3518164938661e+00, + 0.2655796761776e-07, 0.2904422260194e+01, 0.6256703299991e+00, + + 0.2963597929458e-07, 0.5923900431149e+00, 0.1099462426779e+00, + 0.2539523734781e-07, 0.4851947722567e+01, 0.1256615170089e+02, + 0.2283087914139e-07, 0.3400498595496e+01, 0.6681224869435e+01, + 0.2321309799331e-07, 0.5789099148673e+01, 0.3368040641550e-01, + 0.2549657649750e-07, 0.3991856479792e-01, 0.1169588211447e+01, + 0.2290462303977e-07, 0.2788567577052e+01, 0.1045155034888e+01, + 0.1945398522914e-07, 0.3290896998176e+01, 0.1155361302111e+01, + 0.1849171512638e-07, 0.2698060129367e+01, 0.4452511715700e-02, + 0.1647199834254e-07, 0.3016735644085e+01, 0.4408250688924e+00, + 0.1529530765273e-07, 0.5573043116178e+01, 0.6521991896920e-01, + + 0.1433199339978e-07, 0.1481192356147e+01, 0.9420622223326e+00, + 0.1729134193602e-07, 0.1422817538933e+01, 0.2108507877249e+00, + 0.1716463931346e-07, 0.3469468901855e+01, 0.2157473718317e+00, + 0.1391206061378e-07, 0.6122436220547e+01, 0.4123712502208e+00, + 0.1404746661924e-07, 0.1647765641936e+01, 0.4258542984690e-01, + 0.1410452399455e-07, 0.5989729161964e+01, 0.2258291676434e+00, + 0.1089828772168e-07, 0.2833705509371e+01, 0.4226656969313e+00, + 0.1047374564948e-07, 0.5090690007331e+00, 0.3092784376656e+00, + 0.1358279126532e-07, 0.5128990262836e+01, 0.7923417740620e-01, + 0.1020456476148e-07, 0.9632772880808e+00, 0.1456308687557e+00, + + 0.1033428735328e-07, 0.3223779318418e+01, 0.1795258541446e+01, + 0.1412435841540e-07, 0.2410271572721e+01, 0.1525316725248e+00, + 0.9722759371574e-08, 0.2333531395690e+01, 0.8434341241180e-01, + 0.9657334084704e-08, 0.6199270974168e+01, 0.1272681024002e+01, + 0.1083641148690e-07, 0.2864222292929e+01, 0.7032915397480e-01, + 0.1067318403838e-07, 0.5833458866568e+00, 0.2123349582968e+00, + 0.1062366201976e-07, 0.4307753989494e+01, 0.2142632012598e+00, + 0.1236364149266e-07, 0.2873917870593e+01, 0.1847279083684e+00, + 0.1092759489593e-07, 0.2959887266733e+01, 0.1370332435159e+00, + 0.8912069362899e-08, 0.5141213702562e+01, 0.2648454860559e+01, + + 0.9656467707970e-08, 0.4532182462323e+01, 0.4376440768498e+00, + 0.8098386150135e-08, 0.2268906338379e+01, 0.2880807454688e+00, + 0.7857714675000e-08, 0.4055544260745e+01, 0.2037373330570e+00, + 0.7288455940646e-08, 0.5357901655142e+01, 0.1129145838217e+00, + 0.9450595950552e-08, 0.4264926963939e+01, 0.5272426800584e+00, + 0.9381718247537e-08, 0.7489366976576e-01, 0.5321392641652e+00, + 0.7079052646038e-08, 0.1923311052874e+01, 0.6288513220417e+00, + 0.9259004415344e-08, 0.2970256853438e+01, 0.1606092486742e+00, + 0.8259801499742e-08, 0.3327056314697e+01, 0.8389694097774e+00, + 0.6476334355779e-08, 0.2954925505727e+01, 0.2008557621224e+01, + + 0.5984021492007e-08, 0.9138753105829e+00, 0.2042657109477e+02, + 0.5989546863181e-08, 0.3244464082031e+01, 0.2111650433779e+01, + 0.6233108606023e-08, 0.4995232638403e+00, 0.4305306221819e+00, + 0.6877299149965e-08, 0.2834987233449e+01, 0.9561746721300e-02, + 0.8311234227190e-08, 0.2202951835758e+01, 0.3801276407308e+00, + 0.6599472832414e-08, 0.4478581462618e+01, 0.1063314406849e+01, + 0.6160491096549e-08, 0.5145858696411e+01, 0.1368660381889e+01, + 0.6164772043891e-08, 0.3762976697911e+00, 0.4234171675140e+00, + 0.6363248684450e-08, 0.3162246718685e+01, 0.1253008786510e-01, + 0.6448587520999e-08, 0.3442693302119e+01, 0.5287268506303e+00, + + 0.6431662283977e-08, 0.8977549136606e+00, 0.5306550935933e+00, + 0.6351223158474e-08, 0.4306447410369e+01, 0.5217580628120e+02, + 0.5476721393451e-08, 0.3888529177855e+01, 0.2221856701002e+01, + 0.5341772572619e-08, 0.2655560662512e+01, 0.7466759693650e-01, + 0.5337055758302e-08, 0.5164990735946e+01, 0.7489573444450e-01, + 0.5373120816787e-08, 0.6041214553456e+01, 0.1274714967946e+00, + 0.5392351705426e-08, 0.9177763485932e+00, 0.1055449481598e+01, + 0.6688495850205e-08, 0.3089608126937e+01, 0.2213766559277e+00, + 0.5072003660362e-08, 0.4311316541553e+01, 0.2132517061319e+00, + 0.5070726650455e-08, 0.5790675464444e+00, 0.2133464534247e+00, + + 0.5658012950032e-08, 0.2703945510675e+01, 0.7287631425543e+00, + 0.4835509924854e-08, 0.2975422976065e+01, 0.7160067364790e-01, + 0.6479821978012e-08, 0.1324168733114e+01, 0.2209183458640e-01, + 0.6230636494980e-08, 0.2860103632836e+01, 0.3306188016693e+00, + 0.4649239516213e-08, 0.4832259763403e+01, 0.7796265773310e-01, + 0.6487325792700e-08, 0.2726165825042e+01, 0.3884652414254e+00, + 0.4682823682770e-08, 0.6966602455408e+00, 0.1073608853559e+01, + 0.5704230804976e-08, 0.5669634104606e+01, 0.8731175355560e-01, + 0.6125413585489e-08, 0.1513386538915e+01, 0.7605151500000e-01, + 0.6035825038187e-08, 0.1983509168227e+01, 0.9846002785331e+00, + + 0.4331123462303e-08, 0.2782892992807e+01, 0.4297791515992e+00, + 0.4681107685143e-08, 0.5337232886836e+01, 0.2127790306879e+00, + 0.4669105829655e-08, 0.5837133792160e+01, 0.2138191288687e+00, + 0.5138823602365e-08, 0.3080560200507e+01, 0.7233337363710e-01, + 0.4615856664534e-08, 0.1661747897471e+01, 0.8603097737811e+00, + 0.4496916702197e-08, 0.2112508027068e+01, 0.7381754420900e-01, + 0.4278479042945e-08, 0.5716528462627e+01, 0.7574578717200e-01, + 0.3840525503932e-08, 0.6424172726492e+00, 0.3407705765729e+00, + 0.4866636509685e-08, 0.4919244697715e+01, 0.7722995774390e-01, + 0.3526100639296e-08, 0.2550821052734e+01, 0.6225157782540e-01, + + 0.3939558488075e-08, 0.3939331491710e+01, 0.5268983110410e-01, + 0.4041268772576e-08, 0.2275337571218e+01, 0.3503323232942e+00, + 0.3948761842853e-08, 0.1999324200790e+01, 0.1451108196653e+00, + 0.3258394550029e-08, 0.9121001378200e+00, 0.5296435984654e+00, + 0.3257897048761e-08, 0.3428428660869e+01, 0.5297383457582e+00, + 0.3842559031298e-08, 0.6132927720035e+01, 0.9098186128426e+00, + 0.3109920095448e-08, 0.7693650193003e+00, 0.3932462625300e-02, + 0.3132237775119e-08, 0.3621293854908e+01, 0.2346394437820e+00, + 0.3942189421510e-08, 0.4841863659733e+01, 0.3180992042600e-02, + 0.3796972285340e-08, 0.1814174994268e+01, 0.1862120789403e+00, + + 0.3995640233688e-08, 0.1386990406091e+01, 0.4549093064213e+00, + 0.2875013727414e-08, 0.9178318587177e+00, 0.1905464808669e+01, + 0.3073719932844e-08, 0.2688923811835e+01, 0.3628624111593e+00, + 0.2731016580075e-08, 0.1188259127584e+01, 0.2131850110243e+00, + 0.2729549896546e-08, 0.3702160634273e+01, 0.2134131485323e+00, + 0.3339372892449e-08, 0.7199163960331e+00, 0.2007689919132e+00, + 0.2898833764204e-08, 0.1916709364999e+01, 0.5291709230214e+00, + 0.2894536549362e-08, 0.2424043195547e+01, 0.5302110212022e+00, + 0.3096872473843e-08, 0.4445894977497e+01, 0.2976424921901e+00, + 0.2635672326810e-08, 0.3814366984117e+01, 0.1485980103780e+01, + + 0.3649302697001e-08, 0.2924200596084e+01, 0.6044726378023e+00, + 0.3127954585895e-08, 0.1842251648327e+01, 0.1084620721060e+00, + 0.2616040173947e-08, 0.4155841921984e+01, 0.1258454114666e+01, + 0.2597395859860e-08, 0.1158045978874e+00, 0.2103781122809e+00, + 0.2593286172210e-08, 0.4771850408691e+01, 0.2162200472757e+00, + 0.2481823585747e-08, 0.4608842558889e+00, 0.1062562936266e+01, + 0.2742219550725e-08, 0.1538781127028e+01, 0.5651155736444e+00, + 0.3199558469610e-08, 0.3226647822878e+00, 0.7036329877322e+00, + 0.2666088542957e-08, 0.1967991731219e+00, 0.1400015846597e+00, + 0.2397067430580e-08, 0.3707036669873e+01, 0.2125476091956e+00, + + 0.2376570772738e-08, 0.1182086628042e+01, 0.2140505503610e+00, + 0.2547228007887e-08, 0.4906256820629e+01, 0.1534957940063e+00, + 0.2265575594114e-08, 0.3414949866857e+01, 0.2235935264888e+00, + 0.2464381430585e-08, 0.4599122275378e+01, 0.2091065926078e+00, + 0.2433408527044e-08, 0.2830751145445e+00, 0.2174915669488e+00, + 0.2443605509076e-08, 0.4212046432538e+01, 0.1739420156204e+00, + 0.2319779262465e-08, 0.9881978408630e+00, 0.7530171478090e-01, + 0.2284622835465e-08, 0.5565347331588e+00, 0.7426161660010e-01, + 0.2467268750783e-08, 0.5655708150766e+00, 0.2526561439362e+00, + 0.2808513492782e-08, 0.1418405053408e+01, 0.5636314030725e+00, + + 0.2329528932532e-08, 0.4069557545675e+01, 0.1056200952181e+01, + 0.9698639532817e-09, 0.1074134313634e+01, 0.7826370942180e+02 }; + +/* SSB-to-Sun, T^0, Y */ + static const double s0y[] = { + 0.4955392320126e-02, 0.2170467313679e+01, 0.5296909721118e+00, + 0.2722325167392e-02, 0.2444433682196e+01, 0.2132990797783e+00, + 0.1546579925346e-02, 0.5992779281546e+00, 0.3813291813120e-01, + 0.8363140252966e-03, 0.7687356310801e+00, 0.7478166569050e-01, + 0.3385792683603e-03, 0.0000000000000e+00, 0.0000000000000e+00, + 0.1201192221613e-03, 0.2520035601514e+01, 0.1059381944224e+01, + 0.7587125720554e-04, 0.1669954006449e+01, 0.4265981595566e+00, + 0.1964155361250e-04, 0.5707743963343e+01, 0.2061856251104e+00, + 0.1891900364909e-04, 0.2320960679937e+01, 0.2204125344462e+00, + 0.1937373433356e-04, 0.3226940689555e+01, 0.1495633313810e+00, + + 0.1437139941351e-04, 0.2301626908096e+01, 0.5225775174439e+00, + 0.1406267683099e-04, 0.5188579265542e+01, 0.5368044267797e+00, + 0.1178703080346e-04, 0.5489483248476e+01, 0.7626583626240e-01, + 0.8079835186041e-05, 0.1683751835264e+01, 0.3664874755930e-01, + 0.7623253594652e-05, 0.2656400462961e+01, 0.3961708870310e-01, + 0.6248667483971e-05, 0.4992775362055e+01, 0.7329749511860e-01, + 0.4366353695038e-05, 0.2869706279678e+01, 0.1589072916335e+01, + 0.3829101568895e-05, 0.3572131359950e+01, 0.7113454667900e-02, + 0.3175733773908e-05, 0.4535372530045e+01, 0.4194847048887e+00, + 0.3092437902159e-05, 0.9230153317909e+00, 0.6398972393349e+00, + + 0.2874168812154e-05, 0.3363143761101e+01, 0.1102062672231e+00, + 0.3040119321826e-05, 0.3324250895675e+01, 0.6283075850446e+01, + 0.2699723308006e-05, 0.2917882441928e+00, 0.1030928125552e+00, + 0.2134832683534e-05, 0.4220997202487e+01, 0.3163918923335e+00, + 0.1770412139433e-05, 0.4747318496462e+01, 0.1021328554739e+02, + 0.1377264209373e-05, 0.4305058462401e+00, 0.1484170571900e-02, + 0.1127814538960e-05, 0.8538177240740e+00, 0.6327837846670e+00, + 0.1055608090130e-05, 0.1551800742580e+01, 0.4337116142245e+00, + 0.9802673861420e-06, 0.1459646735377e+01, 0.1052268489556e+01, + 0.1090329461951e-05, 0.1587351228711e+01, 0.1162474756779e+01, + + 0.6959590025090e-06, 0.5534442628766e+01, 0.1066495398892e+01, + 0.5664914529542e-06, 0.6030673003297e+01, 0.9491756770005e+00, + 0.6607787763599e-06, 0.4989507233927e+01, 0.8460828644453e+00, + 0.6269725742838e-06, 0.4222951804572e+01, 0.1480791608091e+00, + 0.6301889697863e-06, 0.5444316669126e+01, 0.2243449970715e+00, + 0.4891042662861e-06, 0.1490552839784e+01, 0.3340612434717e+01, + 0.3457083123290e-06, 0.3030475486049e+01, 0.3516457698740e-01, + 0.3032559967314e-06, 0.2652038793632e+01, 0.1104591729320e-01, + 0.2841133988903e-06, 0.1276744786829e+01, 0.4110125927500e-01, + 0.2855564444432e-06, 0.2143368674733e+01, 0.1510475019529e+00, + + 0.2765157135038e-06, 0.5444186109077e+01, 0.6373574839730e-01, + 0.2382312465034e-06, 0.2190521137593e+01, 0.2275259891141e+00, + 0.2808060365077e-06, 0.5735195064841e+01, 0.2535050500000e-01, + 0.2332175234405e-06, 0.9481985524859e-01, 0.7181332454670e-01, + 0.2322488199659e-06, 0.5180499361533e+01, 0.8582758298370e-01, + 0.1881850258423e-06, 0.3219788273885e+01, 0.2118763888447e+01, + 0.2196111392808e-06, 0.2366941159761e+01, 0.2968341143800e-02, + 0.2183810335519e-06, 0.4825445110915e+01, 0.7775000683430e-01, + 0.2002733093326e-06, 0.2457148995307e+01, 0.2093666171530e+00, + 0.1967111767229e-06, 0.5586291545459e+01, 0.2172315424036e+00, + + 0.1568473250543e-06, 0.3708003123320e+01, 0.7429900518901e+00, + 0.1852528314300e-06, 0.4310638151560e+01, 0.2022531624851e+00, + 0.1832111226447e-06, 0.1494665322656e+01, 0.3235053470014e+00, + 0.1746805502310e-06, 0.1451378500784e+01, 0.1385174140878e+00, + 0.1555730966650e-06, 0.1068040418198e+01, 0.7358765972222e+00, + 0.1554883462559e-06, 0.2442579035461e+01, 0.5154640627760e+00, + 0.1638380568746e-06, 0.2597913420625e+00, 0.8531963191132e+00, + 0.1159938593640e-06, 0.5834512021280e+01, 0.1990721704425e+00, + 0.1083427965695e-06, 0.5054033177950e+01, 0.5439178814476e+00, + 0.1156480369431e-06, 0.5325677432457e+01, 0.5257585094865e+00, + + 0.1141308860095e-06, 0.2153403923857e+01, 0.5336234347371e+00, + 0.7913146470946e-07, 0.8642846847027e+00, 0.1478866649112e+01, + 0.7439752463733e-07, 0.1970628496213e+01, 0.2164800718209e+00, + 0.7280277104079e-07, 0.6073307250609e+01, 0.2101180877357e+00, + 0.8319567719136e-07, 0.1954371928334e+01, 0.1692165728891e+01, + 0.7137705549290e-07, 0.8904989440909e+00, 0.4155522422634e+00, + 0.6900825396225e-07, 0.2825717714977e+01, 0.1173197218910e+00, + 0.7245757216635e-07, 0.2481677513331e+01, 0.1265567569334e+01, + 0.6961165696255e-07, 0.1292955312978e+01, 0.9562891316684e+00, + 0.7571804456890e-07, 0.3427517575069e+01, 0.1422690933580e-01, + + 0.6605425721904e-07, 0.8052192701492e+00, 0.6470106940028e+00, + 0.7375477357248e-07, 0.1705076390088e+01, 0.1581959461667e+01, + 0.7041664951470e-07, 0.4848356967891e+00, 0.9597935788730e-01, + 0.6322199535763e-07, 0.3878069473909e+01, 0.7084920306520e-01, + 0.5244380279191e-07, 0.2645560544125e+01, 0.5265099800692e+00, + 0.5143125704988e-07, 0.4834486101370e+01, 0.5328719641544e+00, + 0.5871866319373e-07, 0.7981472548900e+00, 0.7871412831580e-01, + 0.6300822573871e-07, 0.5979398788281e+01, 0.2608790314060e+02, + 0.6062154271548e-07, 0.4108655402756e+01, 0.1114304132498e+00, + 0.4361912339976e-07, 0.5322624319280e+01, 0.1375773836557e+01, + + 0.4417005920067e-07, 0.6240817359284e+01, 0.2770348281756e+00, + 0.4686806749936e-07, 0.3214977301156e+01, 0.1143987543936e+00, + 0.3758892132305e-07, 0.5879809634765e+01, 0.1596186371003e+01, + 0.5151351332319e-07, 0.2893377688007e+00, 0.2228608264996e+00, + 0.4554683578572e-07, 0.5475427144122e+01, 0.1465949902372e+00, + 0.3442381385338e-07, 0.5992034796640e+01, 0.5070101000000e-01, + 0.2831093954933e-07, 0.5367350273914e+01, 0.3092784376656e+00, + 0.3756267090084e-07, 0.5758171285420e+01, 0.4903339079539e+00, + 0.2816374679892e-07, 0.1863718700923e+01, 0.2991266627620e+00, + 0.3419307025569e-07, 0.9524347534130e+00, 0.3518164938661e+00, + + 0.2904250494239e-07, 0.5304471615602e+01, 0.1099462426779e+00, + 0.2471734511206e-07, 0.1297069793530e+01, 0.6256703299991e+00, + 0.2539620831872e-07, 0.3281126083375e+01, 0.1256615170089e+02, + 0.2281017868007e-07, 0.1829122133165e+01, 0.6681224869435e+01, + 0.2275319473335e-07, 0.5797198160181e+01, 0.3932462625300e-02, + 0.2547755368442e-07, 0.4752697708330e+01, 0.1169588211447e+01, + 0.2285979669317e-07, 0.1223205292886e+01, 0.1045155034888e+01, + 0.1913386560994e-07, 0.1757532993389e+01, 0.1155361302111e+01, + 0.1809020525147e-07, 0.4246116108791e+01, 0.3368040641550e-01, + 0.1649213300201e-07, 0.1445162890627e+01, 0.4408250688924e+00, + + 0.1834972793932e-07, 0.1126917567225e+01, 0.4452511715700e-02, + 0.1439550648138e-07, 0.6160756834764e+01, 0.9420622223326e+00, + 0.1487645457041e-07, 0.4358761931792e+01, 0.4123712502208e+00, + 0.1731729516660e-07, 0.6134456753344e+01, 0.2108507877249e+00, + 0.1717747163567e-07, 0.1898186084455e+01, 0.2157473718317e+00, + 0.1418190430374e-07, 0.4180286741266e+01, 0.6521991896920e-01, + 0.1404844134873e-07, 0.7654053565412e-01, 0.4258542984690e-01, + 0.1409842846538e-07, 0.4418612420312e+01, 0.2258291676434e+00, + 0.1090948346291e-07, 0.1260615686131e+01, 0.4226656969313e+00, + 0.1357577323612e-07, 0.3558248818690e+01, 0.7923417740620e-01, + + 0.1018154061960e-07, 0.5676087241256e+01, 0.1456308687557e+00, + 0.1412073972109e-07, 0.8394392632422e+00, 0.1525316725248e+00, + 0.1030938326496e-07, 0.1653593274064e+01, 0.1795258541446e+01, + 0.1180081567104e-07, 0.1285802592036e+01, 0.7032915397480e-01, + 0.9708510575650e-08, 0.7631889488106e+00, 0.8434341241180e-01, + 0.9637689663447e-08, 0.4630642649176e+01, 0.1272681024002e+01, + 0.1068910429389e-07, 0.5294934032165e+01, 0.2123349582968e+00, + 0.1063716179336e-07, 0.2736266800832e+01, 0.2142632012598e+00, + 0.1234858713814e-07, 0.1302891146570e+01, 0.1847279083684e+00, + 0.8912631189738e-08, 0.3570415993621e+01, 0.2648454860559e+01, + + 0.1036378285534e-07, 0.4236693440949e+01, 0.1370332435159e+00, + 0.9667798501561e-08, 0.2960768892398e+01, 0.4376440768498e+00, + 0.8108314201902e-08, 0.6987781646841e+00, 0.2880807454688e+00, + 0.7648364324628e-08, 0.2499017863863e+01, 0.2037373330570e+00, + 0.7286136828406e-08, 0.3787426951665e+01, 0.1129145838217e+00, + 0.9448237743913e-08, 0.2694354332983e+01, 0.5272426800584e+00, + 0.9374276106428e-08, 0.4787121277064e+01, 0.5321392641652e+00, + 0.7100226287462e-08, 0.3530238792101e+00, 0.6288513220417e+00, + 0.9253056659571e-08, 0.1399478925664e+01, 0.1606092486742e+00, + 0.6636432145504e-08, 0.3479575438447e+01, 0.1368660381889e+01, + + 0.6469975312932e-08, 0.1383669964800e+01, 0.2008557621224e+01, + 0.7335849729765e-08, 0.1243698166898e+01, 0.9561746721300e-02, + 0.8743421205855e-08, 0.3776164289301e+01, 0.3801276407308e+00, + 0.5993635744494e-08, 0.5627122113596e+01, 0.2042657109477e+02, + 0.5981008479693e-08, 0.1674336636752e+01, 0.2111650433779e+01, + 0.6188535145838e-08, 0.5214925208672e+01, 0.4305306221819e+00, + 0.6596074017566e-08, 0.2907653268124e+01, 0.1063314406849e+01, + 0.6630815126226e-08, 0.2127643669658e+01, 0.8389694097774e+00, + 0.6156772830040e-08, 0.5082160803295e+01, 0.4234171675140e+00, + 0.6446960563014e-08, 0.1872100916905e+01, 0.5287268506303e+00, + + 0.6429324424668e-08, 0.5610276103577e+01, 0.5306550935933e+00, + 0.6302232396465e-08, 0.1592152049607e+01, 0.1253008786510e-01, + 0.6399244436159e-08, 0.2746214421532e+01, 0.5217580628120e+02, + 0.5474965172558e-08, 0.2317666374383e+01, 0.2221856701002e+01, + 0.5339293190692e-08, 0.1084724961156e+01, 0.7466759693650e-01, + 0.5334733683389e-08, 0.3594106067745e+01, 0.7489573444450e-01, + 0.5392665782110e-08, 0.5630254365606e+01, 0.1055449481598e+01, + 0.6682075673789e-08, 0.1518480041732e+01, 0.2213766559277e+00, + 0.5079130495960e-08, 0.2739765115711e+01, 0.2132517061319e+00, + 0.5077759793261e-08, 0.5290711290094e+01, 0.2133464534247e+00, + + 0.4832037368310e-08, 0.1404473217200e+01, 0.7160067364790e-01, + 0.6463279674802e-08, 0.6038381695210e+01, 0.2209183458640e-01, + 0.6240592771560e-08, 0.1290170653666e+01, 0.3306188016693e+00, + 0.4672013521493e-08, 0.3261895939677e+01, 0.7796265773310e-01, + 0.6500650750348e-08, 0.1154522312095e+01, 0.3884652414254e+00, + 0.6344161389053e-08, 0.6206111545062e+01, 0.7605151500000e-01, + 0.4682518370646e-08, 0.5409118796685e+01, 0.1073608853559e+01, + 0.5329460015591e-08, 0.1202985784864e+01, 0.7287631425543e+00, + 0.5701588675898e-08, 0.4098715257064e+01, 0.8731175355560e-01, + 0.6030690867211e-08, 0.4132033218460e+00, 0.9846002785331e+00, + + 0.4336256312655e-08, 0.1211415991827e+01, 0.4297791515992e+00, + 0.4688498808975e-08, 0.3765479072409e+01, 0.2127790306879e+00, + 0.4675578609335e-08, 0.4265540037226e+01, 0.2138191288687e+00, + 0.4225578112158e-08, 0.5237566010676e+01, 0.3407705765729e+00, + 0.5139422230028e-08, 0.1507173079513e+01, 0.7233337363710e-01, + 0.4619995093571e-08, 0.9023957449848e-01, 0.8603097737811e+00, + 0.4494776255461e-08, 0.5414930552139e+00, 0.7381754420900e-01, + 0.4274026276788e-08, 0.4145735303659e+01, 0.7574578717200e-01, + 0.5018141789353e-08, 0.3344408829055e+01, 0.3180992042600e-02, + 0.4866163952181e-08, 0.3348534657607e+01, 0.7722995774390e-01, + + 0.4111986020501e-08, 0.4198823597220e+00, 0.1451108196653e+00, + 0.3356142784950e-08, 0.5609144747180e+01, 0.1274714967946e+00, + 0.4070575554551e-08, 0.7028411059224e+00, 0.3503323232942e+00, + 0.3257451857278e-08, 0.5624697983086e+01, 0.5296435984654e+00, + 0.3256973703026e-08, 0.1857842076707e+01, 0.5297383457582e+00, + 0.3830771508640e-08, 0.4562887279931e+01, 0.9098186128426e+00, + 0.3725024005962e-08, 0.2358058692652e+00, 0.1084620721060e+00, + 0.3136763921756e-08, 0.2049731526845e+01, 0.2346394437820e+00, + 0.3795147256194e-08, 0.2432356296933e+00, 0.1862120789403e+00, + 0.2877342229911e-08, 0.5631101279387e+01, 0.1905464808669e+01, + + 0.3076931798805e-08, 0.1117615737392e+01, 0.3628624111593e+00, + 0.2734765945273e-08, 0.5899826516955e+01, 0.2131850110243e+00, + 0.2733405296885e-08, 0.2130562964070e+01, 0.2134131485323e+00, + 0.2898552353410e-08, 0.3462387048225e+00, 0.5291709230214e+00, + 0.2893736103681e-08, 0.8534352781543e+00, 0.5302110212022e+00, + 0.3095717734137e-08, 0.2875061429041e+01, 0.2976424921901e+00, + 0.2636190425832e-08, 0.2242512846659e+01, 0.1485980103780e+01, + 0.3645512095537e-08, 0.1354016903958e+01, 0.6044726378023e+00, + 0.2808173547723e-08, 0.6705114365631e-01, 0.6225157782540e-01, + 0.2625012866888e-08, 0.4775705748482e+01, 0.5268983110410e-01, + + 0.2572233995651e-08, 0.2638924216139e+01, 0.1258454114666e+01, + 0.2604238824792e-08, 0.4826358927373e+01, 0.2103781122809e+00, + 0.2596886385239e-08, 0.3200388483118e+01, 0.2162200472757e+00, + 0.3228057304264e-08, 0.5384848409563e+01, 0.2007689919132e+00, + 0.2481601798252e-08, 0.5173373487744e+01, 0.1062562936266e+01, + 0.2745977498864e-08, 0.6250966149853e+01, 0.5651155736444e+00, + 0.2669878833811e-08, 0.4906001352499e+01, 0.1400015846597e+00, + 0.3203986611711e-08, 0.5034333010005e+01, 0.7036329877322e+00, + 0.3354961227212e-08, 0.6108262423137e+01, 0.4549093064213e+00, + 0.2400407324558e-08, 0.2135399294955e+01, 0.2125476091956e+00, + + 0.2379905859802e-08, 0.5893721933961e+01, 0.2140505503610e+00, + 0.2550844302187e-08, 0.3331940762063e+01, 0.1534957940063e+00, + 0.2268824211001e-08, 0.1843418461035e+01, 0.2235935264888e+00, + 0.2464700891204e-08, 0.3029548547230e+01, 0.2091065926078e+00, + 0.2436814726024e-08, 0.4994717970364e+01, 0.2174915669488e+00, + 0.2443623894745e-08, 0.2645102591375e+01, 0.1739420156204e+00, + 0.2318701783838e-08, 0.5700547397897e+01, 0.7530171478090e-01, + 0.2284448700256e-08, 0.5268898905872e+01, 0.7426161660010e-01, + 0.2468848123510e-08, 0.5276280575078e+01, 0.2526561439362e+00, + 0.2814052350303e-08, 0.6130168623475e+01, 0.5636314030725e+00, + + 0.2243662755220e-08, 0.6631692457995e+00, 0.8886590321940e-01, + 0.2330795855941e-08, 0.2499435487702e+01, 0.1056200952181e+01, + 0.9757679038404e-09, 0.5796846023126e+01, 0.7826370942180e+02 }; + +/* SSB-to-Sun, T^0, Z */ + static const double s0z[] = { + 0.1181255122986e-03, 0.4607918989164e+00, 0.2132990797783e+00, + 0.1127777651095e-03, 0.4169146331296e+00, 0.5296909721118e+00, + 0.4777754401806e-04, 0.4582657007130e+01, 0.3813291813120e-01, + 0.1129354285772e-04, 0.5758735142480e+01, 0.7478166569050e-01, + -0.1149543637123e-04, 0.0000000000000e+00, 0.0000000000000e+00, + 0.3298730512306e-05, 0.5978801994625e+01, 0.4265981595566e+00, + 0.2733376706079e-05, 0.7665413691040e+00, 0.1059381944224e+01, + 0.9426389657270e-06, 0.3710201265838e+01, 0.2061856251104e+00, + 0.8187517749552e-06, 0.3390675605802e+00, 0.2204125344462e+00, + 0.4080447871819e-06, 0.4552296640088e+00, 0.5225775174439e+00, + + 0.3169973017028e-06, 0.3445455899321e+01, 0.5368044267797e+00, + 0.2438098615549e-06, 0.5664675150648e+01, 0.3664874755930e-01, + 0.2601897517235e-06, 0.1931894095697e+01, 0.1495633313810e+00, + 0.2314558080079e-06, 0.3666319115574e+00, 0.3961708870310e-01, + 0.1962549548002e-06, 0.3167411699020e+01, 0.7626583626240e-01, + 0.2180518287925e-06, 0.1544420746580e+01, 0.7113454667900e-02, + 0.1451382442868e-06, 0.1583756740070e+01, 0.1102062672231e+00, + 0.1358439007389e-06, 0.5239941758280e+01, 0.6398972393349e+00, + 0.1050585898028e-06, 0.2266958352859e+01, 0.3163918923335e+00, + 0.1050029870186e-06, 0.2711495250354e+01, 0.4194847048887e+00, + + 0.9934920679800e-07, 0.1116208151396e+01, 0.1589072916335e+01, + 0.1048395331560e-06, 0.3408619600206e+01, 0.1021328554739e+02, + 0.8370147196668e-07, 0.3810459401087e+01, 0.2535050500000e-01, + 0.7989856510998e-07, 0.3769910473647e+01, 0.7329749511860e-01, + 0.5441221655233e-07, 0.2416994903374e+01, 0.1030928125552e+00, + 0.4610812906784e-07, 0.5858503336994e+01, 0.4337116142245e+00, + 0.3923022803444e-07, 0.3354170010125e+00, 0.1484170571900e-02, + 0.2610725582128e-07, 0.5410600646324e+01, 0.6327837846670e+00, + 0.2455279767721e-07, 0.6120216681403e+01, 0.1162474756779e+01, + 0.2375530706525e-07, 0.6055443426143e+01, 0.1052268489556e+01, + + 0.1782967577553e-07, 0.3146108708004e+01, 0.8460828644453e+00, + 0.1581687095238e-07, 0.6255496089819e+00, 0.3340612434717e+01, + 0.1594657672461e-07, 0.3782604300261e+01, 0.1066495398892e+01, + 0.1563448615040e-07, 0.1997775733196e+01, 0.2022531624851e+00, + 0.1463624258525e-07, 0.1736316792088e+00, 0.3516457698740e-01, + 0.1331585056673e-07, 0.4331941830747e+01, 0.9491756770005e+00, + 0.1130634557637e-07, 0.6152017751825e+01, 0.2968341143800e-02, + 0.1028949607145e-07, 0.2101792614637e+00, 0.2275259891141e+00, + 0.1024074971618e-07, 0.4071833211074e+01, 0.5070101000000e-01, + 0.8826956060303e-08, 0.4861633688145e+00, 0.2093666171530e+00, + + 0.8572230171541e-08, 0.5268190724302e+01, 0.4110125927500e-01, + 0.7649332643544e-08, 0.5134543417106e+01, 0.2608790314060e+02, + 0.8581673291033e-08, 0.2920218146681e+01, 0.1480791608091e+00, + 0.8430589300938e-08, 0.3604576619108e+01, 0.2172315424036e+00, + 0.7776165501012e-08, 0.3772942249792e+01, 0.6373574839730e-01, + 0.8311070234408e-08, 0.6200412329888e+01, 0.3235053470014e+00, + 0.6927365212582e-08, 0.4543353113437e+01, 0.8531963191132e+00, + 0.6791574208598e-08, 0.2882188406238e+01, 0.7181332454670e-01, + 0.5593100811839e-08, 0.1776646892780e+01, 0.7429900518901e+00, + 0.4553381853021e-08, 0.3949617611240e+01, 0.7775000683430e-01, + + 0.5758000450068e-08, 0.3859251775075e+01, 0.1990721704425e+00, + 0.4281283457133e-08, 0.1466294631206e+01, 0.2118763888447e+01, + 0.4206935661097e-08, 0.5421776011706e+01, 0.1104591729320e-01, + 0.4213751641837e-08, 0.3412048993322e+01, 0.2243449970715e+00, + 0.5310506239878e-08, 0.5421641370995e+00, 0.5154640627760e+00, + 0.3827450341320e-08, 0.8887314524995e+00, 0.1510475019529e+00, + 0.4292435241187e-08, 0.1405043757194e+01, 0.1422690933580e-01, + 0.3189780702289e-08, 0.1060049293445e+01, 0.1173197218910e+00, + 0.3226611928069e-08, 0.6270858897442e+01, 0.2164800718209e+00, + 0.2893897608830e-08, 0.5117563223301e+01, 0.6470106940028e+00, + + 0.3239852024578e-08, 0.4079092237983e+01, 0.2101180877357e+00, + 0.2956892222200e-08, 0.1594917021704e+01, 0.3092784376656e+00, + 0.2980177912437e-08, 0.5258787667564e+01, 0.4155522422634e+00, + 0.3163725690776e-08, 0.3854589225479e+01, 0.8582758298370e-01, + 0.2662262399118e-08, 0.3561326430187e+01, 0.5257585094865e+00, + 0.2766689135729e-08, 0.3180732086830e+00, 0.1385174140878e+00, + 0.2411600278464e-08, 0.3324798335058e+01, 0.5439178814476e+00, + 0.2483527695131e-08, 0.4169069291947e+00, 0.5336234347371e+00, + 0.7788777276590e-09, 0.1900569908215e+01, 0.5217580628120e+02 }; + +/* SSB-to-Sun, T^1, X */ + static const double s1x[] = { + -0.1296310361520e-07, 0.0000000000000e+00, 0.0000000000000e+00, + 0.8975769009438e-08, 0.1128891609250e+01, 0.4265981595566e+00, + 0.7771113441307e-08, 0.2706039877077e+01, 0.2061856251104e+00, + 0.7538303866642e-08, 0.2191281289498e+01, 0.2204125344462e+00, + 0.6061384579336e-08, 0.3248167319958e+01, 0.1059381944224e+01, + 0.5726994235594e-08, 0.5569981398610e+01, 0.5225775174439e+00, + 0.5616492836424e-08, 0.5057386614909e+01, 0.5368044267797e+00, + 0.1010881584769e-08, 0.3473577116095e+01, 0.7113454667900e-02, + 0.7259606157626e-09, 0.3651858593665e+00, 0.6398972393349e+00, + 0.8755095026935e-09, 0.1662835408338e+01, 0.4194847048887e+00, + + 0.5370491182812e-09, 0.1327673878077e+01, 0.4337116142245e+00, + 0.5743773887665e-09, 0.4250200846687e+01, 0.2132990797783e+00, + 0.4408103140300e-09, 0.3598752574277e+01, 0.1589072916335e+01, + 0.3101892374445e-09, 0.4887822983319e+01, 0.1052268489556e+01, + 0.3209453713578e-09, 0.9702272295114e+00, 0.5296909721118e+00, + 0.3017228286064e-09, 0.5484462275949e+01, 0.1066495398892e+01, + 0.3200700038601e-09, 0.2846613338643e+01, 0.1495633313810e+00, + 0.2137637279911e-09, 0.5692163292729e+00, 0.3163918923335e+00, + 0.1899686386727e-09, 0.2061077157189e+01, 0.2275259891141e+00, + 0.1401994545308e-09, 0.4177771136967e+01, 0.1102062672231e+00, + + 0.1578057810499e-09, 0.5782460597335e+01, 0.7626583626240e-01, + 0.1237713253351e-09, 0.5705900866881e+01, 0.5154640627760e+00, + 0.1313076837395e-09, 0.5163438179576e+01, 0.3664874755930e-01, + 0.1184963304860e-09, 0.3054804427242e+01, 0.6327837846670e+00, + 0.1238130878565e-09, 0.2317292575962e+01, 0.3961708870310e-01, + 0.1015959527736e-09, 0.2194643645526e+01, 0.7329749511860e-01, + 0.9017954423714e-10, 0.2868603545435e+01, 0.1990721704425e+00, + 0.8668024955603e-10, 0.4923849675082e+01, 0.5439178814476e+00, + 0.7756083930103e-10, 0.3014334135200e+01, 0.9491756770005e+00, + 0.7536503401741e-10, 0.2704886279769e+01, 0.1030928125552e+00, + + 0.5483308679332e-10, 0.6010983673799e+01, 0.8531963191132e+00, + 0.5184339620428e-10, 0.1952704573291e+01, 0.2093666171530e+00, + 0.5108658712030e-10, 0.2958575786649e+01, 0.2172315424036e+00, + 0.5019424524650e-10, 0.1736317621318e+01, 0.2164800718209e+00, + 0.4909312625978e-10, 0.3167216416257e+01, 0.2101180877357e+00, + 0.4456638901107e-10, 0.7697579923471e+00, 0.3235053470014e+00, + 0.4227030350925e-10, 0.3490910137928e+01, 0.6373574839730e-01, + 0.4095456040093e-10, 0.5178888984491e+00, 0.6470106940028e+00, + 0.4990537041422e-10, 0.3323887668974e+01, 0.1422690933580e-01, + 0.4321170010845e-10, 0.4288484987118e+01, 0.7358765972222e+00, + + 0.3544072091802e-10, 0.6021051579251e+01, 0.5265099800692e+00, + 0.3480198638687e-10, 0.4600027054714e+01, 0.5328719641544e+00, + 0.3440287244435e-10, 0.4349525970742e+01, 0.8582758298370e-01, + 0.3330628322713e-10, 0.2347391505082e+01, 0.1104591729320e-01, + 0.2973060707184e-10, 0.4789409286400e+01, 0.5257585094865e+00, + 0.2932606766089e-10, 0.5831693799927e+01, 0.5336234347371e+00, + 0.2876972310953e-10, 0.2692638514771e+01, 0.1173197218910e+00, + 0.2827488278556e-10, 0.2056052487960e+01, 0.2022531624851e+00, + 0.2515028239756e-10, 0.7411863262449e+00, 0.9597935788730e-01, + 0.2853033744415e-10, 0.3948481024894e+01, 0.2118763888447e+01 }; + +/* SSB-to-Sun, T^1, Y */ + static const double s1y[] = { + 0.8989047573576e-08, 0.5840593672122e+01, 0.4265981595566e+00, + 0.7815938401048e-08, 0.1129664707133e+01, 0.2061856251104e+00, + 0.7550926713280e-08, 0.6196589104845e+00, 0.2204125344462e+00, + 0.6056556925895e-08, 0.1677494667846e+01, 0.1059381944224e+01, + 0.5734142698204e-08, 0.4000920852962e+01, 0.5225775174439e+00, + 0.5614341822459e-08, 0.3486722577328e+01, 0.5368044267797e+00, + 0.1028678147656e-08, 0.1877141024787e+01, 0.7113454667900e-02, + 0.7270792075266e-09, 0.5077167301739e+01, 0.6398972393349e+00, + 0.8734141726040e-09, 0.9069550282609e-01, 0.4194847048887e+00, + 0.5377371402113e-09, 0.6039381844671e+01, 0.4337116142245e+00, + + 0.4729719431571e-09, 0.2153086311760e+01, 0.2132990797783e+00, + 0.4458052820973e-09, 0.5059830025565e+01, 0.5296909721118e+00, + 0.4406855467908e-09, 0.2027971692630e+01, 0.1589072916335e+01, + 0.3101659310977e-09, 0.3317677981860e+01, 0.1052268489556e+01, + 0.3016749232545e-09, 0.3913703482532e+01, 0.1066495398892e+01, + 0.3198541352656e-09, 0.1275513098525e+01, 0.1495633313810e+00, + 0.2142065389871e-09, 0.5301351614597e+01, 0.3163918923335e+00, + 0.1902615247592e-09, 0.4894943352736e+00, 0.2275259891141e+00, + 0.1613410990871e-09, 0.2449891130437e+01, 0.1102062672231e+00, + 0.1576992165097e-09, 0.4211421447633e+01, 0.7626583626240e-01, + + 0.1241637259894e-09, 0.4140803368133e+01, 0.5154640627760e+00, + 0.1313974830355e-09, 0.3591920305503e+01, 0.3664874755930e-01, + 0.1181697118258e-09, 0.1506314382788e+01, 0.6327837846670e+00, + 0.1238239742779e-09, 0.7461405378404e+00, 0.3961708870310e-01, + 0.1010107068241e-09, 0.6271010795475e+00, 0.7329749511860e-01, + 0.9226316616509e-10, 0.1259158839583e+01, 0.1990721704425e+00, + 0.8664946419555e-10, 0.3353244696934e+01, 0.5439178814476e+00, + 0.7757230468978e-10, 0.1447677295196e+01, 0.9491756770005e+00, + 0.7693168628139e-10, 0.1120509896721e+01, 0.1030928125552e+00, + 0.5487897454612e-10, 0.4439380426795e+01, 0.8531963191132e+00, + + 0.5196118677218e-10, 0.3788856619137e+00, 0.2093666171530e+00, + 0.5110853339935e-10, 0.1386879372016e+01, 0.2172315424036e+00, + 0.5027804534813e-10, 0.1647881805466e+00, 0.2164800718209e+00, + 0.4922485922674e-10, 0.1594315079862e+01, 0.2101180877357e+00, + 0.6155599524400e-10, 0.0000000000000e+00, 0.0000000000000e+00, + 0.4447147832161e-10, 0.5480720918976e+01, 0.3235053470014e+00, + 0.4144691276422e-10, 0.1931371033660e+01, 0.6373574839730e-01, + 0.4099950625452e-10, 0.5229611294335e+01, 0.6470106940028e+00, + 0.5060541682953e-10, 0.1731112486298e+01, 0.1422690933580e-01, + 0.4293615946300e-10, 0.2714571038925e+01, 0.7358765972222e+00, + + 0.3545659845763e-10, 0.4451041444634e+01, 0.5265099800692e+00, + 0.3479112041196e-10, 0.3029385448081e+01, 0.5328719641544e+00, + 0.3438516493570e-10, 0.2778507143731e+01, 0.8582758298370e-01, + 0.3297341285033e-10, 0.7898709807584e+00, 0.1104591729320e-01, + 0.2972585818015e-10, 0.3218785316973e+01, 0.5257585094865e+00, + 0.2931707295017e-10, 0.4260731012098e+01, 0.5336234347371e+00, + 0.2897198149403e-10, 0.1120753978101e+01, 0.1173197218910e+00, + 0.2832293240878e-10, 0.4597682717827e+00, 0.2022531624851e+00, + 0.2864348326612e-10, 0.2169939928448e+01, 0.9597935788730e-01, + 0.2852714675471e-10, 0.2377659870578e+01, 0.2118763888447e+01 }; + +/* SSB-to-Sun, T^1, Z */ + static const double s1z[] = { + 0.5444220475678e-08, 0.1803825509310e+01, 0.2132990797783e+00, + 0.3883412695596e-08, 0.4668616389392e+01, 0.5296909721118e+00, + 0.1334341434551e-08, 0.0000000000000e+00, 0.0000000000000e+00, + 0.3730001266883e-09, 0.5401405918943e+01, 0.2061856251104e+00, + 0.2894929197956e-09, 0.4932415609852e+01, 0.2204125344462e+00, + 0.2857950357701e-09, 0.3154625362131e+01, 0.7478166569050e-01, + 0.2499226432292e-09, 0.3657486128988e+01, 0.4265981595566e+00, + 0.1937705443593e-09, 0.5740434679002e+01, 0.1059381944224e+01, + 0.1374894396320e-09, 0.1712857366891e+01, 0.5368044267797e+00, + 0.1217248678408e-09, 0.2312090870932e+01, 0.5225775174439e+00, + + 0.7961052740870e-10, 0.5283368554163e+01, 0.3813291813120e-01, + 0.4979225949689e-10, 0.4298290471860e+01, 0.4194847048887e+00, + 0.4388552286597e-10, 0.6145515047406e+01, 0.7113454667900e-02, + 0.2586835212560e-10, 0.3019448001809e+01, 0.6398972393349e+00 }; + +/* SSB-to-Sun, T^2, X */ + static const double s2x[] = { + 0.1603551636587e-11, 0.4404109410481e+01, 0.2061856251104e+00, + 0.1556935889384e-11, 0.4818040873603e+00, 0.2204125344462e+00, + 0.1182594414915e-11, 0.9935762734472e+00, 0.5225775174439e+00, + 0.1158794583180e-11, 0.3353180966450e+01, 0.5368044267797e+00, + 0.9597358943932e-12, 0.5567045358298e+01, 0.2132990797783e+00, + 0.6511516579605e-12, 0.5630872420788e+01, 0.4265981595566e+00, + 0.7419792747688e-12, 0.2156188581957e+01, 0.5296909721118e+00, + 0.3951972655848e-12, 0.1981022541805e+01, 0.1059381944224e+01, + 0.4478223877045e-12, 0.0000000000000e+00, 0.0000000000000e+00 }; + +/* SSB-to-Sun, T^2, Y */ + static const double s2y[] = { + 0.1609114495091e-11, 0.2831096993481e+01, 0.2061856251104e+00, + 0.1560330784946e-11, 0.5193058213906e+01, 0.2204125344462e+00, + 0.1183535479202e-11, 0.5707003443890e+01, 0.5225775174439e+00, + 0.1158183066182e-11, 0.1782400404928e+01, 0.5368044267797e+00, + 0.1032868027407e-11, 0.4036925452011e+01, 0.2132990797783e+00, + 0.6540142847741e-12, 0.4058241056717e+01, 0.4265981595566e+00, + 0.7305236491596e-12, 0.6175401942957e+00, 0.5296909721118e+00, + -0.5580725052968e-12, 0.0000000000000e+00, 0.0000000000000e+00, + 0.3946122651015e-12, 0.4108265279171e+00, 0.1059381944224e+01 }; + +/* SSB-to-Sun, T^2, Z */ + static const double s2z[] = { + 0.3749920358054e-12, 0.3230285558668e+01, 0.2132990797783e+00, + 0.2735037220939e-12, 0.6154322683046e+01, 0.5296909721118e+00 }; + +/* Pointers to coefficient arrays, in x,y,z sets */ + static const double *ce0[] = { e0x, e0y, e0z }, + *ce1[] = { e1x, e1y, e1z }, + *ce2[] = { e2x, e2y, e2z }, + *cs0[] = { s0x, s0y, s0z }, + *cs1[] = { s1x, s1y, s1z }, + *cs2[] = { s2x, s2y, s2z }; + const double *coeffs; + +/* Numbers of terms for each component of the model, in x,y,z sets */ + static const int ne0[3] = {(int)(sizeof e0x / sizeof (double) / 3), + (int)(sizeof e0y / sizeof (double) / 3), + (int)(sizeof e0z / sizeof (double) / 3) }, + ne1[3] = {(int)(sizeof e1x / sizeof (double) / 3), + (int)(sizeof e1y / sizeof (double) / 3), + (int)(sizeof e1z / sizeof (double) / 3) }, + ne2[3] = {(int)(sizeof e2x / sizeof (double) / 3), + (int)(sizeof e2y / sizeof (double) / 3), + (int)(sizeof e2z / sizeof (double) / 3) }, + ns0[3] = {(int)(sizeof s0x / sizeof (double) / 3), + (int)(sizeof s0y / sizeof (double) / 3), + (int)(sizeof s0z / sizeof (double) / 3) }, + ns1[3] = {(int)(sizeof s1x / sizeof (double) / 3), + (int)(sizeof s1y / sizeof (double) / 3), + (int)(sizeof s1z / sizeof (double) / 3) }, + ns2[3] = {(int)(sizeof s2x / sizeof (double) / 3), + (int)(sizeof s2y / sizeof (double) / 3), + (int)(sizeof s2z / sizeof (double) / 3) }; + int nterms; + +/* Miscellaneous */ + int jstat, i, j; + double t, t2, xyz, xyzd, a, b, c, ct, p, cp, + ph[3], vh[3], pb[3], vb[3], x, y, z; + +/* ------------------------------------------------------------------ */ + +/* Time since reference epoch, Julian years. */ + t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJY; + t2 = t*t; + +/* Set status. */ + jstat = fabs(t) <= 100.0 ? 0 : 1; + +/* X then Y then Z. */ + for (i = 0; i < 3; i++) { + + /* Initialize position and velocity component. */ + xyz = 0.0; + xyzd = 0.0; + + /* ------------------------------------------------ */ + /* Obtain component of Sun to Earth ecliptic vector */ + /* ------------------------------------------------ */ + + /* Sun to Earth, T^0 terms. */ + coeffs = ce0[i]; + nterms = ne0[i]; + for (j = 0; j < nterms; j++) { + a = *coeffs++; + b = *coeffs++; + c = *coeffs++; + p = b + c*t; + xyz += a*cos(p); + xyzd -= a*c*sin(p); + } + + /* Sun to Earth, T^1 terms. */ + coeffs = ce1[i]; + nterms = ne1[i]; + for (j = 0; j < nterms; j++) { + a = *coeffs++; + b = *coeffs++; + c = *coeffs++; + ct = c*t; + p = b + ct; + cp = cos(p); + xyz += a*t*cp; + xyzd += a*( cp - ct*sin(p) ); + } + + /* Sun to Earth, T^2 terms. */ + coeffs = ce2[i]; + nterms = ne2[i]; + for (j = 0; j < nterms; j++) { + a = *coeffs++; + b = *coeffs++; + c = *coeffs++; + ct = c*t; + p = b + ct; + cp = cos(p); + xyz += a*t2*cp; + xyzd += a*t*( 2.0*cp - ct*sin(p) ); + } + + /* Heliocentric Earth position and velocity component. */ + ph[i] = xyz; + vh[i] = xyzd / ERFA_DJY; + + /* ------------------------------------------------ */ + /* Obtain component of SSB to Earth ecliptic vector */ + /* ------------------------------------------------ */ + + /* SSB to Sun, T^0 terms. */ + coeffs = cs0[i]; + nterms = ns0[i]; + for (j = 0; j < nterms; j++) { + a = *coeffs++; + b = *coeffs++; + c = *coeffs++; + p = b + c*t; + xyz += a*cos(p); + xyzd -= a*c*sin(p); + } + + /* SSB to Sun, T^1 terms. */ + coeffs = cs1[i]; + nterms = ns1[i]; + for (j = 0; j < nterms; j++) { + a = *coeffs++; + b = *coeffs++; + c = *coeffs++; + ct = c*t; + p = b + ct; + cp = cos(p); + xyz += a*t*cp; + xyzd += a*(cp - ct*sin(p)); + } + + /* SSB to Sun, T^2 terms. */ + coeffs = cs2[i]; + nterms = ns2[i]; + for (j = 0; j < nterms; j++) { + a = *coeffs++; + b = *coeffs++; + c = *coeffs++; + ct = c*t; + p = b + ct; + cp = cos(p); + xyz += a*t2*cp; + xyzd += a*t*(2.0*cp - ct*sin(p)); + } + + /* Barycentric Earth position and velocity component. */ + pb[i] = xyz; + vb[i] = xyzd / ERFA_DJY; + + /* Next Cartesian component. */ + } + +/* Rotate from ecliptic to BCRS coordinates. */ + + x = ph[0]; + y = ph[1]; + z = ph[2]; + pvh[0][0] = x + am12*y + am13*z; + pvh[0][1] = am21*x + am22*y + am23*z; + pvh[0][2] = am32*y + am33*z; + + x = vh[0]; + y = vh[1]; + z = vh[2]; + pvh[1][0] = x + am12*y + am13*z; + pvh[1][1] = am21*x + am22*y + am23*z; + pvh[1][2] = am32*y + am33*z; + + x = pb[0]; + y = pb[1]; + z = pb[2]; + pvb[0][0] = x + am12*y + am13*z; + pvb[0][1] = am21*x + am22*y + am23*z; + pvb[0][2] = am32*y + am33*z; + + x = vb[0]; + y = vb[1]; + z = vb[2]; + pvb[1][0] = x + am12*y + am13*z; + pvb[1][1] = am21*x + am22*y + am23*z; + pvb[1][2] = am32*y + am33*z; + +/* Return the status. */ + return jstat; + +/* Finished. */ + +} + diff --git a/android/app/src/main/cpp/util/astronomy.h b/android/app/src/main/cpp/util/astronomy.h new file mode 100644 index 0000000..1bb65a8 --- /dev/null +++ b/android/app/src/main/cpp/util/astronomy.h @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_ASTRONOMY_H +#define INCLUDE_ASTRONOMY_H + +#include "export.h" + +#include + +// Right ascension and declination +struct SDRBASE_API RADec { + double ra; + double dec; +}; + +// Azimuth and Altitude +struct SDRBASE_API AzAlt { + double az; + double alt; +}; + +class SDRBASE_API Astronomy { + +public: + static double julianDate(int year, int month, int day, int hours, int minutes, int seconds); + static double julianDate(QDateTime dt); + static double modifiedJulianDate(QDateTime dt); + static QDateTime julianDateToDateTime(double jd); + + static double jd_j2000(void); + static double jd_b1950(void); + static double jd_now(void); + + static RADec precess(RADec rd_in, double jd_from, double jd_to); + static AzAlt raDecToAzAlt(RADec rd, double latitude, double longitude, QDateTime dt, bool j2000=true); + static RADec azAltToRaDec(AzAlt aa, double latitude, double longitude, QDateTime dt); + + static void azAltToXY85(AzAlt aa, double& x, double& y); + static void azAltToXY30(AzAlt aa, double& x, double& y); + static AzAlt xy85ToAzAlt(double x, double y); + static AzAlt xy30ToAzAlt(double x, double y); + + static double localSiderealTime(QDateTime dateTime, double longitude); + + static void sunPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt); + static double moonDays(QDateTime dt); + static void moonPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt); + + static double refractionSaemundsson(double alt, double pressure, double temperature); + static double refractionPAL(double alt, double pressure, double temperature, double humidity, double frequency, double latitude, double heightAboveSeaLevel, double temperatureLapseRate); + + static double lstAndRAToLongitude(double lst, double raHours); + + static void equatorialToGalactic(double ra, double dec, double& l, double& b); + static void northGalacticPoleJ2000(double& ra, double& dec); + static void galacticToEquatorial(double l, double b, double& ra, double& dec); + + static double dopplerToVelocity(double f, double f0); + static double velocityToDoppler(double v, double f0); + + static double earthRotationVelocity(RADec rd, double latitude, double longitude, QDateTime dt); + static double earthOrbitVelocityBCRS(RADec rd, QDateTime dt); + static double sunVelocityLSRK(RADec rd); + static double observerVelocityLSRK(RADec rd, double latitude, double longitude, QDateTime dt); + + static void sunrise(QDate date, double latitude, double longitude, QDateTime& rise, QDateTime& set); + + static double noisePowerdBm(double temp, double bw); + static double noiseTemp(double dBm, double bw); + + static double modulo(double a, double b); + + static const double m_boltzmann; + static const double m_hydrogenLineFrequency; + static const double m_hydroxylLineFrequency; + static const double m_deuteriumLineFrequency; + static const double m_speedOfLight; + static const double m_hydrogenMass; +}; + +#endif // INCLUDE_ASTRONOMY_H diff --git a/android/app/src/main/cpp/util/aurora.cpp b/android/app/src/main/cpp/util/aurora.cpp new file mode 100644 index 0000000..21112f6 --- /dev/null +++ b/android/app/src/main/cpp/util/aurora.cpp @@ -0,0 +1,430 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2025 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "aurora.h" + +#include +#include +#include +#include + +// Green -> Yellow -> Red +const unsigned char Aurora::m_colorMap[] = { + 0, 255, 0, + 1, 255, 0, + 3, 255, 0, + 5, 255, 0, + 7, 255, 0, + 9, 255, 0, + 11, 255, 0, + 13, 255, 0, + 15, 255, 0, + 17, 255, 0, + 19, 255, 0, + 21, 255, 0, + 23, 255, 0, + 25, 255, 0, + 27, 255, 0, + 29, 255, 0, + 31, 255, 0, + 33, 255, 0, + 35, 255, 0, + 37, 255, 0, + 39, 255, 0, + 41, 255, 0, + 43, 255, 0, + 45, 255, 0, + 47, 255, 0, + 49, 255, 0, + 51, 255, 0, + 53, 255, 0, + 55, 255, 0, + 57, 255, 0, + 59, 255, 0, + 61, 255, 0, + 63, 255, 0, + 65, 255, 0, + 67, 255, 0, + 69, 255, 0, + 71, 255, 0, + 73, 255, 0, + 75, 255, 0, + 77, 255, 0, + 79, 255, 0, + 81, 255, 0, + 83, 255, 0, + 85, 255, 0, + 87, 255, 0, + 89, 255, 0, + 91, 255, 0, + 93, 255, 0, + 95, 255, 0, + 97, 255, 0, + 99, 255, 0, + 101, 255, 0, + 103, 255, 0, + 105, 255, 0, + 107, 255, 0, + 109, 255, 0, + 111, 255, 0, + 113, 255, 0, + 115, 255, 0, + 117, 255, 0, + 119, 255, 0, + 121, 255, 0, + 123, 255, 0, + 125, 255, 0, + 127, 255, 0, + 129, 255, 0, + 131, 255, 0, + 133, 255, 0, + 135, 255, 0, + 137, 255, 0, + 139, 255, 0, + 141, 255, 0, + 143, 255, 0, + 145, 255, 0, + 147, 255, 0, + 149, 255, 0, + 151, 255, 0, + 153, 255, 0, + 155, 255, 0, + 157, 255, 0, + 159, 255, 0, + 161, 255, 0, + 163, 255, 0, + 165, 255, 0, + 167, 255, 0, + 169, 255, 0, + 171, 255, 0, + 173, 255, 0, + 175, 255, 0, + 177, 255, 0, + 179, 255, 0, + 181, 255, 0, + 183, 255, 0, + 185, 255, 0, + 187, 255, 0, + 189, 255, 0, + 191, 255, 0, + 193, 255, 0, + 195, 255, 0, + 197, 255, 0, + 199, 255, 0, + 201, 255, 0, + 203, 255, 0, + 205, 255, 0, + 207, 255, 0, + 209, 255, 0, + 211, 255, 0, + 213, 255, 0, + 215, 255, 0, + 217, 255, 0, + 219, 255, 0, + 221, 255, 0, + 223, 255, 0, + 225, 255, 0, + 227, 255, 0, + 229, 255, 0, + 231, 255, 0, + 233, 255, 0, + 235, 255, 0, + 237, 255, 0, + 239, 255, 0, + 241, 255, 0, + 243, 255, 0, + 245, 255, 0, + 247, 255, 0, + 249, 255, 0, + 251, 255, 0, + 253, 255, 0, + 255, 255, 0, + 255, 253, 0, + 255, 251, 0, + 255, 249, 0, + 255, 247, 0, + 255, 245, 0, + 255, 243, 0, + 255, 241, 0, + 255, 239, 0, + 255, 237, 0, + 255, 235, 0, + 255, 233, 0, + 255, 231, 0, + 255, 229, 0, + 255, 227, 0, + 255, 225, 0, + 255, 223, 0, + 255, 221, 0, + 255, 219, 0, + 255, 217, 0, + 255, 215, 0, + 255, 213, 0, + 255, 211, 0, + 255, 209, 0, + 255, 207, 0, + 255, 205, 0, + 255, 203, 0, + 255, 201, 0, + 255, 199, 0, + 255, 197, 0, + 255, 195, 0, + 255, 193, 0, + 255, 191, 0, + 255, 189, 0, + 255, 187, 0, + 255, 185, 0, + 255, 183, 0, + 255, 181, 0, + 255, 179, 0, + 255, 177, 0, + 255, 175, 0, + 255, 173, 0, + 255, 171, 0, + 255, 169, 0, + 255, 167, 0, + 255, 165, 0, + 255, 163, 0, + 255, 161, 0, + 255, 159, 0, + 255, 157, 0, + 255, 155, 0, + 255, 153, 0, + 255, 151, 0, + 255, 149, 0, + 255, 147, 0, + 255, 145, 0, + 255, 143, 0, + 255, 141, 0, + 255, 139, 0, + 255, 137, 0, + 255, 135, 0, + 255, 133, 0, + 255, 131, 0, + 255, 129, 0, + 255, 127, 0, + 255, 125, 0, + 255, 123, 0, + 255, 121, 0, + 255, 119, 0, + 255, 117, 0, + 255, 115, 0, + 255, 113, 0, + 255, 111, 0, + 255, 109, 0, + 255, 107, 0, + 255, 105, 0, + 255, 103, 0, + 255, 101, 0, + 255, 99, 0, + 255, 97, 0, + 255, 95, 0, + 255, 93, 0, + 255, 91, 0, + 255, 89, 0, + 255, 87, 0, + 255, 85, 0, + 255, 83, 0, + 255, 81, 0, + 255, 79, 0, + 255, 77, 0, + 255, 75, 0, + 255, 73, 0, + 255, 71, 0, + 255, 69, 0, + 255, 67, 0, + 255, 65, 0, + 255, 63, 0, + 255, 61, 0, + 255, 59, 0, + 255, 57, 0, + 255, 55, 0, + 255, 53, 0, + 255, 51, 0, + 255, 49, 0, + 255, 47, 0, + 255, 45, 0, + 255, 43, 0, + 255, 41, 0, + 255, 39, 0, + 255, 37, 0, + 255, 35, 0, + 255, 33, 0, + 255, 31, 0, + 255, 29, 0, + 255, 27, 0, + 255, 25, 0, + 255, 23, 0, + 255, 21, 0, + 255, 19, 0, + 255, 17, 0, + 255, 15, 0, + 255, 13, 0, + 255, 11, 0, + 255, 9, 0, + 255, 7, 0, + 255, 5, 0, + 255, 3, 0, + 255, 1, 0, +}; + +Aurora::Aurora() +{ + connect(&m_dataTimer, &QTimer::timeout, this, &Aurora::getData); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &Aurora::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("aurora"))) { + qDebug() << "Failed to create cache/aurora"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("giro")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); +} + +Aurora::~Aurora() +{ + disconnect(&m_dataTimer, &QTimer::timeout, this, &Aurora::getData); + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &Aurora::handleReply); + delete m_networkManager; +} + +Aurora* Aurora::create(const QString& service) +{ + if (service == "noaa.gov") + { + return new Aurora(); + } + else + { + qDebug() << "Aurora::create: Unsupported service: " << service; + return nullptr; + } +} + +void Aurora::getDataPeriodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_dataTimer.setInterval(periodInMins*60*1000); + m_dataTimer.start(); + getData(); + } + else + { + m_dataTimer.stop(); + } +} + +void Aurora::getData() +{ + QUrl url(QString("https://services.swpc.noaa.gov/json/ovation_aurora_latest.json")); + m_networkManager->get(QNetworkRequest(url)); +} + +void Aurora::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + + QString fileName = reply->url().fileName(); + if (fileName == "ovation_aurora_latest.json") + { + handleJSON(document); + } + else + { + qDebug() << "Aurora::handleReply: unexpected filename: " << fileName; + } + } + else + { + qDebug() << "Aurora::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "Aurora::handleReply: reply is null"; + } +} + +void Aurora::handleJSON(QJsonDocument& document) +{ + if (document.isObject()) + { + QJsonObject obj = document.object(); + + if (obj.contains(QStringLiteral("coordinates"))) + { + QJsonArray array = obj.value(QStringLiteral("coordinates")).toArray(); + + // Longitude: [0, 359] + // Latitude: [-90, 90] including 0 + QImage image(360, 181, QImage::Format_ARGB32); + image.fill(qRgba(0, 0, 0, 0)); + + for (auto valRef : array) + { + if (valRef.isArray()) + { + QJsonArray coords = valRef.toArray(); + + if (coords.size() == 3) + { + int longitude = coords[0].toInt(); + int latitude = coords[1].toInt(); + int probabilty = coords[2].toInt(); + const int min = 5; // Don't display anything for <5% probabilty + const int alphaMax = 180; // Always slightly transparent + + if ((probabilty > min) && (std::abs(latitude) > 5)) // Ignore data around equator, as can incorrectly predict aurora + { + // Scale from 5%-100% to 256 entry colormap + int index = (int) ((probabilty - min) / (100.0f - min) * 255.0f); + // Make lowest probababilties a bit more transparent + int alpha = index < 25 ? (int)((index / 25.0f) * alphaMax) : alphaMax; + //qDebug() << probabilty << index << alpha; + image.setPixel((longitude + 180) % 360, 180 - (latitude + 90), qRgba(m_colorMap[index*3], m_colorMap[index*3+1], m_colorMap[index*3+2], alpha)); + } + } + else + { + qDebug() << "Aurora::handleJSON: Expected coordinates array to be of length 3"; + } + } + } + + emit dataUpdated(image); + } + else + { + qDebug() << "Aurora::handleJSON: No coordinates"; + } + } + else + { + qDebug() << "Aurora::handleJSON: Expected an object"; + } +} diff --git a/android/app/src/main/cpp/util/aurora.h b/android/app/src/main/cpp/util/aurora.h new file mode 100644 index 0000000..f238a08 --- /dev/null +++ b/android/app/src/main/cpp/util/aurora.h @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2025 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AURORA_H +#define INCLUDE_AURORA_H + +#include +#include +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// Aurora prediction +// Data from https://services.swpc.noaa.gov/ +class SDRBASE_API Aurora : public QObject +{ + Q_OBJECT +protected: + Aurora(); + +public: + + static Aurora* create(const QString& service="noaa.gov"); + + ~Aurora(); + void getDataPeriodically(int periodInMins=30); + +public slots: + void getData(); + +private slots: + void handleReply(QNetworkReply* reply); + +signals: + void dataUpdated(const QImage& data); // Called when new data available. + +private: + + void handleJSON(QJsonDocument& document); + + QTimer m_dataTimer; // Timer for periodic updates + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + + static const unsigned char m_colorMap[256*3]; + +}; + +#endif /* INCLUDE_AURORA_H */ diff --git a/android/app/src/main/cpp/util/average.h b/android/app/src/main/cpp/util/average.h new file mode 100644 index 0000000..5e2d1de --- /dev/null +++ b/android/app/src/main/cpp/util/average.h @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// + +#ifndef _UTIL_AVERAGE_H_ +#define _UTIL_AVERAGE_H_ + +#include + +template +class AverageUtil +{ + public: + AverageUtil() + : m_numSamples(0), m_total(0) + { } + + AverageUtil(T sample) + : m_numSamples(1), m_total(sample) + { } + + void reset() + { + m_numSamples = 0; + m_total = 0; + } + + void operator()(T sample) + { + m_total += sample; + m_numSamples++; + } + + double asDouble() const { return ((double)m_total) / (m_numSamples == 0 ? 1 : m_numSamples); } + float asFloat() const { return ((float)m_total) / (m_numSamples == 0 ? 1 : m_numSamples); } + operator T() const { return m_total / (m_numSamples == 0 ? 1 : m_numSamples); } + T instantAverage() const { return m_total / (m_numSamples == 0 ? 1 : m_numSamples); } + int getNumSamples() const { return m_numSamples; } + + private: + int m_numSamples; + Total m_total; +}; + +#endif /* _UTIL_AVERAGE_H_ */ diff --git a/android/app/src/main/cpp/util/aviationweather.cpp b/android/app/src/main/cpp/util/aviationweather.cpp new file mode 100644 index 0000000..bdc94c7 --- /dev/null +++ b/android/app/src/main/cpp/util/aviationweather.cpp @@ -0,0 +1,245 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "aviationweather.h" + +#include +#include +#include +#include +#include +#include + +AviationWeather::AviationWeather() +{ + connect(&m_timer, &QTimer::timeout, this, &AviationWeather::update); +} + +AviationWeather* AviationWeather::create(const QString& apiKey, const QString& service) +{ + if (service == "checkwxapi.com") + { + if (!apiKey.isEmpty()) + { + return new CheckWXAPI(apiKey); + } + else + { + qDebug() << "AviationWeather::create: An API key is required for: " << service; + return nullptr; + } + } + else + { + qDebug() << "AviationWeather::create: Unsupported service: " << service; + return nullptr; + } +} + +void AviationWeather::getWeatherPeriodically(const QString &icao, int periodInMins) +{ + m_icao = icao; + m_timer.setInterval(periodInMins*60*1000); + m_timer.start(); + update(); +} + +void AviationWeather::update() +{ + getWeather(m_icao); +} + +CheckWXAPI::CheckWXAPI(const QString& apiKey) : + m_apiKey(apiKey) +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &CheckWXAPI::handleReply + ); +} + +CheckWXAPI::~CheckWXAPI() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &CheckWXAPI::handleReply + ); + delete m_networkManager; +} + +void CheckWXAPI::getWeather(const QString &icao) +{ + QUrl url(QString("https://api.checkwx.com/metar/%1/decoded").arg(icao)); + QNetworkRequest req(url); + req.setRawHeader(QByteArray("X-API-Key"), m_apiKey.toUtf8()); + + m_networkManager->get(req); +} + +void CheckWXAPI::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("data"))) + { + QJsonValue val = obj.value(QStringLiteral("data")); + if (val.isArray()) + { + QJsonArray array = val.toArray(); + for (auto mainObjRef : array) + { + QJsonObject mainObj = mainObjRef.toObject(); + METAR metar; + + if (mainObj.contains(QStringLiteral("icao"))) { + metar.m_icao = mainObj.value(QStringLiteral("icao")).toString(); + } + + if (mainObj.contains(QStringLiteral("raw_text"))) { + metar.m_text = mainObj.value(QStringLiteral("raw_text")).toString(); + } + + if (mainObj.contains(QStringLiteral("observed"))) { + metar.m_dateTime = QDateTime::fromString(mainObj.value(QStringLiteral("observed")).toString(), Qt::ISODate); + } + + if (mainObj.contains(QStringLiteral("wind"))) { + QJsonObject windObj = mainObj.value(QStringLiteral("wind")).toObject(); + if (windObj.contains(QStringLiteral("degrees"))) { + metar.m_windDirection = windObj.value(QStringLiteral("degrees")).toDouble(); + } + if (windObj.contains(QStringLiteral("speed_kts"))) { + metar.m_windSpeed = windObj.value(QStringLiteral("speed_kts")).toDouble(); + } + if (windObj.contains(QStringLiteral("wind.gust_kts"))) { + metar.m_windGusts = windObj.value(QStringLiteral("wind.gust_kts")).toDouble(); + } + } + if (mainObj.contains(QStringLiteral("visibility"))) { + QJsonObject visibiltyObj = mainObj.value(QStringLiteral("visibility")).toObject(); + if (visibiltyObj.contains(QStringLiteral("meters"))) { + metar.m_visibility = visibiltyObj.value(QStringLiteral("meters")).toString(); + } + } + + if (mainObj.contains(QStringLiteral("conditions"))) { + QJsonArray conditions = mainObj.value(QStringLiteral("conditions")).toArray(); + for (auto condition : conditions) { + QJsonObject conditionObj = condition.toObject(); + if (conditionObj.contains(QStringLiteral("text"))) { + metar.m_conditions.append(conditionObj.value(QStringLiteral("text")).toString()); + } + } + } + + if (mainObj.contains(QStringLiteral("ceiling"))) { + QJsonObject ceilingObj = mainObj.value(QStringLiteral("ceiling")).toObject(); + if (ceilingObj.contains(QStringLiteral("feet"))) { + metar.m_ceiling = ceilingObj.value(QStringLiteral("feet")).toDouble(); + } + } + + if (mainObj.contains(QStringLiteral("clouds"))) { + QJsonArray clouds = mainObj.value(QStringLiteral("clouds")).toArray(); + for (auto cloud : clouds) { + QJsonObject cloudObj = cloud.toObject(); + // "Clear skies" doesn't have an altitude + if (cloudObj.contains(QStringLiteral("text")) && cloudObj.contains(QStringLiteral("feet"))) { + metar.m_clouds.append(QString("%1 %2 ft").arg(cloudObj.value(QStringLiteral("text")).toString()) + .arg(cloudObj.value(QStringLiteral("feet")).toDouble())); + } else if (cloudObj.contains(QStringLiteral("text"))) { + metar.m_clouds.append(cloudObj.value(QStringLiteral("text")).toString()); + } + } + } + + if (mainObj.contains(QStringLiteral("temperature"))) { + QJsonObject tempObj = mainObj.value(QStringLiteral("temperature")).toObject(); + if (tempObj.contains(QStringLiteral("celsius"))) { + metar.m_temperature = tempObj.value(QStringLiteral("celsius")).toDouble(); + } + } + + if (mainObj.contains(QStringLiteral("dewpoint"))) { + QJsonObject dewpointObj = mainObj.value(QStringLiteral("dewpoint")).toObject(); + if (dewpointObj.contains(QStringLiteral("celsius"))) { + metar.m_dewpoint = dewpointObj.value(QStringLiteral("celsius")).toDouble(); + } + } + + if (mainObj.contains(QStringLiteral("barometer"))) { + QJsonObject pressureObj = mainObj.value(QStringLiteral("barometer")).toObject(); + if (pressureObj.contains(QStringLiteral("hpa"))) { + metar.m_pressure = pressureObj.value(QStringLiteral("hpa")).toDouble(); + } + } + + if (mainObj.contains(QStringLiteral("humidity"))) { + QJsonObject humidityObj = mainObj.value(QStringLiteral("humidity")).toObject(); + if (humidityObj.contains(QStringLiteral("percent"))) { + metar.m_humidity = humidityObj.value(QStringLiteral("percent")).toDouble(); + } + } + + if (mainObj.contains(QStringLiteral("flight_category"))) { + metar.m_flightCateogory = mainObj.value(QStringLiteral("flight_category")).toString(); + } + + if (!metar.m_icao.isEmpty()) { + emit weatherUpdated(metar); + } else { + qDebug() << "CheckWXAPI::handleReply: object doesn't contain icao: " << mainObj; + } + } + } + else + { + qDebug() << "CheckWXAPI::handleReply: data isn't an array: " << obj; + } + } + else + { + qDebug() << "CheckWXAPI::handleReply: Object doesn't contain data: " << obj; + } + } + else + { + qDebug() << "CheckWXAPI::handleReply: Document is not an object: " << document; + } + } + else + { + qDebug() << "CheckWXAPI::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "CheckWXAPI::handleReply: reply is null"; + } +} diff --git a/android/app/src/main/cpp/util/aviationweather.h b/android/app/src/main/cpp/util/aviationweather.h new file mode 100644 index 0000000..4fa06f7 --- /dev/null +++ b/android/app/src/main/cpp/util/aviationweather.h @@ -0,0 +1,152 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AVIATIONWEATHER_H +#define INCLUDE_AVIATIONWEATHER_H + +#include +#include + +#include "export.h" + +#include + +#include + +class QNetworkAccessManager; +class QNetworkReply; + +// Aviation Weather API wrapper +// Allows METAR information to be obtained for a given airport +// Currently supports checkwxapi.com +class SDRBASE_API AviationWeather : public QObject +{ + Q_OBJECT + +public: + struct METAR { + QString m_icao; // ICAO of reporting station/airport + QString m_text; // Raw METAR text + QDateTime m_dateTime; // Date&time of observation + float m_windDirection; // Direction wind is blowing from, in degrees + float m_windSpeed; // Wind speed in knots + float m_windGusts; // Wind gusts in knots + QString m_visibility; // Visibility in metres (may be non-numeric) + QStringList m_conditions; // Weather conditions (Rain, snow) + float m_ceiling; // Ceiling in feet + QStringList m_clouds; // Cloud types and altitudes + float m_temperature; // Air temperature in Celsius + float m_dewpoint; // Dewpoint in Celsuius + float m_pressure; // Air pressure in hPa/mb + float m_humidity; // Humidity in % + QString m_flightCateogory; // VFR/MVFR/IFR/LIFR + + METAR() : + m_windDirection(NAN), + m_windSpeed(NAN), + m_windGusts(NAN), + m_ceiling(NAN), + m_temperature(NAN), + m_dewpoint(NAN), + m_pressure(NAN), + m_humidity(NAN) + { + } + + QString decoded(const QString joinArg="\n") const + { + QStringList s; + if (m_dateTime.isValid()) { + s.append(QString("Observed: %1").arg(m_dateTime.toString())); + } + if (!std::isnan(m_windDirection) && !std::isnan(m_windSpeed)) { + s.append(QString("Wind: %1%2 / %3 knts").arg(m_windDirection).arg(QChar(0xb0)).arg(m_windSpeed)); + } + if (!std::isnan(m_windGusts) ) { + s.append(QString("Gusts: %1 knts").arg(m_windGusts)); + } + if (!m_visibility.isEmpty()) { + s.append(QString("Visibility: %1 metres").arg(m_visibility)); + } + if (!m_conditions.isEmpty()) { + s.append(QString("Conditions: %1").arg(m_conditions.join(", "))); + } + if (!std::isnan(m_ceiling)) { + s.append(QString("Ceiling: %1 ft").arg(m_ceiling)); + } + if (!m_clouds.isEmpty()) { + s.append(QString("Clouds: %1").arg(m_clouds.join(", "))); + } + if (!std::isnan(m_temperature)) { + s.append(QString("Temperature: %1 %2C").arg(m_temperature).arg(QChar(0xb0))); + } + if (!std::isnan(m_dewpoint)) { + s.append(QString("Dewpoint: %1 %2C").arg(m_dewpoint).arg(QChar(0xb0))); + } + if (!std::isnan(m_pressure)) { + s.append(QString("Pressure: %1 hPa").arg(m_pressure)); + } + if (!std::isnan(m_humidity)) { + s.append(QString("Humidity: %1 %").arg(m_humidity)); + } + if (!m_flightCateogory.isEmpty()) { + s.append(QString("Category: %1").arg(m_flightCateogory)); + } + return s.join(joinArg); + } + }; + +protected: + AviationWeather(); + +public: + static AviationWeather* create(const QString& apiKey, const QString& service="checkwxapi.com"); + + virtual void getWeather(const QString &icao) = 0; + void getWeatherPeriodically(const QString &, int periodInMins); + +public slots: + void update(); + +signals: + void weatherUpdated(const METAR &metar); // Called when new data available. If no value is available, parameter will be NAN + +private: + QTimer m_timer; // Timer for periodic updates + QString m_icao; // Saved airport ICAO for period updates + +}; + +class SDRBASE_API CheckWXAPI : public AviationWeather { + Q_OBJECT +public: + + CheckWXAPI(const QString& apiKey); + ~CheckWXAPI(); + virtual void getWeather(const QString &icao) override; + +private: + + QString m_apiKey; + QNetworkAccessManager *m_networkManager; + +public slots: + void handleReply(QNetworkReply* reply); + +}; + +#endif /* INCLUDE_AVIATIONWEATHER_H */ diff --git a/android/app/src/main/cpp/util/ax25.cpp b/android/app/src/main/cpp/util/ax25.cpp new file mode 100644 index 0000000..625eae8 --- /dev/null +++ b/android/app/src/main/cpp/util/ax25.cpp @@ -0,0 +1,220 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "ax25.h" + +// CRC is assumed to be correct (checked in packetdemodsink) +bool AX25Packet::decode(QByteArray packet) +{ + int i, j; + char destAddress[7]; + unsigned char destSSID; + char sourceAddress[7]; + unsigned char sourceSSID; + char repeaterAddress[7]; + unsigned char repeaterSSID; + unsigned char ssid; + unsigned char control; + + // Check for minimum size packet. Addresses, control and CRC + if (packet.size() < 7+7+1+2) + return false; + + // Address - ASCII shifted right one bit + for (i = 0; i < 6; i++) + destAddress[i] = (packet[i] >> 1) & 0x7f; + destAddress[6] = '\0'; + destSSID = packet[6]; + for (i = 0; i < 6; i++) + sourceAddress[i] = (packet[7+i] >> 1) & 0x7f; + sourceAddress[6] = '\0'; + sourceSSID = packet[13]; + + // From = source address + m_from = QString(sourceAddress).trimmed(); + ssid = (sourceSSID >> 1) & 0xf; + if (ssid != 0) + m_from = QString("%1-%2").arg(m_from).arg(ssid); + + // To = destination address + m_to = QString(destAddress).trimmed(); + ssid = (destSSID >> 1) & 0xf; + if (ssid != 0) + m_to = QString("%1-%2").arg(m_to).arg(ssid); + + // List of repeater addresses for via field + m_via = QString(""); + i = 13; + int incomingViaStrIdx = -1; + while ((packet[i] & 1) == 0) + { + i++; + for (j = 0; j < 6; j++) + repeaterAddress[j] = (packet[i+j] >> 1) & 0x7f; + repeaterAddress[j] = '\0'; + i += 6; + repeaterSSID = packet[i]; + ssid = (repeaterSSID >> 1) & 0xf; + QString repeater = QString(repeaterAddress).trimmed(); + QString ssidString = (ssid != 0) ? QString("%2-%3").arg(repeater).arg(ssid) : QString(repeater); + + if (!m_via.isEmpty()) + m_via.append(','); + m_via.append(ssidString); + + if (packet[i] & 0x80) + incomingViaStrIdx = m_via.length(); + } + if (incomingViaStrIdx >= 0) + m_via.insert(incomingViaStrIdx, "*"); + + i++; + // Control can be 1 or 2 bytes - how to know if 2? + // I, U and S frames + control = packet[i++]; + if ((control & 1) == 0) + m_type = QString("I"); + else if ((control & 3) == 3) + { + // See figure 4.4 of AX.25 spec + switch (control & 0xef) + { + case 0x6f: + m_type = QString("SABME"); + break; + case 0x2f: + m_type = QString("SABM"); + break; + case 0x43: + m_type = QString("DISC"); + break; + case 0x0f: + m_type = QString("DM"); + break; + case 0x63: + m_type = QString("UA"); + break; + case 0x87: + m_type = QString("FR"); + break; + case 0x03: + m_type = QString("UI"); + break; + case 0xaf: + m_type = QString("XID"); + break; + case 0xe3: + m_type = QString("TEST"); + break; + default: + m_type = QString("U"); + break; + } + } + else + m_type = QString("S"); + // APRS packets use UI frames, which are a subype of U frames + // Only I and UI frames have Layer 3 Protocol ID (PID). + if ((m_type == "I") || (m_type == "UI")) + m_pid = QString("%1").arg(((unsigned)packet[i++]) & 0xff, 2, 16, QLatin1Char('0')); + else + m_pid = QString(""); + int infoStart, infoEnd; + infoStart = i; + infoEnd = packet.size()-2-i; + QByteArray info(packet.mid(infoStart, infoEnd)); + m_data = info; + m_dataHex = QString(info.toHex()); + + return true; +} + +bool AX25Packet::ssid(QByteArray& b, int i, int len, uint8_t& ssid) +{ + if (b[i] == '-') + { + if (len > i + 1) + { + ssid = b[i+1] - '0'; + if ((len > i + 2) && isdigit(b[i+2])) { + ssid = (ssid*10) + (b[i+2] - '0'); + } + if (ssid >= 16) + { + // FIXME: APRS-IS seems to support 2 letter SSIDs + // These can't be sent over RF, as not enough bits in AX.25 packet + qDebug() << "AX25Packet::ssid: SSID greater than 15 not supported"; + ssid = 0; + return false; + } + else + { + return true; + } + } + else + { + qDebug() << "AX25Packet::ssid: SSID number missing"; + return false; + } + } + else + return false; +} + +QByteArray AX25Packet::encodeAddress(QString address, uint8_t crrl) +{ + int len; + int i; + QByteArray encodedAddress; + QByteArray b; + uint8_t ssid = 0; + bool hyphenSeen = false; + + len = address.length(); + b = address.toUtf8(); + ssid = 0; + for (i = 0; i < 6; i++) + { + if ((i < len) && !hyphenSeen) + { + if (b[i] == '-') + { + AX25Packet::ssid(b, i, len, ssid); + hyphenSeen = true; + encodedAddress.append(' ' << 1); + } + else + { + encodedAddress.append(b[i] << 1); + } + } + else + { + encodedAddress.append(' ' << 1); + } + } + if (b[i] == '-') + { + AX25Packet::ssid(b, i, len, ssid); + } + encodedAddress.append(crrl | (ssid << 1)); + + return encodedAddress; +} diff --git a/android/app/src/main/cpp/util/ax25.h b/android/app/src/main/cpp/util/ax25.h new file mode 100644 index 0000000..d2c70c0 --- /dev/null +++ b/android/app/src/main/cpp/util/ax25.h @@ -0,0 +1,45 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020-2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AX25_H +#define INCLUDE_AX25_H + +#include +#include + +#include "export.h" + +struct SDRBASE_API AX25Packet { + QString m_from; + QString m_to; + QString m_via; + QString m_type; + QString m_pid; + QByteArray m_data; + QString m_dataHex; + + bool decode(QByteArray packet); + + static bool ssid(QByteArray& b, int i, int len, uint8_t& ssid); + static QByteArray encodeAddress(QString address, uint8_t crrl=0); + +}; + +#endif // INCLUDE_AX25_H diff --git a/android/app/src/main/cpp/util/azel.cpp b/android/app/src/main/cpp/util/azel.cpp new file mode 100644 index 0000000..01a6b9e --- /dev/null +++ b/android/app/src/main/cpp/util/azel.cpp @@ -0,0 +1,152 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "azel.h" + +#include + +// Calculate cartesian distance between two points +double AzEl::cartDistance(const AzElPoint& a, const AzElPoint& b) const +{ + double dx = b.m_cart.m_x - a.m_cart.m_x; + double dy = b.m_cart.m_y - a.m_cart.m_y; + double dz = b.m_cart.m_z - a.m_cart.m_z; + return std::sqrt(dx*dx + dy*dy + dz*dz); +} + +// Calculate vector difference then normalise the result +bool AzEl::normVectorDiff(const AzElCartesian& a, const AzElCartesian& b, AzElCartesian& n) const +{ + n.m_x = b.m_x - a.m_x; + n.m_y = b.m_y - a.m_y; + n.m_z = b.m_z - a.m_z; + double distance = std::sqrt(n.m_x*n.m_x + n.m_y*n.m_y + n.m_z*n.m_z); + if (distance > 0.0f) + { + n.m_x = n.m_x / distance; + n.m_y = n.m_y / distance; + n.m_z = n.m_z / distance; + return true; + } + else + { + return false; + } +} + +// Convert geodetic latitude (as given by GPS) to geocentric latitude (angle from centre of Earth between the point and equator) +// Both in radians. +// https://en.wikipedia.org/wiki/Latitude#Geocentric_latitude +double AzEl::geocentricLatitude(double latRad) const +{ + double e2 = 0.00669437999014; + return std::atan((1.0 - e2) * std::tan(latRad)); +} + +// Earth radius for a given latitude, as it's not quite spherical +// http://en.wikipedia.org/wiki/Earth_radius +double AzEl::earthRadiusInMetres(double geodeticLatRad) const +{ + double equatorialRadius = 6378137.0; + double polarRadius = 6356752.3; + double cosLat = std::cos(geodeticLatRad); + double sinLat = std::sin(geodeticLatRad); + double t1 = equatorialRadius * equatorialRadius * cosLat; + double t2 = polarRadius * polarRadius * sinLat; + double t3 = equatorialRadius * cosLat; + double t4 = polarRadius * sinLat; + return std::sqrt((t1*t1 + t2*t2)/(t3*t3 + t4*t4)); +} + +// Convert spherical coordinate to cartesian. Also calculates radius and a normal vector +void AzEl::sphericalToCartesian(AzElPoint& point) +{ + // First calculate cartesian coords for point on Earth's surface + double latRad = point.m_spherical.m_latitude * M_PI/180.0; + double longRad = point.m_spherical.m_longitude * M_PI/180.0; + point.m_radius = earthRadiusInMetres(latRad); + double clat = geocentricLatitude(latRad); + double cosLong = cos(longRad); + double sinLong = sin(longRad); + double cosLat = cos(clat); + double sinLat = sin(clat); + + point.m_cart.m_x = point.m_radius * cosLong * cosLat; + point.m_cart.m_y = point.m_radius * sinLong * cosLat; + point.m_cart.m_z = point.m_radius * sinLat; + + // Calculate normal vector at surface + double cosGLat = std::cos(latRad); + double sinGLat = std::sin(latRad); + + point.m_norm.m_x = cosGLat * cosLong; + point.m_norm.m_y = cosGLat * sinLong; + point.m_norm.m_z = sinGLat; + + // Add altitude along normal vector + point.m_cart.m_x += point.m_spherical.m_altitude * point.m_norm.m_x; + point.m_cart.m_y += point.m_spherical.m_altitude * point.m_norm.m_y; + point.m_cart.m_z += point.m_spherical.m_altitude * point.m_norm.m_z; +} + +// Calculate azimuth of target from location +void AzEl::calcAzimuth() +{ + AzElPoint bRot; + + // Rotate so location is at lat=0, long=0 + bRot.m_spherical.m_latitude = m_target.m_spherical.m_latitude; + bRot.m_spherical.m_longitude = m_target.m_spherical.m_longitude - m_location.m_spherical.m_longitude; + bRot.m_spherical.m_altitude = m_target.m_spherical.m_altitude; + sphericalToCartesian(bRot); + + double aLat = geocentricLatitude(-m_location.m_spherical.m_latitude * M_PI / 180.0); + double aCos = std::cos(aLat); + double aSin = std::sin(aLat); + + //double bx = (bRot.m_cart.m_x * aCos) - (bRot.m_cart.m_z * aSin); + double by = bRot.m_cart.m_y; + double bz = (bRot.m_cart.m_x * aSin) + (bRot.m_cart.m_z * aCos); + + if (bz*bz + by*by > 1e-6) + { + double theta = std::atan2(bz, by) * 180.0 / M_PI; + m_azimuth = 90.0 - theta; + if (m_azimuth < 0.0) + m_azimuth += 360.0; + else if (m_azimuth > 360.0) + m_azimuth -= 360.0; + } + else + m_azimuth = 0.0; +} + +// Calculate elevation of target from location +void AzEl::calcElevation() +{ + AzElCartesian bma; + if (normVectorDiff(m_location.m_cart, m_target.m_cart, bma)) + { + m_elevation = 90.0 - (180.0/M_PI) * std::acos(bma.m_x * m_location.m_norm.m_x + + bma.m_y * m_location.m_norm.m_y + + bma.m_z * m_location.m_norm.m_z); + } + else + m_elevation = 0.0; +} diff --git a/android/app/src/main/cpp/util/azel.h b/android/app/src/main/cpp/util/azel.h new file mode 100644 index 0000000..01ef8aa --- /dev/null +++ b/android/app/src/main/cpp/util/azel.h @@ -0,0 +1,133 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AZ_EL_H +#define INCLUDE_AZ_EL_H + +#include "export.h" + +// Spherical coordinate +struct SDRBASE_API AzElSpherical +{ + double m_latitude; // Degrees + double m_longitude; // Degrees + double m_altitude; // Metres +}; + +// Cartesian coordinate +struct SDRBASE_API AzElCartesian +{ + double m_x; // Metres + double m_y; // Metres + double m_z; // Metres +}; + +struct SDRBASE_API AzElPoint +{ + AzElSpherical m_spherical; // Spherical coordinates + AzElCartesian m_cart; // Cartesian coordinates + AzElCartesian m_norm; // Normal vector on surface of sphere + double m_radius; // Radius from centre of sphere to the point +}; + + +// Class to calculate azimuth, elevation and distance between two points specified +// by latitude, longitude and altitude +// See: https://doncross.net/geocalc +class SDRBASE_API AzEl +{ +public: + // Location is the point we are looking from + void setLocation(double latitude, double longitude, double altitude) + { + m_location.m_spherical.m_latitude = latitude; + m_location.m_spherical.m_longitude = longitude; + m_location.m_spherical.m_altitude = altitude; + sphericalToCartesian(m_location); + } + + // Target is the point we are looking at + void setTarget(double latiude, double longitude, double altitude) + { + m_target.m_spherical.m_latitude = latiude; + m_target.m_spherical.m_longitude = longitude; + m_target.m_spherical.m_altitude = altitude; + sphericalToCartesian(m_target); + } + + AzElSpherical getLocationSpherical() const + { + return m_location.m_spherical; + } + + AzElCartesian getLocationCartesian() const + { + return m_location.m_cart; + } + + AzElSpherical getTargetSpherical() const + { + return m_target.m_spherical; + } + + AzElCartesian getTargetCartesian() const + { + return m_target.m_cart; + } + + void calculate() + { + m_distance = cartDistance(m_location, m_target); + calcAzimuth(); + calcElevation(); + } + + double getDistance() + { + return m_distance; + } + + double getAzimuth() + { + return m_azimuth; + } + + double getElevation() + { + return m_elevation; + } + +private: + + double cartDistance(const AzElPoint& a, const AzElPoint& b) const; + bool normVectorDiff(const AzElCartesian& a, const AzElCartesian& b, AzElCartesian& n) const; + double geocentricLatitude(double latRad) const; + double earthRadiusInMetres(double geodeticLatRad) const; + void sphericalToCartesian(AzElPoint& point); + void calcAzimuth(); + void calcElevation(); + + AzElPoint m_location; + AzElPoint m_target; + + double m_azimuth; + double m_elevation; + double m_distance; + +}; + +#endif diff --git a/android/app/src/main/cpp/util/baudot.cpp b/android/app/src/main/cpp/util/baudot.cpp new file mode 100644 index 0000000..fa1c09d --- /dev/null +++ b/android/app/src/main/cpp/util/baudot.cpp @@ -0,0 +1,372 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "baudot.h" + +// https://en.wikipedia.org/wiki/Baudot_code +// We use < for FIGS and > for LTRS and ^ for Cyrillic +// Unicode used for source file encoding + +const QStringList Baudot::m_ita2Letter = { + "\0", "E", "\n", "A", " ", "S", "I", "U", + "\r", "D", "R", "J", "N", "F", "C", "K", + "T", "Z", "L", "W", "H", "Y", "P", "Q", + "O", "B", "G", "<", "M", "X", "V", ">" +}; + +const QStringList Baudot::m_ita2Figure = { + "\0", "3", "\n", "-", " ", "\'", "8", "7", + "\r", "\x5", "4", "\a", ",", "!", ":", "(", + "5", "+", ")", "2", "£", "6", "0", "1", + "9", "?", "&", "<", ".", "/", "=", ">" +}; + +const QStringList Baudot::m_ukLetter = { + "\0", "A", "E", "/", "Y", "U", "I", "O", + "<", "J", "G", "H", "B", "C", "F", "D", + " ", "-", "X", "Z", "S", "T", "W", "V", + "\b", "K", "M", "L", "R", "Q", "N", "P" +}; + +const QStringList Baudot::m_ukFigure = { + "\0", "1", "2", "⅟", "3", "4", "³⁄", "5", + " ", "6", "7", "¹", "8", "9", "⁵⁄", "0", + ">", ".", "⁹⁄", ":", "⁷⁄", "²", "?", "\'", + "\b", "(", ")", "=", "-", "/", "£", "+" +}; + +const QStringList Baudot::m_europeanLetter = { + "\0", "A", "E", "É", "Y", "U", "I", "O", + "<", "J", "G", "H", "B", "C", "F", "D", + " ", "t", "X", "Z", "S", "T", "W", "V", + "\b", "K", "M", "L", "R", "Q", "N", "P" +}; + +const QStringList Baudot::m_europeanFigure = { + "\0", "1", "2", "&", "3", "4", "º", "5", + " ", "6", "7", "H̱", "8", "9", "F̱", "0", + ">", ".", ",", ":", ";", "!", "?", "\'", + "\b", "(", ")", "=", "-", "/", "№", "%" +}; + +const QStringList Baudot::m_usLetter = { + "\0", "E", "\n", "A", " ", "S", "I", "U", + "\r", "D", "R", "J", "N", "F", "C", "K", + "T", "Z", "L", "W", "H", "Y", "P", "Q", + "O", "B", "G", "<", "M", "X", "V", ">" +}; + +const QStringList Baudot::m_usFigure = { + "\0", "3", "\n", "-", " ", "\a", "8", "7", + "\r", "\x5", "4", "\'", ",", "!", ":", "(", + "5", "\"", ")", "2", "#", "6", "0", "1", + "9", "?", "&", "<", ".", "/", ";", ">" +}; + +const QStringList Baudot::m_russianLetter = { + "\0", "Е", "\n", "А", " ", "С", "И", "У", + "\r", "Д", "Р", "Й", "Ч", "Ф", "Ц", "К", + "Т", "З", "Л", "В", "Х", "Ы", "П", "Я", + "О", "Б", "Г", "<", "М", "Ь", "Ж", ">" +}; + +const QStringList Baudot::m_russianFigure = { + "\0", "3", "\n", "-", " ", "\'", "8", "7", + "\r", "Ч", "4", "Ю", ",", "Э", ":", "(", + "5", "+", ")", "2", "Щ", "6", "0", "1", + "9", "?", "Ш", "<", ".", "/", ";", ">" +}; + +const QStringList Baudot::m_murrayLetter = { + " ", "E", "?", "A", ">", "S", "I", "U", + "\n", "D", "R", "J", "N", "F", "C", "K", + "T", "Z", "L", "W", "H", "Y", "P", "Q", + "O", "B", "G", "<", "M", "X", "V", "\b" +}; + +const QStringList Baudot::m_murrayFigure = { + " ", "3", "?", " ", ">", "'", "8", "7", + "\n", "²", "4", "⁷⁄", "-", "⅟", "(", "⁹⁄", + "5", ".", "/", "2", "⁵⁄", "6", "0", "1", + "9", "?", "³⁄", "<", ",", "£", ")", "\b" +}; + +BaudotDecoder::BaudotDecoder() +{ + setCharacterSet(Baudot::ITA2); + setUnshiftOnSpace(false); + init(); +} + +void BaudotDecoder::setCharacterSet(Baudot::CharacterSet characterSet) +{ + m_characterSet = characterSet; + switch (m_characterSet) + { + case Baudot::ITA2: + m_letters = Baudot::m_ita2Letter; + m_figures = Baudot::m_ita2Figure; + break; + case Baudot::UK: + m_letters = Baudot::m_ukLetter; + m_figures = Baudot::m_ukFigure; + break; + case Baudot::EUROPEAN: + m_letters = Baudot::m_europeanLetter; + m_figures = Baudot::m_europeanFigure; + break; + case Baudot::US: + m_letters = Baudot::m_usLetter; + m_figures = Baudot::m_usFigure; + break; + case Baudot::RUSSIAN: + m_letters = Baudot::m_russianLetter; + m_figures = Baudot::m_russianFigure; + break; + case Baudot::MURRAY: + m_letters = Baudot::m_murrayLetter; + m_figures = Baudot::m_murrayFigure; + break; + default: + qDebug() << "BaudotDecoder::BaudotDecoder: Unsupported character set " << m_characterSet; + m_letters = Baudot::m_ita2Letter; + m_figures = Baudot::m_ita2Figure; + m_characterSet = Baudot::ITA2; + break; + } +} + +void BaudotDecoder::setUnshiftOnSpace(bool unshiftOnSpace) +{ + m_unshiftOnSpace = unshiftOnSpace; +} + +void BaudotDecoder::init() +{ + m_figure = false; +} + +QString BaudotDecoder::decode(char bits) +{ + QString c = m_figure ? m_figures[(int)bits] : m_letters[(int)bits]; + + if ((c == ">") || (m_unshiftOnSpace && (c == " "))) + { + // Switch to letters + m_figure = false; + if (m_characterSet == Baudot::RUSSIAN) { + m_letters = Baudot::m_ita2Letter; + } + } + if (c == "<") + { + // Switch to figures + m_figure = true; + } + if ((m_characterSet == Baudot::RUSSIAN) && (c == "\0")) + { + // Switch to Cyrillic + m_figure = false; + m_letters = Baudot::m_russianLetter; + c = "^"; + } + + return c; +} + +BaudotEncoder::BaudotEncoder() +{ + setCharacterSet(Baudot::ITA2); + setUnshiftOnSpace(false); + setMsbFirst(false); + setStartBits(1); + setStopBits(1); + init(); +} + +void BaudotEncoder::setCharacterSet(Baudot::CharacterSet characterSet) +{ + m_characterSet = characterSet; + switch (m_characterSet) + { + case Baudot::ITA2: + m_chars[LETTERS] = Baudot::m_ita2Letter; + m_chars[FIGURES] = Baudot::m_ita2Figure; + break; + case Baudot::UK: + m_chars[LETTERS] = Baudot::m_ukLetter; + m_chars[FIGURES] = Baudot::m_ukFigure; + break; + case Baudot::EUROPEAN: + m_chars[LETTERS] = Baudot::m_europeanLetter; + m_chars[FIGURES] = Baudot::m_europeanFigure; + break; + case Baudot::US: + m_chars[LETTERS] = Baudot::m_usLetter; + m_chars[FIGURES] = Baudot::m_usFigure; + break; + case Baudot::RUSSIAN: + m_chars[LETTERS] = Baudot::m_ita2Letter; + m_chars[FIGURES] = Baudot::m_russianFigure; + break; + case Baudot::MURRAY: + m_chars[LETTERS] = Baudot::m_murrayLetter; + m_chars[FIGURES] = Baudot::m_murrayFigure; + break; + default: + qDebug() << "BaudotEncoder::BaudotEncoder: Unsupported character set " << m_characterSet; + m_chars[LETTERS] = Baudot::m_ita2Letter; + m_chars[FIGURES] = Baudot::m_ita2Figure; + m_characterSet = Baudot::ITA2; + break; + } + m_chars[(int)CYRILLIC] = Baudot::m_russianLetter; +} + +void BaudotEncoder::setUnshiftOnSpace(bool unshiftOnSpace) +{ + m_unshiftOnSpace = unshiftOnSpace; +} + +void BaudotEncoder::setMsbFirst(bool msbFirst) +{ + m_msbFirst = msbFirst; +} + +// startBits should be 0 or 1 +void BaudotEncoder::setStartBits(int startBits) +{ + m_startBits = startBits; +} + +// stopBits should be 0, 1 or 2 +void BaudotEncoder::setStopBits(int stopBits) +{ + m_stopBits = stopBits; +} + +void BaudotEncoder::init() +{ + m_page = LETTERS; +} + +bool BaudotEncoder::encode(QChar c, unsigned &bits, unsigned int &bitCount) +{ + bits = 0; + bitCount = 0; + + // Only upper case is supported + c = c.toUpper(); + QString s(c); + + if (s == '>') + { + addCode(bits, bitCount, m_chars[m_page].indexOf(s)); + m_page = LETTERS; + return true; + } + else if (s == '<') + { + addCode(bits, bitCount, m_chars[m_page].indexOf(s)); + m_page = FIGURES; + return true; + } + else if ((m_characterSet == Baudot::RUSSIAN) && (s == '\0')) + { + addCode(bits, bitCount, m_chars[m_page].indexOf(s)); + m_page = CYRILLIC; + return true; + } + + // We could create reverse look-up tables to speed this up, but it's only 200 baud... + + // Is character in current page? If so, use that, as it avoids switching + if (m_chars[m_page].contains(s)) + { + addCode(bits, bitCount, m_chars[m_page].indexOf(s)); + return true; + } + else + { + // Look for character in other pages + const QString switchPage[] = { ">", "<", "\0" }; + + for (int page = m_page == LETTERS ? 1 : 0; page < ((m_characterSet == Baudot::RUSSIAN) ? 3 : 2); page++) + { + if (m_chars[page].contains(s)) + { + // Switch to page + addCode(bits, bitCount, m_chars[m_page].indexOf(switchPage[page])); + m_page = (BaudotEncoder::Page)page; + + addCode(bits, bitCount, m_chars[m_page].indexOf(s)); + return true; + } + } + } + + return false; +} + +void BaudotEncoder::addCode(unsigned& bits, unsigned int& bitCount, unsigned int code) const +{ + const unsigned int codeLen = 5; + + addStartBits(bits, bitCount); + code = reverseBits(code, codeLen); + addBits(bits, bitCount, code, codeLen); + addStopBits(bits, bitCount); +} + +void BaudotEncoder::addStartBits(unsigned& bits, unsigned int& bitCount) const +{ + // Start bit is 0 + addBits(bits, bitCount, 0, m_startBits); +} + +void BaudotEncoder::addStopBits(unsigned& bits, unsigned int& bitCount) const +{ + // Stop bit is 1 + addBits(bits, bitCount, ((1 << m_stopBits)) - 1, m_stopBits); +} + +void BaudotEncoder::addBits(unsigned& bits, unsigned int& bitCount, int data, int count) const +{ + bits |= data << bitCount; + bitCount += count; +} + +unsigned BaudotEncoder::reverseBits(unsigned bits, unsigned int count) const +{ + if (m_msbFirst) { + return BaudotEncoder::reverse(bits) >> (sizeof(unsigned int) * 8 - count); + } else { + return bits; + } +} + +unsigned int BaudotEncoder::reverse(unsigned int x) +{ + x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1)); + x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2)); + x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4)); + x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8)); + + return((x >> 16) | (x << 16)); +} diff --git a/android/app/src/main/cpp/util/baudot.h b/android/app/src/main/cpp/util/baudot.h new file mode 100644 index 0000000..986059e --- /dev/null +++ b/android/app/src/main/cpp/util/baudot.h @@ -0,0 +1,117 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// Copyright (C) 2020-2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_BAUDOT_H +#define INCLUDE_UTIL_BAUDOT_H + +#include +#include +#include + +#include "export.h" + +class SDRBASE_API Baudot { + +public: + + enum CharacterSet { + ITA2, + UK, + EUROPEAN, + US, + RUSSIAN, // MTK-2 + MURRAY + }; + + // QString used for fractions in figure set + static const QStringList m_ita2Letter; + static const QStringList m_ita2Figure; + static const QStringList m_ukLetter; + static const QStringList m_ukFigure; + static const QStringList m_europeanLetter; + static const QStringList m_europeanFigure; + static const QStringList m_usLetter; + static const QStringList m_usFigure; + static const QStringList m_russianLetter; + static const QStringList m_russianFigure; + static const QStringList m_murrayLetter; + static const QStringList m_murrayFigure; + +}; + +class SDRBASE_API BaudotDecoder { + +public: + + BaudotDecoder(); + void setCharacterSet(Baudot::CharacterSet characterSet = Baudot::ITA2); + void setUnshiftOnSpace(bool unshiftOnSpace); + void init(); + QString decode(char bits); + +private: + + Baudot::CharacterSet m_characterSet; + bool m_unshiftOnSpace; + QStringList m_letters; + QStringList m_figures; + bool m_figure; + +}; + +class SDRBASE_API BaudotEncoder { + +public: + + BaudotEncoder(); + void setCharacterSet(Baudot::CharacterSet characterSet = Baudot::ITA2); + void setUnshiftOnSpace(bool unshiftOnSpace); + void setMsbFirst(bool msbFirst); + void setStartBits(int startBits); + void setStopBits(int stopBits); + void init(); + bool encode(QChar c, unsigned& bits, unsigned int &bitCount); + +private: + + void addCode(unsigned& bits, unsigned int& bitCount, unsigned int code) const; + void addStartBits(unsigned int& bits, unsigned int& bitCount) const; + void addStopBits(unsigned int& bits, unsigned int& bitCount) const; + void addBits(unsigned int& bits, unsigned int& bitCount, int data, int count) const; + unsigned reverseBits(unsigned int bits, unsigned int count) const; + static unsigned reverse(unsigned int bits); + + Baudot::CharacterSet m_characterSet; + bool m_unshiftOnSpace; + QStringList m_chars[3]; + enum Page { + LETTERS, + FIGURES, + CYRILLIC + } m_page; + bool m_msbFirst; + int m_startBits; + int m_stopBits; + +}; + +#endif // INCLUDE_UTIL_BAUDOT_H + diff --git a/android/app/src/main/cpp/util/bitfieldindex.h b/android/app/src/main/cpp/util/bitfieldindex.h new file mode 100644 index 0000000..1d5428d --- /dev/null +++ b/android/app/src/main/cpp/util/bitfieldindex.h @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _UTIL_BITFIELDINDEX_H_ +#define _UTIL_BITFIELDINDEX_H_ + +#include + +template +struct BitfieldIndex +{ + uint32_t v : size; + + BitfieldIndex() : v(0) {} + BitfieldIndex(int i) { v = i; } + + BitfieldIndex& operator=(const BitfieldIndex& rhs) { v = rhs.v; return *this; } + BitfieldIndex& operator=(const int& rhi) { v = rhi; return *this; } + BitfieldIndex& operator++() { v++; return *this; } + BitfieldIndex operator++(int) { BitfieldIndex x(*this); ++(*this); return x; } + BitfieldIndex& operator+=(const BitfieldIndex& b) { v += b.v; return *this; } + BitfieldIndex& operator-=(const BitfieldIndex& b) { v -= b.v; return *this; } + BitfieldIndex& operator+=(int i) { v += i; return *this; } + BitfieldIndex& operator-=(int i) { v -= i; return *this; } + BitfieldIndex operator+(const BitfieldIndex& b) const { BitfieldIndex x(*this); x.v += b.v; return x; } + BitfieldIndex operator-(const BitfieldIndex& b) const { BitfieldIndex x(*this); x.v -= b.v; return x; } + BitfieldIndex operator+(int i) const { BitfieldIndex x(*this); x.v += i; return x; } + BitfieldIndex operator-(int i) const { BitfieldIndex x(*this); x.v -= i; return x; } + + operator int() const { return v; } +}; + +template +BitfieldIndex operator+(const BitfieldIndex &a, const BitfieldIndex &b) +{ + BitfieldIndex x; + x.v = a.v + b.v; + return x; +} + +template +BitfieldIndex operator-(const BitfieldIndex &a, const BitfieldIndex &b) +{ + BitfieldIndex x; + x.v = a.v - b.v; + return x; +} + +#endif // _UTIL_BITFIELDINDEX_H_ diff --git a/android/app/src/main/cpp/util/callsign.cpp b/android/app/src/main/cpp/util/callsign.cpp new file mode 100644 index 0000000..e3136f4 --- /dev/null +++ b/android/app/src/main/cpp/util/callsign.cpp @@ -0,0 +1,180 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// This source code file was last time modified by Arvo ES1JA on January 5th, 2019 +// All changes are shown in the patch file coming together with the full JTDX source code. + +#include "callsign.h" + +const QRegularExpression Callsign::valid_callsign_regexp {R"([2,4-9]{1,1}[A-Z]{1,1}[0-9]{1,4}|[3]{1,1}[A-Z]{1,2}[0-9]{1,4}|[A-Z]{1,2}[0-9]{1,4})"}; +const QRegularExpression Callsign::prefix_re {R"(^(?:(?[2-9]{0,1}[A-Z]{1,2}[0-9]{1,4})?)?)"}; + +Q_GLOBAL_STATIC(Callsign, callsign) +Callsign *Callsign::instance() +{ + return callsign; +} + +Callsign::Callsign() +{ + m_countryDat.init(); + m_countryDat.load(); +} + +Callsign::~Callsign() +{} + +bool Callsign::is_callsign (QString const& callsign) +{ + if ((!callsign.at(1).isDigit() && callsign.size () == 2) || callsign == "F" || callsign == "G" || callsign == "I" || callsign == "K" || callsign == "W") + { + auto call = callsign + "0"; + return call.contains (valid_callsign_regexp); + } + else + { + return callsign.contains (valid_callsign_regexp); + } +} + +bool Callsign::is_compound_callsign (QString const& callsign) +{ + return callsign.contains ('/'); +} + +// split on first '/' and return the larger portion or the whole if +// there is no '/' +QString Callsign::base_callsign (QString callsign) +{ + auto slash_pos = callsign.indexOf('/'); + + if (slash_pos >= 0) + { + auto right_size = callsign.size () - slash_pos - 1; + + if (right_size>= slash_pos) { + callsign = callsign.mid(slash_pos + 1); + } else { + callsign = callsign.left(slash_pos); + } + } + + return callsign.toUpper(); +} + +// analyze the callsign and determine the effective prefix, returns +// the full call if no valid prefix (or prefix as a suffix) is specified +QString Callsign::effective_prefix(QString callsign) +{ + QStringList parts = callsign.split('/'); + + if (parts.size() == 2 && (parts.at(1) == "P" || parts.at(1) == "A" || parts.at(1) == "MM")) { + return callsign; + } + + auto prefix = parts.at(0); + int size = prefix.size(); + int region = -1; + + for (int i = 1; i < parts.size(); ++i) + { + if (parts.at(i) == "AM") + { + prefix="1B1ABCD"; + size = prefix.size(); + } + else if (is_callsign(parts.at(i)) && size > parts.at(i).size() && !((parts.at(i) == "LH" || parts.at(i) == "ND" || parts.at(i) == "AG" || parts.at(i) == "AE" || parts.at(i) == "KT") && i == (parts.size() -1))) + { + prefix = parts.at(i); + size = prefix.size(); + } + else { + bool ok; + auto reg = parts.at(i).toInt(&ok); + + if (ok) { + region = reg; + } + } + } + + auto const& match = prefix_re.match (prefix); + auto shorted = match.captured ("prefix"); + + if (shorted.isEmpty() || region > -1) + { + if (shorted.isEmpty()) + { + if (region > -1) { + shorted = prefix+"0"; + } else { + shorted = prefix; + } + } + + if (region > -1) { + shorted = shorted.left(shorted.size()-1) + QString::number(region); + } + + return shorted.toUpper (); + } + else + { + return prefix; + } +} + +QString Callsign::striped_prefix (QString callsign) +{ + auto const& match = prefix_re.match(callsign); + return match.captured("prefix"); +} + +CountryDat::CountryInfo Callsign::getCountryInfo(QString const& callsign) +{ + QString pf = callsign.toUpper(); + + while (!pf.isEmpty ()) + { + CountryDat::CountryInfo country = CountryDat::nullCountry; + + if (pf.length() == callsign.length()) + { + country = m_countryDat.getCountries().value("="+pf, country); + + if (country.country == CountryDat::nullCountry.country) { + pf = effective_prefix(callsign); + } else { + return country; + } + } + + if (pf == "KG4" && callsign.length() != 5) { + pf = "AA"; + } + + country = m_countryDat.getCountries().value(pf, country); + + if (country.country != CountryDat::nullCountry.country) { + return country; + } + + pf = pf.left(pf.length()-1); + } + + return CountryDat::nullCountry; +} diff --git a/android/app/src/main/cpp/util/callsign.h b/android/app/src/main/cpp/util/callsign.h new file mode 100644 index 0000000..2a6d7bf --- /dev/null +++ b/android/app/src/main/cpp/util/callsign.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// This source code file was last time modified by Arvo ES1JA on January 5th, 2019 +// All changes are shown in the patch file coming together with the full JTDX source code. + +#ifndef INCLUDE_CALLSIGN_H +#define INCLUDE_CALLSIGN_H + +#include +#include + +#include "export.h" +#include "countrydat.h" + +class SDRBASE_API Callsign : public QObject +{ + Q_OBJECT + +public: + static Callsign *instance(); + Callsign(); + ~Callsign(); + + static bool is_callsign(QString const& callsign); + static bool is_compound_callsign(QString const&); + static QString base_callsign(QString); + static QString effective_prefix(QString); + static QString striped_prefix(QString); + CountryDat::CountryInfo getCountryInfo(QString const& callsign); + +private: + static const QRegularExpression valid_callsign_regexp; + static const QRegularExpression prefix_re; + CountryDat m_countryDat; +}; + +#endif diff --git a/android/app/src/main/cpp/util/colormap.cpp b/android/app/src/main/cpp/util/colormap.cpp new file mode 100644 index 0000000..4f70654 --- /dev/null +++ b/android/app/src/main/cpp/util/colormap.cpp @@ -0,0 +1,7365 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Fabio Crameri // +// // +// Permission is hereby granted, free of charge, to any person obtaining a copy // +// of this software and associated documentation files (the "Software"), to deal // +// in the Software without restriction, including without limitation the rights // +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // +// copies of the Software, and to permit persons to whom the Software is // +// furnished to do so, subject to the following conditions: // +// // +// The above copyright notice and this permission notice shall be included in // +// all copies or substantial portions of the Software. // +// // +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // +// SOFTWARE. // +/////////////////////////////////////////////////////////////////////////////////// + +#include "colormap.h" + +QStringList ColorMap::getColorMapNames() +{ + QStringList names = m_colorMaps.keys(); + names.sort(); + return names; +} + +const float *ColorMap::getColorMap(const QString &name) +{ + QHash::const_iterator it = m_colorMaps.find(name); + + if (it == m_colorMaps.end()) + { + qWarning("ColorMap::getColorMap: %s color map not supported. Default to Angel", qPrintable(name)); + return m_colorMaps.value("Angel"); + } + + return m_colorMaps.value(name); +} + +QHash ColorMap::m_colorMaps{ + {"Angel", &m_angel[0]}, + {"Jet", &m_jet[0]}, + {"Turbo", &m_turbo[0]}, + {"Parula", &m_parula[0]}, + {"Hot", &m_hot[0]}, + {"Cool", &m_cool[0]}, + {"Batlow", &m_batlow[0]}, + {"Hawaii", &m_hawaii[0]}, + {"Acton", &m_acton[0]}, + {"Imola", &m_imola[0]}, + {"Tokyo", &m_tokyo[0]}, + {"Lapaz", &m_lapaz[0]}, + {"Buda", &m_buda[0]}, + {"Devon", &m_devon[0]}, + {"Lajolla", &m_lajolla[0]}, + {"Bamako", &m_bamako[0]}, + {"Plasma", &m_plasma[0]}, + {"Rainbow", &m_rainbow[0]}, + {"Prism", &m_prism[0]}, + {"Viridis", &m_viridis[0]}, + {"Loggray", &m_loggray[0]}, + {"Shrimp", &m_shrimp[0]}, + {"Cubehelix", &m_cubehelix[0]}, + {"Cubegamma", &m_cubegamma[0]}, + {"Cubehlx2", &m_cubehlx2[0]}, + {"Icy", &m_icy[0]}, + {"Mint", &m_mint[0]}, + {"A.C.A.B.", &m_acab[0]}, +}; + +const float ColorMap::m_angel[m_size] = +{ + 0.0, 0.000976577, 0.0588235, + 0.0, 0.00209049, 0.0627451, + 0.0, 0.00332647, 0.0666667, + 0.0, 0.00469978, 0.0705882, + 0.0, 0.00621042, 0.0745098, + 0.0, 0.00784314, 0.0784314, + 0.0, 0.00961318, 0.0823529, + 0.0, 0.0115053, 0.0862745, + 0.0, 0.0135348, 0.0901961, + 0.0, 0.0156863, 0.0941176, + 0.0, 0.0179751, 0.0980392, + 0.0, 0.0203861, 0.101961, + 0.0, 0.0229343, 0.105882, + 0.0, 0.0256199, 0.109804, + 0.0, 0.0284276, 0.113725, + 0.0, 0.0313725, 0.117647, + 0.0, 0.0344396, 0.121569, + 0.0, 0.037644, 0.12549, + 0.0, 0.0409857, 0.129412, + 0.0, 0.0444495, 0.133333, + 0.0, 0.0480354, 0.137255, + 0.0, 0.0517586, 0.141176, + 0.0, 0.0556191, 0.145098, + 0.0, 0.0596017, 0.14902, + 0.0, 0.0637217, 0.152941, + 0.0, 0.0679789, 0.156863, + 0.0, 0.0723583, 0.160784, + 0.0, 0.0768597, 0.164706, + 0.0, 0.0814984, 0.168627, + 0.0, 0.0862745, 0.172549, + 0.0, 0.0911727, 0.176471, + 0.0, 0.0962081, 0.180392, + 0.0, 0.101366, 0.184314, + 0.0, 0.106661, 0.188235, + 0.0, 0.112093, 0.192157, + 0.0, 0.117647, 0.196078, + 0.0, 0.123339, 0.2, + 0.0, 0.129152, 0.203922, + 0.0, 0.135103, 0.207843, + 0.0, 0.141176, 0.211765, + 0.0, 0.147387, 0.215686, + 0.0, 0.153719, 0.219608, + 0.0, 0.160189, 0.223529, + 0.0, 0.166796, 0.227451, + 0.0, 0.173526, 0.231373, + 0.0, 0.180392, 0.235294, + 0.0, 0.187381, 0.239216, + 0.0, 0.194507, 0.243137, + 0.0, 0.20177, 0.247059, + 0.0, 0.209155, 0.25098, + 0.0, 0.216663, 0.254902, + 0.0, 0.224308, 0.258824, + 0.0, 0.23209, 0.262745, + 0.0, 0.239994, 0.266667, + 0.0, 0.248035, 0.270588, + 0.0, 0.256214, 0.27451, + 0.0, 0.264515, 0.278431, + 0.0, 0.272938, 0.282353, + 0.0, 0.281498, 0.286275, + 0.0, 0.290196, 0.290196, + 0.0, 0.294118, 0.28922, + 0.0, 0.298039, 0.288106, + 0.0, 0.301961, 0.28687, + 0.0, 0.305882, 0.285496, + 0.0, 0.309804, 0.283986, + 0.0, 0.313725, 0.282353, + 0.0, 0.317647, 0.280583, + 0.0, 0.321569, 0.278691, + 0.0, 0.32549, 0.276661, + 0.0, 0.329412, 0.27451, + 0.0, 0.333333, 0.272221, + 0.0, 0.337255, 0.26981, + 0.0, 0.341176, 0.267262, + 0.0, 0.345098, 0.264576, + 0.0, 0.34902, 0.261769, + 0.0, 0.352941, 0.258824, + 0.0, 0.356863, 0.255756, + 0.0, 0.360784, 0.252552, + 0.0, 0.364706, 0.24921, + 0.0, 0.368627, 0.245747, + 0.0, 0.372549, 0.242161, + 0.0, 0.376471, 0.238437, + 0.0, 0.380392, 0.234577, + 0.0, 0.384314, 0.230594, + 0.0, 0.388235, 0.226474, + 0.0, 0.392157, 0.222217, + 0.0, 0.396078, 0.217838, + 0.0, 0.4, 0.213336, + 0.0, 0.403922, 0.208698, + 0.0, 0.407843, 0.203922, + 0.0, 0.411765, 0.199023, + 0.0, 0.415686, 0.193988, + 0.0, 0.419608, 0.18883, + 0.0, 0.423529, 0.183536, + 0.0, 0.427451, 0.178103, + 0.0, 0.431373, 0.172549, + 0.0, 0.435294, 0.166857, + 0.0, 0.439216, 0.161044, + 0.0, 0.443137, 0.155093, + 0.0, 0.447059, 0.14902, + 0.0, 0.45098, 0.142809, + 0.0, 0.454902, 0.136477, + 0.0, 0.458824, 0.130007, + 0.0, 0.462745, 0.1234, + 0.0, 0.466667, 0.11667, + 0.0, 0.470588, 0.109804, + 0.0, 0.47451, 0.102815, + 0.0, 0.478431, 0.0956893, + 0.0, 0.482353, 0.088426, + 0.0, 0.486275, 0.0810407, + 0.0, 0.490196, 0.0735332, + 0.0, 0.494118, 0.0658885, + 0.0, 0.498039, 0.0581064, + 0.0, 0.501961, 0.0502022, + 0.0, 0.505882, 0.0421607, + 0.0, 0.509804, 0.0339818, + 0.0, 0.513725, 0.0256809, + 0.0, 0.517647, 0.017258, + 0.0, 0.521569, 0.00869764, + 0.0, 0.52549, 0.0, + 0.00881971, 0.529412, 0.0, + 0.0177768, 0.533333, 0.0, + 0.0268559, 0.537255, 0.0, + 0.0360723, 0.541176, 0.0, + 0.0454261, 0.545098, 0.0, + 0.054902, 0.54902, 0.0, + 0.0645151, 0.552941, 0.0, + 0.0742504, 0.556863, 0.0, + 0.084123, 0.560784, 0.0, + 0.0941176, 0.564706, 0.0, + 0.10425, 0.568627, 0.0, + 0.114504, 0.572549, 0.0, + 0.124895, 0.576471, 0.0, + 0.135424, 0.580392, 0.0, + 0.146075, 0.584314, 0.0, + 0.156863, 0.588235, 0.0, + 0.167773, 0.592157, 0.0, + 0.17882, 0.596078, 0.0, + 0.190005, 0.6, 0.0, + 0.201312, 0.603922, 0.0, + 0.212741, 0.607843, 0.0, + 0.224308, 0.611765, 0.0, + 0.236011, 0.615686, 0.0, + 0.247837, 0.619608, 0.0, + 0.2598, 0.623529, 0.0, + 0.271901, 0.627451, 0.0, + 0.284123, 0.631373, 0.0, + 0.296468, 0.635294, 0.0, + 0.308949, 0.639216, 0.0, + 0.321569, 0.643137, 0.0, + 0.33431, 0.647059, 0.0, + 0.347189, 0.65098, 0.0, + 0.360189, 0.654902, 0.0, + 0.373327, 0.658824, 0.0, + 0.386603, 0.662745, 0.0, + 0.4, 0.666667, 0.0, + 0.413535, 0.670588, 0.0, + 0.427192, 0.67451, 0.0, + 0.440986, 0.678431, 0.0, + 0.454902, 0.682353, 0.0, + 0.468956, 0.686275, 0.0, + 0.483131, 0.690196, 0.0, + 0.497444, 0.694118, 0.0, + 0.511894, 0.698039, 0.0, + 0.526467, 0.701961, 0.0, + 0.541176, 0.705882, 0.0, + 0.556008, 0.709804, 0.0, + 0.570977, 0.713725, 0.0, + 0.586084, 0.717647, 0.0, + 0.601312, 0.721569, 0.0, + 0.616663, 0.72549, 0.0, + 0.632151, 0.729412, 0.0, + 0.647776, 0.733333, 0.0, + 0.663523, 0.737255, 0.0, + 0.679408, 0.741176, 0.0, + 0.69543, 0.745098, 0.0, + 0.711574, 0.74902, 0.0, + 0.72784, 0.752941, 0.0, + 0.744244, 0.756863, 0.0, + 0.760784, 0.760784, 0.0, + 0.764706, 0.751965, 0.0, + 0.768627, 0.743008, 0.0, + 0.772549, 0.733928, 0.0, + 0.776471, 0.724712, 0.0, + 0.780392, 0.715358, 0.0, + 0.784314, 0.705882, 0.0, + 0.788235, 0.696269, 0.0, + 0.792157, 0.686534, 0.0, + 0.796078, 0.676661, 0.0, + 0.8, 0.666667, 0.0, + 0.803922, 0.656535, 0.0, + 0.807843, 0.646281, 0.0, + 0.811765, 0.635889, 0.0, + 0.815686, 0.62536, 0.0, + 0.819608, 0.61471, 0.0, + 0.823529, 0.603922, 0.0, + 0.827451, 0.593011, 0.0, + 0.831373, 0.581964, 0.0, + 0.835294, 0.570779, 0.0, + 0.839216, 0.559472, 0.0, + 0.843137, 0.548043, 0.0, + 0.847059, 0.536477, 0.0, + 0.85098, 0.524773, 0.0, + 0.854902, 0.512947, 0.0, + 0.858824, 0.500984, 0.0, + 0.862745, 0.488884, 0.0, + 0.866667, 0.476661, 0.0, + 0.870588, 0.464317, 0.0, + 0.87451, 0.451835, 0.0, + 0.878431, 0.439216, 0.0, + 0.882353, 0.426474, 0.0, + 0.886275, 0.413596, 0.0, + 0.890196, 0.400595, 0.0, + 0.894118, 0.387457, 0.0, + 0.898039, 0.374182, 0.0, + 0.901961, 0.360784, 0.0, + 0.905882, 0.34725, 0.0, + 0.909804, 0.333593, 0.0, + 0.913725, 0.319799, 0.0, + 0.917647, 0.305882, 0.0, + 0.921569, 0.291829, 0.0, + 0.92549, 0.277653, 0.0, + 0.929412, 0.26334, 0.0, + 0.933333, 0.24889, 0.0, + 0.937255, 0.234318, 0.0, + 0.941176, 0.219608, 0.0, + 0.945098, 0.204776, 0.0, + 0.94902, 0.189807, 0.0, + 0.952941, 0.174701, 0.0, + 0.956863, 0.159472, 0.0, + 0.960784, 0.144121, 0.0, + 0.964706, 0.128634, 0.0, + 0.968627, 0.113008, 0.0, + 0.972549, 0.097261, 0.0, + 0.976471, 0.0813764, 0.0, + 0.980392, 0.0653544, 0.0, + 0.984314, 0.0492103, 0.0, + 0.988235, 0.0329442, 0.0, + 0.992157, 0.0165408, 0.0, + 0.996078, 0.0, 0.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, +}; + +const float ColorMap::m_jet[m_size] = +{ + 0.0, 0.0, 0.5156, + 0.0, 0.0, 0.5312, + 0.0, 0.0, 0.5469, + 0.0, 0.0, 0.5625, + 0.0, 0.0, 0.5781, + 0.0, 0.0, 0.5938, + 0.0, 0.0, 0.6094, + 0.0, 0.0, 0.6250, + 0.0, 0.0, 0.6406, + 0.0, 0.0, 0.6562, + 0.0, 0.0, 0.6719, + 0.0, 0.0, 0.6875, + 0.0, 0.0, 0.7031, + 0.0, 0.0, 0.7188, + 0.0, 0.0, 0.7344, + 0.0, 0.0, 0.7500, + 0.0, 0.0, 0.7656, + 0.0, 0.0, 0.7812, + 0.0, 0.0, 0.7969, + 0.0, 0.0, 0.8125, + 0.0, 0.0, 0.8281, + 0.0, 0.0, 0.8438, + 0.0, 0.0, 0.8594, + 0.0, 0.0, 0.8750, + 0.0, 0.0, 0.8906, + 0.0, 0.0, 0.9062, + 0.0, 0.0, 0.9219, + 0.0, 0.0, 0.9375, + 0.0, 0.0, 0.9531, + 0.0, 0.0, 0.9688, + 0.0, 0.0, 0.9844, + 0.0, 0.0, 1.0000, + 0.0, 0.0156, 1.0000, + 0.0, 0.0312, 1.0000, + 0.0, 0.0469, 1.0000, + 0.0, 0.0625, 1.0000, + 0.0, 0.0781, 1.0000, + 0.0, 0.0938, 1.0000, + 0.0, 0.1094, 1.0000, + 0.0, 0.1250, 1.0000, + 0.0, 0.1406, 1.0000, + 0.0, 0.1562, 1.0000, + 0.0, 0.1719, 1.0000, + 0.0, 0.1875, 1.0000, + 0.0, 0.2031, 1.0000, + 0.0, 0.2188, 1.0000, + 0.0, 0.2344, 1.0000, + 0.0, 0.2500, 1.0000, + 0.0, 0.2656, 1.0000, + 0.0, 0.2812, 1.0000, + 0.0, 0.2969, 1.0000, + 0.0, 0.3125, 1.0000, + 0.0, 0.3281, 1.0000, + 0.0, 0.3438, 1.0000, + 0.0, 0.3594, 1.0000, + 0.0, 0.3750, 1.0000, + 0.0, 0.3906, 1.0000, + 0.0, 0.4062, 1.0000, + 0.0, 0.4219, 1.0000, + 0.0, 0.4375, 1.0000, + 0.0, 0.4531, 1.0000, + 0.0, 0.4688, 1.0000, + 0.0, 0.4844, 1.0000, + 0.0, 0.5000, 1.0000, + 0.0, 0.5156, 1.0000, + 0.0, 0.5312, 1.0000, + 0.0, 0.5469, 1.0000, + 0.0, 0.5625, 1.0000, + 0.0, 0.5781, 1.0000, + 0.0, 0.5938, 1.0000, + 0.0, 0.6094, 1.0000, + 0.0, 0.6250, 1.0000, + 0.0, 0.6406, 1.0000, + 0.0, 0.6562, 1.0000, + 0.0, 0.6719, 1.0000, + 0.0, 0.6875, 1.0000, + 0.0, 0.7031, 1.0000, + 0.0, 0.7188, 1.0000, + 0.0, 0.7344, 1.0000, + 0.0, 0.7500, 1.0000, + 0.0, 0.7656, 1.0000, + 0.0, 0.7812, 1.0000, + 0.0, 0.7969, 1.0000, + 0.0, 0.8125, 1.0000, + 0.0, 0.8281, 1.0000, + 0.0, 0.8438, 1.0000, + 0.0, 0.8594, 1.0000, + 0.0, 0.8750, 1.0000, + 0.0, 0.8906, 1.0000, + 0.0, 0.9062, 1.0000, + 0.0, 0.9219, 1.0000, + 0.0, 0.9375, 1.0000, + 0.0, 0.9531, 1.0000, + 0.0, 0.9688, 1.0000, + 0.0, 0.9844, 1.0000, + 0.0, 1.0000, 1.0000, + 0.0156, 1.0000, 0.9844, + 0.0312, 1.0000, 0.9688, + 0.0469, 1.0000, 0.9531, + 0.0625, 1.0000, 0.9375, + 0.0781, 1.0000, 0.9219, + 0.0938, 1.0000, 0.9062, + 0.1094, 1.0000, 0.8906, + 0.1250, 1.0000, 0.8750, + 0.1406, 1.0000, 0.8594, + 0.1562, 1.0000, 0.8438, + 0.1719, 1.0000, 0.8281, + 0.1875, 1.0000, 0.8125, + 0.2031, 1.0000, 0.7969, + 0.2188, 1.0000, 0.7812, + 0.2344, 1.0000, 0.7656, + 0.2500, 1.0000, 0.7500, + 0.2656, 1.0000, 0.7344, + 0.2812, 1.0000, 0.7188, + 0.2969, 1.0000, 0.7031, + 0.3125, 1.0000, 0.6875, + 0.3281, 1.0000, 0.6719, + 0.3438, 1.0000, 0.6562, + 0.3594, 1.0000, 0.6406, + 0.3750, 1.0000, 0.6250, + 0.3906, 1.0000, 0.6094, + 0.4062, 1.0000, 0.5938, + 0.4219, 1.0000, 0.5781, + 0.4375, 1.0000, 0.5625, + 0.4531, 1.0000, 0.5469, + 0.4688, 1.0000, 0.5312, + 0.4844, 1.0000, 0.5156, + 0.5000, 1.0000, 0.5000, + 0.5156, 1.0000, 0.4844, + 0.5312, 1.0000, 0.4688, + 0.5469, 1.0000, 0.4531, + 0.5625, 1.0000, 0.4375, + 0.5781, 1.0000, 0.4219, + 0.5938, 1.0000, 0.4062, + 0.6094, 1.0000, 0.3906, + 0.6250, 1.0000, 0.3750, + 0.6406, 1.0000, 0.3594, + 0.6562, 1.0000, 0.3438, + 0.6719, 1.0000, 0.3281, + 0.6875, 1.0000, 0.3125, + 0.7031, 1.0000, 0.2969, + 0.7188, 1.0000, 0.2812, + 0.7344, 1.0000, 0.2656, + 0.7500, 1.0000, 0.2500, + 0.7656, 1.0000, 0.2344, + 0.7812, 1.0000, 0.2188, + 0.7969, 1.0000, 0.2031, + 0.8125, 1.0000, 0.1875, + 0.8281, 1.0000, 0.1719, + 0.8438, 1.0000, 0.1562, + 0.8594, 1.0000, 0.1406, + 0.8750, 1.0000, 0.1250, + 0.8906, 1.0000, 0.1094, + 0.9062, 1.0000, 0.0938, + 0.9219, 1.0000, 0.0781, + 0.9375, 1.0000, 0.0625, + 0.9531, 1.0000, 0.0469, + 0.9688, 1.0000, 0.0312, + 0.9844, 1.0000, 0.0156, + 1.0000, 1.0000, 0.0, + 1.0000, 0.9844, 0.0, + 1.0000, 0.9688, 0.0, + 1.0000, 0.9531, 0.0, + 1.0000, 0.9375, 0.0, + 1.0000, 0.9219, 0.0, + 1.0000, 0.9062, 0.0, + 1.0000, 0.8906, 0.0, + 1.0000, 0.8750, 0.0, + 1.0000, 0.8594, 0.0, + 1.0000, 0.8438, 0.0, + 1.0000, 0.8281, 0.0, + 1.0000, 0.8125, 0.0, + 1.0000, 0.7969, 0.0, + 1.0000, 0.7812, 0.0, + 1.0000, 0.7656, 0.0, + 1.0000, 0.7500, 0.0, + 1.0000, 0.7344, 0.0, + 1.0000, 0.7188, 0.0, + 1.0000, 0.7031, 0.0, + 1.0000, 0.6875, 0.0, + 1.0000, 0.6719, 0.0, + 1.0000, 0.6562, 0.0, + 1.0000, 0.6406, 0.0, + 1.0000, 0.6250, 0.0, + 1.0000, 0.6094, 0.0, + 1.0000, 0.5938, 0.0, + 1.0000, 0.5781, 0.0, + 1.0000, 0.5625, 0.0, + 1.0000, 0.5469, 0.0, + 1.0000, 0.5312, 0.0, + 1.0000, 0.5156, 0.0, + 1.0000, 0.5000, 0.0, + 1.0000, 0.4844, 0.0, + 1.0000, 0.4688, 0.0, + 1.0000, 0.4531, 0.0, + 1.0000, 0.4375, 0.0, + 1.0000, 0.4219, 0.0, + 1.0000, 0.4062, 0.0, + 1.0000, 0.3906, 0.0, + 1.0000, 0.3750, 0.0, + 1.0000, 0.3594, 0.0, + 1.0000, 0.3438, 0.0, + 1.0000, 0.3281, 0.0, + 1.0000, 0.3125, 0.0, + 1.0000, 0.2969, 0.0, + 1.0000, 0.2812, 0.0, + 1.0000, 0.2656, 0.0, + 1.0000, 0.2500, 0.0, + 1.0000, 0.2344, 0.0, + 1.0000, 0.2188, 0.0, + 1.0000, 0.2031, 0.0, + 1.0000, 0.1875, 0.0, + 1.0000, 0.1719, 0.0, + 1.0000, 0.1562, 0.0, + 1.0000, 0.1406, 0.0, + 1.0000, 0.1250, 0.0, + 1.0000, 0.1094, 0.0, + 1.0000, 0.0938, 0.0, + 1.0000, 0.0781, 0.0, + 1.0000, 0.0625, 0.0, + 1.0000, 0.0469, 0.0, + 1.0000, 0.0312, 0.0, + 1.0000, 0.0156, 0.0, + 1.0000, 0.0, 0.0, + 0.9844, 0.0, 0.0, + 0.9688, 0.0, 0.0, + 0.9531, 0.0, 0.0, + 0.9375, 0.0, 0.0, + 0.9219, 0.0, 0.0, + 0.9062, 0.0, 0.0, + 0.8906, 0.0, 0.0, + 0.8750, 0.0, 0.0, + 0.8594, 0.0, 0.0, + 0.8438, 0.0, 0.0, + 0.8281, 0.0, 0.0, + 0.8125, 0.0, 0.0, + 0.7969, 0.0, 0.0, + 0.7812, 0.0, 0.0, + 0.7656, 0.0, 0.0, + 0.7500, 0.0, 0.0, + 0.7344, 0.0, 0.0, + 0.7188, 0.0, 0.0, + 0.7031, 0.0, 0.0, + 0.6875, 0.0, 0.0, + 0.6719, 0.0, 0.0, + 0.6562, 0.0, 0.0, + 0.6406, 0.0, 0.0, + 0.6250, 0.0, 0.0, + 0.6094, 0.0, 0.0, + 0.5938, 0.0, 0.0, + 0.5781, 0.0, 0.0, + 0.5625, 0.0, 0.0, + 0.5469, 0.0, 0.0, + 0.5312, 0.0, 0.0, + 0.5156, 0.0, 0.0, + 0.5000, 0.0, 0.0, +}; + +const float ColorMap::m_turbo[m_size] = +{ + 0.1900, 0.0718, 0.2322, + 0.1948, 0.0834, 0.2615, + 0.1996, 0.0950, 0.2902, + 0.2041, 0.1065, 0.3184, + 0.2086, 0.1180, 0.3461, + 0.2129, 0.1295, 0.3731, + 0.2171, 0.1409, 0.3996, + 0.2211, 0.1522, 0.4256, + 0.2250, 0.1635, 0.4510, + 0.2288, 0.1748, 0.4758, + 0.2324, 0.1860, 0.5000, + 0.2358, 0.1972, 0.5237, + 0.2392, 0.2083, 0.5469, + 0.2423, 0.2194, 0.5694, + 0.2454, 0.2304, 0.5914, + 0.2483, 0.2414, 0.6129, + 0.2511, 0.2524, 0.6337, + 0.2537, 0.2633, 0.6541, + 0.2562, 0.2741, 0.6738, + 0.2585, 0.2849, 0.6930, + 0.2607, 0.2957, 0.7116, + 0.2628, 0.3064, 0.7297, + 0.2647, 0.3171, 0.7472, + 0.2665, 0.3277, 0.7641, + 0.2682, 0.3382, 0.7805, + 0.2697, 0.3488, 0.7963, + 0.2710, 0.3593, 0.8116, + 0.2723, 0.3697, 0.8262, + 0.2733, 0.3801, 0.8404, + 0.2743, 0.3904, 0.8539, + 0.2751, 0.4007, 0.8669, + 0.2758, 0.4110, 0.8794, + 0.2763, 0.4212, 0.8912, + 0.2767, 0.4313, 0.9025, + 0.2769, 0.4415, 0.9133, + 0.2770, 0.4515, 0.9235, + 0.2770, 0.4615, 0.9331, + 0.2768, 0.4715, 0.9421, + 0.2765, 0.4814, 0.9506, + 0.2760, 0.4913, 0.9586, + 0.2754, 0.5011, 0.9659, + 0.2747, 0.5109, 0.9728, + 0.2738, 0.5207, 0.9790, + 0.2727, 0.5304, 0.9846, + 0.2711, 0.5402, 0.9893, + 0.2688, 0.5500, 0.9930, + 0.2659, 0.5598, 0.9958, + 0.2625, 0.5697, 0.9977, + 0.2586, 0.5796, 0.9988, + 0.2542, 0.5895, 0.9990, + 0.2495, 0.5994, 0.9983, + 0.2443, 0.6094, 0.9970, + 0.2387, 0.6193, 0.9949, + 0.2329, 0.6292, 0.9920, + 0.2268, 0.6391, 0.9885, + 0.2204, 0.6490, 0.9844, + 0.2138, 0.6589, 0.9796, + 0.2071, 0.6687, 0.9742, + 0.2002, 0.6784, 0.9683, + 0.1933, 0.6881, 0.9619, + 0.1862, 0.6977, 0.9550, + 0.1792, 0.7073, 0.9476, + 0.1722, 0.7168, 0.9398, + 0.1653, 0.7262, 0.9316, + 0.1584, 0.7355, 0.9231, + 0.1517, 0.7447, 0.9142, + 0.1452, 0.7538, 0.9050, + 0.1389, 0.7628, 0.8955, + 0.1328, 0.7716, 0.8858, + 0.1270, 0.7804, 0.8759, + 0.1215, 0.7890, 0.8658, + 0.1164, 0.7974, 0.8556, + 0.1117, 0.8057, 0.8452, + 0.1074, 0.8138, 0.8348, + 0.1036, 0.8218, 0.8244, + 0.1003, 0.8296, 0.8139, + 0.0975, 0.8371, 0.8034, + 0.0953, 0.8446, 0.7930, + 0.0938, 0.8518, 0.7826, + 0.0929, 0.8588, 0.7724, + 0.0927, 0.8655, 0.7623, + 0.0932, 0.8721, 0.7524, + 0.0945, 0.8784, 0.7427, + 0.0966, 0.8845, 0.7332, + 0.0996, 0.8904, 0.7239, + 0.1034, 0.8960, 0.7150, + 0.1081, 0.9014, 0.7060, + 0.1137, 0.9067, 0.6965, + 0.1201, 0.9119, 0.6866, + 0.1273, 0.9170, 0.6763, + 0.1353, 0.9220, 0.6656, + 0.1439, 0.9268, 0.6545, + 0.1532, 0.9315, 0.6431, + 0.1632, 0.9361, 0.6314, + 0.1738, 0.9405, 0.6194, + 0.1849, 0.9448, 0.6071, + 0.1966, 0.9490, 0.5947, + 0.2088, 0.9530, 0.5820, + 0.2214, 0.9569, 0.5691, + 0.2345, 0.9607, 0.5561, + 0.2480, 0.9642, 0.5430, + 0.2618, 0.9677, 0.5298, + 0.2760, 0.9709, 0.5165, + 0.2904, 0.9740, 0.5032, + 0.3051, 0.9770, 0.4899, + 0.3201, 0.9797, 0.4765, + 0.3352, 0.9823, 0.4632, + 0.3504, 0.9848, 0.4500, + 0.3658, 0.9870, 0.4369, + 0.3813, 0.9891, 0.4239, + 0.3968, 0.9910, 0.4110, + 0.4123, 0.9927, 0.3983, + 0.4278, 0.9942, 0.3857, + 0.4432, 0.9955, 0.3735, + 0.4585, 0.9966, 0.3614, + 0.4738, 0.9976, 0.3496, + 0.4888, 0.9983, 0.3382, + 0.5036, 0.9988, 0.3270, + 0.5182, 0.9991, 0.3162, + 0.5325, 0.9992, 0.3058, + 0.5466, 0.9991, 0.2958, + 0.5603, 0.9987, 0.2862, + 0.5736, 0.9982, 0.2771, + 0.5865, 0.9974, 0.2685, + 0.5989, 0.9964, 0.2604, + 0.6109, 0.9951, 0.2528, + 0.6223, 0.9937, 0.2458, + 0.6332, 0.9919, 0.2394, + 0.6436, 0.9900, 0.2336, + 0.6539, 0.9878, 0.2283, + 0.6643, 0.9852, 0.2237, + 0.6746, 0.9825, 0.2196, + 0.6849, 0.9794, 0.2160, + 0.6953, 0.9761, 0.2129, + 0.7055, 0.9726, 0.2103, + 0.7158, 0.9688, 0.2082, + 0.7260, 0.9647, 0.2064, + 0.7361, 0.9604, 0.2050, + 0.7462, 0.9559, 0.2041, + 0.7562, 0.9512, 0.2034, + 0.7661, 0.9463, 0.2031, + 0.7759, 0.9411, 0.2031, + 0.7856, 0.9358, 0.2034, + 0.7952, 0.9303, 0.2039, + 0.8047, 0.9245, 0.2046, + 0.8141, 0.9186, 0.2055, + 0.8233, 0.9125, 0.2066, + 0.8324, 0.9063, 0.2079, + 0.8413, 0.8999, 0.2093, + 0.8501, 0.8933, 0.2107, + 0.8587, 0.8865, 0.2123, + 0.8671, 0.8797, 0.2139, + 0.8753, 0.8727, 0.2155, + 0.8833, 0.8655, 0.2172, + 0.8911, 0.8583, 0.2188, + 0.8987, 0.8509, 0.2204, + 0.9061, 0.8434, 0.2219, + 0.9132, 0.8358, 0.2233, + 0.9200, 0.8281, 0.2246, + 0.9267, 0.8203, 0.2257, + 0.9330, 0.8124, 0.2267, + 0.9391, 0.8044, 0.2274, + 0.9449, 0.7963, 0.2280, + 0.9504, 0.7882, 0.2283, + 0.9556, 0.7801, 0.2284, + 0.9605, 0.7718, 0.2281, + 0.9651, 0.7635, 0.2275, + 0.9693, 0.7552, 0.2266, + 0.9732, 0.7468, 0.2254, + 0.9768, 0.7384, 0.2237, + 0.9800, 0.7300, 0.2216, + 0.9829, 0.7214, 0.2192, + 0.9855, 0.7125, 0.2165, + 0.9878, 0.7033, 0.2136, + 0.9899, 0.6938, 0.2104, + 0.9916, 0.6841, 0.2071, + 0.9931, 0.6741, 0.2035, + 0.9944, 0.6639, 0.1997, + 0.9953, 0.6534, 0.1958, + 0.9961, 0.6428, 0.1916, + 0.9965, 0.6319, 0.1874, + 0.9968, 0.6209, 0.1830, + 0.9967, 0.6098, 0.1784, + 0.9964, 0.5985, 0.1738, + 0.9959, 0.5870, 0.1690, + 0.9952, 0.5755, 0.1641, + 0.9942, 0.5639, 0.1592, + 0.9930, 0.5521, 0.1542, + 0.9915, 0.5404, 0.1491, + 0.9899, 0.5285, 0.1440, + 0.9880, 0.5167, 0.1388, + 0.9859, 0.5048, 0.1337, + 0.9836, 0.4929, 0.1285, + 0.9811, 0.4810, 0.1233, + 0.9784, 0.4692, 0.1182, + 0.9755, 0.4574, 0.1130, + 0.9723, 0.4456, 0.1080, + 0.9690, 0.4340, 0.1029, + 0.9656, 0.4224, 0.0980, + 0.9619, 0.4109, 0.0931, + 0.9580, 0.3996, 0.0883, + 0.9540, 0.3884, 0.0836, + 0.9498, 0.3773, 0.0790, + 0.9454, 0.3664, 0.0746, + 0.9408, 0.3557, 0.0703, + 0.9361, 0.3451, 0.0662, + 0.9313, 0.3348, 0.0622, + 0.9262, 0.3247, 0.0584, + 0.9211, 0.3149, 0.0548, + 0.9157, 0.3053, 0.0513, + 0.9102, 0.2960, 0.0481, + 0.9046, 0.2870, 0.0452, + 0.8989, 0.2782, 0.0424, + 0.8930, 0.2698, 0.0399, + 0.8869, 0.2615, 0.0375, + 0.8807, 0.2533, 0.0352, + 0.8742, 0.2453, 0.0330, + 0.8676, 0.2373, 0.0308, + 0.8608, 0.2294, 0.0288, + 0.8538, 0.2217, 0.0268, + 0.8466, 0.2141, 0.0249, + 0.8393, 0.2065, 0.0231, + 0.8317, 0.1991, 0.0213, + 0.8240, 0.1918, 0.0197, + 0.8161, 0.1846, 0.0181, + 0.8080, 0.1775, 0.0166, + 0.7997, 0.1706, 0.0152, + 0.7913, 0.1637, 0.0139, + 0.7826, 0.1569, 0.0126, + 0.7738, 0.1503, 0.0115, + 0.7648, 0.1437, 0.0104, + 0.7556, 0.1373, 0.0094, + 0.7462, 0.1310, 0.0085, + 0.7366, 0.1248, 0.0077, + 0.7269, 0.1187, 0.0069, + 0.7169, 0.1127, 0.0063, + 0.7068, 0.1068, 0.0057, + 0.6965, 0.1010, 0.0052, + 0.6860, 0.0954, 0.0048, + 0.6754, 0.0898, 0.0045, + 0.6645, 0.0844, 0.0042, + 0.6534, 0.0790, 0.0041, + 0.6422, 0.0738, 0.0040, + 0.6308, 0.0687, 0.0040, + 0.6192, 0.0637, 0.0041, + 0.6075, 0.0588, 0.0043, + 0.5955, 0.0540, 0.0045, + 0.5834, 0.0493, 0.0049, + 0.5710, 0.0447, 0.0053, + 0.5585, 0.0403, 0.0058, + 0.5458, 0.0359, 0.0064, + 0.5330, 0.0317, 0.0070, + 0.5199, 0.0276, 0.0078, + 0.5066, 0.0235, 0.0086, + 0.4932, 0.0196, 0.0095, + 0.4796, 0.0158, 0.0106, +}; + +const float ColorMap::m_parula[m_size] = +{ + 0.2422, 0.1504, 0.6603, + 0.2444, 0.1534, 0.6728, + 0.2464, 0.1569, 0.6847, + 0.2484, 0.1607, 0.6961, + 0.2503, 0.1648, 0.7071, + 0.2522, 0.1689, 0.7179, + 0.2540, 0.1732, 0.7286, + 0.2558, 0.1773, 0.7393, + 0.2576, 0.1814, 0.7501, + 0.2594, 0.1854, 0.7610, + 0.2611, 0.1893, 0.7719, + 0.2628, 0.1932, 0.7828, + 0.2645, 0.1972, 0.7937, + 0.2661, 0.2011, 0.8043, + 0.2676, 0.2052, 0.8148, + 0.2691, 0.2094, 0.8249, + 0.2704, 0.2138, 0.8346, + 0.2717, 0.2184, 0.8439, + 0.2729, 0.2231, 0.8528, + 0.2740, 0.2280, 0.8612, + 0.2749, 0.2330, 0.8692, + 0.2758, 0.2382, 0.8767, + 0.2766, 0.2435, 0.8840, + 0.2774, 0.2489, 0.8908, + 0.2781, 0.2543, 0.8973, + 0.2788, 0.2598, 0.9035, + 0.2794, 0.2653, 0.9094, + 0.2798, 0.2708, 0.9150, + 0.2802, 0.2764, 0.9204, + 0.2806, 0.2819, 0.9255, + 0.2809, 0.2875, 0.9305, + 0.2811, 0.2930, 0.9352, + 0.2813, 0.2985, 0.9397, + 0.2814, 0.3040, 0.9441, + 0.2814, 0.3095, 0.9483, + 0.2813, 0.3150, 0.9524, + 0.2811, 0.3204, 0.9563, + 0.2809, 0.3259, 0.9600, + 0.2807, 0.3313, 0.9636, + 0.2803, 0.3367, 0.9670, + 0.2798, 0.3421, 0.9702, + 0.2791, 0.3475, 0.9733, + 0.2784, 0.3529, 0.9763, + 0.2776, 0.3583, 0.9791, + 0.2766, 0.3638, 0.9817, + 0.2754, 0.3693, 0.9840, + 0.2741, 0.3748, 0.9862, + 0.2726, 0.3804, 0.9881, + 0.2710, 0.3860, 0.9898, + 0.2691, 0.3916, 0.9912, + 0.2670, 0.3973, 0.9924, + 0.2647, 0.4030, 0.9935, + 0.2621, 0.4088, 0.9946, + 0.2591, 0.4145, 0.9955, + 0.2556, 0.4203, 0.9965, + 0.2517, 0.4261, 0.9974, + 0.2473, 0.4319, 0.9983, + 0.2424, 0.4378, 0.9991, + 0.2369, 0.4437, 0.9996, + 0.2311, 0.4497, 0.9995, + 0.2250, 0.4559, 0.9985, + 0.2189, 0.4620, 0.9968, + 0.2128, 0.4682, 0.9948, + 0.2066, 0.4743, 0.9926, + 0.2006, 0.4803, 0.9906, + 0.1950, 0.4861, 0.9887, + 0.1903, 0.4919, 0.9867, + 0.1869, 0.4975, 0.9844, + 0.1847, 0.5030, 0.9819, + 0.1831, 0.5084, 0.9793, + 0.1818, 0.5138, 0.9766, + 0.1806, 0.5191, 0.9738, + 0.1795, 0.5244, 0.9709, + 0.1785, 0.5296, 0.9677, + 0.1778, 0.5349, 0.9641, + 0.1773, 0.5401, 0.9602, + 0.1768, 0.5452, 0.9560, + 0.1764, 0.5504, 0.9516, + 0.1755, 0.5554, 0.9473, + 0.1740, 0.5605, 0.9432, + 0.1716, 0.5655, 0.9393, + 0.1686, 0.5705, 0.9357, + 0.1649, 0.5755, 0.9323, + 0.1610, 0.5805, 0.9289, + 0.1573, 0.5854, 0.9254, + 0.1540, 0.5902, 0.9218, + 0.1513, 0.5950, 0.9182, + 0.1492, 0.5997, 0.9147, + 0.1475, 0.6043, 0.9113, + 0.1461, 0.6089, 0.9080, + 0.1446, 0.6135, 0.9050, + 0.1429, 0.6180, 0.9022, + 0.1408, 0.6226, 0.8998, + 0.1383, 0.6272, 0.8975, + 0.1354, 0.6317, 0.8953, + 0.1321, 0.6363, 0.8932, + 0.1288, 0.6408, 0.8910, + 0.1253, 0.6453, 0.8887, + 0.1219, 0.6497, 0.8862, + 0.1185, 0.6541, 0.8834, + 0.1152, 0.6584, 0.8804, + 0.1119, 0.6627, 0.8770, + 0.1085, 0.6669, 0.8734, + 0.1048, 0.6710, 0.8695, + 0.1009, 0.6750, 0.8653, + 0.0964, 0.6789, 0.8609, + 0.0914, 0.6828, 0.8562, + 0.0855, 0.6865, 0.8513, + 0.0789, 0.6902, 0.8462, + 0.0713, 0.6938, 0.8409, + 0.0628, 0.6972, 0.8355, + 0.0535, 0.7006, 0.8299, + 0.0433, 0.7039, 0.8242, + 0.0328, 0.7071, 0.8183, + 0.0234, 0.7103, 0.8124, + 0.0155, 0.7133, 0.8064, + 0.0091, 0.7163, 0.8003, + 0.0046, 0.7192, 0.7941, + 0.0019, 0.7220, 0.7878, + 0.0009, 0.7248, 0.7815, + 0.0018, 0.7275, 0.7752, + 0.0046, 0.7301, 0.7688, + 0.0094, 0.7327, 0.7623, + 0.0162, 0.7352, 0.7558, + 0.0253, 0.7376, 0.7492, + 0.0369, 0.7400, 0.7426, + 0.0504, 0.7423, 0.7359, + 0.0638, 0.7446, 0.7292, + 0.0770, 0.7468, 0.7224, + 0.0899, 0.7489, 0.7156, + 0.1023, 0.7510, 0.7088, + 0.1141, 0.7531, 0.7019, + 0.1252, 0.7552, 0.6950, + 0.1354, 0.7572, 0.6881, + 0.1448, 0.7593, 0.6812, + 0.1532, 0.7614, 0.6741, + 0.1609, 0.7635, 0.6671, + 0.1678, 0.7656, 0.6599, + 0.1741, 0.7678, 0.6527, + 0.1799, 0.7699, 0.6454, + 0.1853, 0.7721, 0.6379, + 0.1905, 0.7743, 0.6303, + 0.1954, 0.7765, 0.6225, + 0.2003, 0.7787, 0.6146, + 0.2061, 0.7808, 0.6065, + 0.2118, 0.7828, 0.5983, + 0.2178, 0.7849, 0.5899, + 0.2244, 0.7869, 0.5813, + 0.2318, 0.7887, 0.5725, + 0.2401, 0.7905, 0.5636, + 0.2491, 0.7922, 0.5546, + 0.2589, 0.7937, 0.5454, + 0.2695, 0.7951, 0.5360, + 0.2809, 0.7964, 0.5266, + 0.2929, 0.7975, 0.5170, + 0.3052, 0.7985, 0.5074, + 0.3176, 0.7994, 0.4975, + 0.3301, 0.8002, 0.4876, + 0.3424, 0.8009, 0.4774, + 0.3548, 0.8016, 0.4669, + 0.3671, 0.8021, 0.4563, + 0.3795, 0.8026, 0.4454, + 0.3921, 0.8029, 0.4344, + 0.4050, 0.8031, 0.4233, + 0.4184, 0.8030, 0.4122, + 0.4322, 0.8028, 0.4013, + 0.4463, 0.8024, 0.3904, + 0.4608, 0.8018, 0.3797, + 0.4753, 0.8011, 0.3691, + 0.4899, 0.8002, 0.3586, + 0.5044, 0.7993, 0.3480, + 0.5187, 0.7982, 0.3374, + 0.5329, 0.7970, 0.3267, + 0.5470, 0.7957, 0.3159, + 0.5609, 0.7943, 0.3050, + 0.5748, 0.7929, 0.2941, + 0.5886, 0.7913, 0.2833, + 0.6024, 0.7896, 0.2726, + 0.6161, 0.7878, 0.2622, + 0.6297, 0.7859, 0.2521, + 0.6433, 0.7839, 0.2423, + 0.6567, 0.7818, 0.2329, + 0.6701, 0.7796, 0.2239, + 0.6833, 0.7773, 0.2155, + 0.6963, 0.7750, 0.2075, + 0.7091, 0.7727, 0.1998, + 0.7218, 0.7703, 0.1924, + 0.7344, 0.7679, 0.1852, + 0.7468, 0.7654, 0.1782, + 0.7590, 0.7629, 0.1717, + 0.7710, 0.7604, 0.1658, + 0.7829, 0.7579, 0.1608, + 0.7945, 0.7554, 0.1570, + 0.8060, 0.7529, 0.1546, + 0.8172, 0.7505, 0.1535, + 0.8281, 0.7481, 0.1536, + 0.8389, 0.7457, 0.1546, + 0.8495, 0.7435, 0.1564, + 0.8600, 0.7413, 0.1587, + 0.8703, 0.7392, 0.1615, + 0.8804, 0.7372, 0.1650, + 0.8903, 0.7353, 0.1695, + 0.9000, 0.7336, 0.1749, + 0.9093, 0.7321, 0.1815, + 0.9184, 0.7308, 0.1890, + 0.9272, 0.7298, 0.1973, + 0.9357, 0.7290, 0.2061, + 0.9440, 0.7285, 0.2151, + 0.9523, 0.7284, 0.2237, + 0.9606, 0.7285, 0.2312, + 0.9689, 0.7292, 0.2373, + 0.9770, 0.7304, 0.2418, + 0.9842, 0.7330, 0.2446, + 0.9900, 0.7365, 0.2429, + 0.9946, 0.7407, 0.2394, + 0.9966, 0.7458, 0.2351, + 0.9971, 0.7513, 0.2309, + 0.9972, 0.7569, 0.2267, + 0.9971, 0.7626, 0.2224, + 0.9969, 0.7683, 0.2181, + 0.9966, 0.7740, 0.2138, + 0.9962, 0.7798, 0.2095, + 0.9957, 0.7856, 0.2053, + 0.9949, 0.7915, 0.2012, + 0.9938, 0.7974, 0.1974, + 0.9923, 0.8034, 0.1939, + 0.9906, 0.8095, 0.1906, + 0.9885, 0.8156, 0.1875, + 0.9861, 0.8218, 0.1846, + 0.9835, 0.8280, 0.1817, + 0.9807, 0.8342, 0.1787, + 0.9778, 0.8404, 0.1757, + 0.9748, 0.8467, 0.1726, + 0.9720, 0.8529, 0.1695, + 0.9694, 0.8591, 0.1665, + 0.9671, 0.8654, 0.1636, + 0.9651, 0.8716, 0.1608, + 0.9634, 0.8778, 0.1582, + 0.9619, 0.8840, 0.1557, + 0.9608, 0.8902, 0.1532, + 0.9601, 0.8963, 0.1507, + 0.9596, 0.9023, 0.1480, + 0.9595, 0.9084, 0.1450, + 0.9597, 0.9143, 0.1418, + 0.9601, 0.9203, 0.1382, + 0.9608, 0.9262, 0.1344, + 0.9618, 0.9320, 0.1304, + 0.9629, 0.9379, 0.1261, + 0.9642, 0.9437, 0.1216, + 0.9657, 0.9494, 0.1168, + 0.9674, 0.9552, 0.1116, + 0.9692, 0.9609, 0.1061, + 0.9711, 0.9667, 0.1001, + 0.9730, 0.9724, 0.0938, + 0.9749, 0.9782, 0.0872, + 0.9769, 0.9839, 0.0805, +}; + +const float ColorMap::m_hot[m_size] = +{ + 0.0104, 0.0, 0.0, + 0.0208, 0.0, 0.0, + 0.0312, 0.0, 0.0, + 0.0417, 0.0, 0.0, + 0.0521, 0.0, 0.0, + 0.0625, 0.0, 0.0, + 0.0729, 0.0, 0.0, + 0.0833, 0.0, 0.0, + 0.0938, 0.0, 0.0, + 0.1042, 0.0, 0.0, + 0.1146, 0.0, 0.0, + 0.1250, 0.0, 0.0, + 0.1354, 0.0, 0.0, + 0.1458, 0.0, 0.0, + 0.1562, 0.0, 0.0, + 0.1667, 0.0, 0.0, + 0.1771, 0.0, 0.0, + 0.1875, 0.0, 0.0, + 0.1979, 0.0, 0.0, + 0.2083, 0.0, 0.0, + 0.2188, 0.0, 0.0, + 0.2292, 0.0, 0.0, + 0.2396, 0.0, 0.0, + 0.2500, 0.0, 0.0, + 0.2604, 0.0, 0.0, + 0.2708, 0.0, 0.0, + 0.2812, 0.0, 0.0, + 0.2917, 0.0, 0.0, + 0.3021, 0.0, 0.0, + 0.3125, 0.0, 0.0, + 0.3229, 0.0, 0.0, + 0.3333, 0.0, 0.0, + 0.3438, 0.0, 0.0, + 0.3542, 0.0, 0.0, + 0.3646, 0.0, 0.0, + 0.3750, 0.0, 0.0, + 0.3854, 0.0, 0.0, + 0.3958, 0.0, 0.0, + 0.4062, 0.0, 0.0, + 0.4167, 0.0, 0.0, + 0.4271, 0.0, 0.0, + 0.4375, 0.0, 0.0, + 0.4479, 0.0, 0.0, + 0.4583, 0.0, 0.0, + 0.4688, 0.0, 0.0, + 0.4792, 0.0, 0.0, + 0.4896, 0.0, 0.0, + 0.5000, 0.0, 0.0, + 0.5104, 0.0, 0.0, + 0.5208, 0.0, 0.0, + 0.5312, 0.0, 0.0, + 0.5417, 0.0, 0.0, + 0.5521, 0.0, 0.0, + 0.5625, 0.0, 0.0, + 0.5729, 0.0, 0.0, + 0.5833, 0.0, 0.0, + 0.5938, 0.0, 0.0, + 0.6042, 0.0, 0.0, + 0.6146, 0.0, 0.0, + 0.6250, 0.0, 0.0, + 0.6354, 0.0, 0.0, + 0.6458, 0.0, 0.0, + 0.6562, 0.0, 0.0, + 0.6667, 0.0, 0.0, + 0.6771, 0.0, 0.0, + 0.6875, 0.0, 0.0, + 0.6979, 0.0, 0.0, + 0.7083, 0.0, 0.0, + 0.7188, 0.0, 0.0, + 0.7292, 0.0, 0.0, + 0.7396, 0.0, 0.0, + 0.7500, 0.0, 0.0, + 0.7604, 0.0, 0.0, + 0.7708, 0.0, 0.0, + 0.7812, 0.0, 0.0, + 0.7917, 0.0, 0.0, + 0.8021, 0.0, 0.0, + 0.8125, 0.0, 0.0, + 0.8229, 0.0, 0.0, + 0.8333, 0.0, 0.0, + 0.8438, 0.0, 0.0, + 0.8542, 0.0, 0.0, + 0.8646, 0.0, 0.0, + 0.8750, 0.0, 0.0, + 0.8854, 0.0, 0.0, + 0.8958, 0.0, 0.0, + 0.9062, 0.0, 0.0, + 0.9167, 0.0, 0.0, + 0.9271, 0.0, 0.0, + 0.9375, 0.0, 0.0, + 0.9479, 0.0, 0.0, + 0.9583, 0.0, 0.0, + 0.9688, 0.0, 0.0, + 0.9792, 0.0, 0.0, + 0.9896, 0.0, 0.0, + 1.0000, 0.0, 0.0, + 1.0000, 0.0104, 0.0, + 1.0000, 0.0208, 0.0, + 1.0000, 0.0312, 0.0, + 1.0000, 0.0417, 0.0, + 1.0000, 0.0521, 0.0, + 1.0000, 0.0625, 0.0, + 1.0000, 0.0729, 0.0, + 1.0000, 0.0833, 0.0, + 1.0000, 0.0938, 0.0, + 1.0000, 0.1042, 0.0, + 1.0000, 0.1146, 0.0, + 1.0000, 0.1250, 0.0, + 1.0000, 0.1354, 0.0, + 1.0000, 0.1458, 0.0, + 1.0000, 0.1562, 0.0, + 1.0000, 0.1667, 0.0, + 1.0000, 0.1771, 0.0, + 1.0000, 0.1875, 0.0, + 1.0000, 0.1979, 0.0, + 1.0000, 0.2083, 0.0, + 1.0000, 0.2188, 0.0, + 1.0000, 0.2292, 0.0, + 1.0000, 0.2396, 0.0, + 1.0000, 0.2500, 0.0, + 1.0000, 0.2604, 0.0, + 1.0000, 0.2708, 0.0, + 1.0000, 0.2812, 0.0, + 1.0000, 0.2917, 0.0, + 1.0000, 0.3021, 0.0, + 1.0000, 0.3125, 0.0, + 1.0000, 0.3229, 0.0, + 1.0000, 0.3333, 0.0, + 1.0000, 0.3438, 0.0, + 1.0000, 0.3542, 0.0, + 1.0000, 0.3646, 0.0, + 1.0000, 0.3750, 0.0, + 1.0000, 0.3854, 0.0, + 1.0000, 0.3958, 0.0, + 1.0000, 0.4062, 0.0, + 1.0000, 0.4167, 0.0, + 1.0000, 0.4271, 0.0, + 1.0000, 0.4375, 0.0, + 1.0000, 0.4479, 0.0, + 1.0000, 0.4583, 0.0, + 1.0000, 0.4688, 0.0, + 1.0000, 0.4792, 0.0, + 1.0000, 0.4896, 0.0, + 1.0000, 0.5000, 0.0, + 1.0000, 0.5104, 0.0, + 1.0000, 0.5208, 0.0, + 1.0000, 0.5312, 0.0, + 1.0000, 0.5417, 0.0, + 1.0000, 0.5521, 0.0, + 1.0000, 0.5625, 0.0, + 1.0000, 0.5729, 0.0, + 1.0000, 0.5833, 0.0, + 1.0000, 0.5938, 0.0, + 1.0000, 0.6042, 0.0, + 1.0000, 0.6146, 0.0, + 1.0000, 0.6250, 0.0, + 1.0000, 0.6354, 0.0, + 1.0000, 0.6458, 0.0, + 1.0000, 0.6562, 0.0, + 1.0000, 0.6667, 0.0, + 1.0000, 0.6771, 0.0, + 1.0000, 0.6875, 0.0, + 1.0000, 0.6979, 0.0, + 1.0000, 0.7083, 0.0, + 1.0000, 0.7188, 0.0, + 1.0000, 0.7292, 0.0, + 1.0000, 0.7396, 0.0, + 1.0000, 0.7500, 0.0, + 1.0000, 0.7604, 0.0, + 1.0000, 0.7708, 0.0, + 1.0000, 0.7812, 0.0, + 1.0000, 0.7917, 0.0, + 1.0000, 0.8021, 0.0, + 1.0000, 0.8125, 0.0, + 1.0000, 0.8229, 0.0, + 1.0000, 0.8333, 0.0, + 1.0000, 0.8438, 0.0, + 1.0000, 0.8542, 0.0, + 1.0000, 0.8646, 0.0, + 1.0000, 0.8750, 0.0, + 1.0000, 0.8854, 0.0, + 1.0000, 0.8958, 0.0, + 1.0000, 0.9062, 0.0, + 1.0000, 0.9167, 0.0, + 1.0000, 0.9271, 0.0, + 1.0000, 0.9375, 0.0, + 1.0000, 0.9479, 0.0, + 1.0000, 0.9583, 0.0, + 1.0000, 0.9688, 0.0, + 1.0000, 0.9792, 0.0, + 1.0000, 0.9896, 0.0, + 1.0000, 1.0000, 0.0, + 1.0000, 1.0000, 0.0156, + 1.0000, 1.0000, 0.0312, + 1.0000, 1.0000, 0.0469, + 1.0000, 1.0000, 0.0625, + 1.0000, 1.0000, 0.0781, + 1.0000, 1.0000, 0.0938, + 1.0000, 1.0000, 0.1094, + 1.0000, 1.0000, 0.1250, + 1.0000, 1.0000, 0.1406, + 1.0000, 1.0000, 0.1562, + 1.0000, 1.0000, 0.1719, + 1.0000, 1.0000, 0.1875, + 1.0000, 1.0000, 0.2031, + 1.0000, 1.0000, 0.2188, + 1.0000, 1.0000, 0.2344, + 1.0000, 1.0000, 0.2500, + 1.0000, 1.0000, 0.2656, + 1.0000, 1.0000, 0.2812, + 1.0000, 1.0000, 0.2969, + 1.0000, 1.0000, 0.3125, + 1.0000, 1.0000, 0.3281, + 1.0000, 1.0000, 0.3438, + 1.0000, 1.0000, 0.3594, + 1.0000, 1.0000, 0.3750, + 1.0000, 1.0000, 0.3906, + 1.0000, 1.0000, 0.4062, + 1.0000, 1.0000, 0.4219, + 1.0000, 1.0000, 0.4375, + 1.0000, 1.0000, 0.4531, + 1.0000, 1.0000, 0.4688, + 1.0000, 1.0000, 0.4844, + 1.0000, 1.0000, 0.5000, + 1.0000, 1.0000, 0.5156, + 1.0000, 1.0000, 0.5312, + 1.0000, 1.0000, 0.5469, + 1.0000, 1.0000, 0.5625, + 1.0000, 1.0000, 0.5781, + 1.0000, 1.0000, 0.5938, + 1.0000, 1.0000, 0.6094, + 1.0000, 1.0000, 0.6250, + 1.0000, 1.0000, 0.6406, + 1.0000, 1.0000, 0.6562, + 1.0000, 1.0000, 0.6719, + 1.0000, 1.0000, 0.6875, + 1.0000, 1.0000, 0.7031, + 1.0000, 1.0000, 0.7188, + 1.0000, 1.0000, 0.7344, + 1.0000, 1.0000, 0.7500, + 1.0000, 1.0000, 0.7656, + 1.0000, 1.0000, 0.7812, + 1.0000, 1.0000, 0.7969, + 1.0000, 1.0000, 0.8125, + 1.0000, 1.0000, 0.8281, + 1.0000, 1.0000, 0.8438, + 1.0000, 1.0000, 0.8594, + 1.0000, 1.0000, 0.8750, + 1.0000, 1.0000, 0.8906, + 1.0000, 1.0000, 0.9062, + 1.0000, 1.0000, 0.9219, + 1.0000, 1.0000, 0.9375, + 1.0000, 1.0000, 0.9531, + 1.0000, 1.0000, 0.9688, + 1.0000, 1.0000, 0.9844, + 1.0000, 1.0000, 1.0000, +}; + +const float ColorMap::m_cool[m_size] = +{ + 0.0, 1.0000, 1.0000, + 0.0039, 0.9961, 1.0000, + 0.0078, 0.9922, 1.0000, + 0.0118, 0.9882, 1.0000, + 0.0157, 0.9843, 1.0000, + 0.0196, 0.9804, 1.0000, + 0.0235, 0.9765, 1.0000, + 0.0275, 0.9725, 1.0000, + 0.0314, 0.9686, 1.0000, + 0.0353, 0.9647, 1.0000, + 0.0392, 0.9608, 1.0000, + 0.0431, 0.9569, 1.0000, + 0.0471, 0.9529, 1.0000, + 0.0510, 0.9490, 1.0000, + 0.0549, 0.9451, 1.0000, + 0.0588, 0.9412, 1.0000, + 0.0627, 0.9373, 1.0000, + 0.0667, 0.9333, 1.0000, + 0.0706, 0.9294, 1.0000, + 0.0745, 0.9255, 1.0000, + 0.0784, 0.9216, 1.0000, + 0.0824, 0.9176, 1.0000, + 0.0863, 0.9137, 1.0000, + 0.0902, 0.9098, 1.0000, + 0.0941, 0.9059, 1.0000, + 0.0980, 0.9020, 1.0000, + 0.1020, 0.8980, 1.0000, + 0.1059, 0.8941, 1.0000, + 0.1098, 0.8902, 1.0000, + 0.1137, 0.8863, 1.0000, + 0.1176, 0.8824, 1.0000, + 0.1216, 0.8784, 1.0000, + 0.1255, 0.8745, 1.0000, + 0.1294, 0.8706, 1.0000, + 0.1333, 0.8667, 1.0000, + 0.1373, 0.8627, 1.0000, + 0.1412, 0.8588, 1.0000, + 0.1451, 0.8549, 1.0000, + 0.1490, 0.8510, 1.0000, + 0.1529, 0.8471, 1.0000, + 0.1569, 0.8431, 1.0000, + 0.1608, 0.8392, 1.0000, + 0.1647, 0.8353, 1.0000, + 0.1686, 0.8314, 1.0000, + 0.1725, 0.8275, 1.0000, + 0.1765, 0.8235, 1.0000, + 0.1804, 0.8196, 1.0000, + 0.1843, 0.8157, 1.0000, + 0.1882, 0.8118, 1.0000, + 0.1922, 0.8078, 1.0000, + 0.1961, 0.8039, 1.0000, + 0.2000, 0.8000, 1.0000, + 0.2039, 0.7961, 1.0000, + 0.2078, 0.7922, 1.0000, + 0.2118, 0.7882, 1.0000, + 0.2157, 0.7843, 1.0000, + 0.2196, 0.7804, 1.0000, + 0.2235, 0.7765, 1.0000, + 0.2275, 0.7725, 1.0000, + 0.2314, 0.7686, 1.0000, + 0.2353, 0.7647, 1.0000, + 0.2392, 0.7608, 1.0000, + 0.2431, 0.7569, 1.0000, + 0.2471, 0.7529, 1.0000, + 0.2510, 0.7490, 1.0000, + 0.2549, 0.7451, 1.0000, + 0.2588, 0.7412, 1.0000, + 0.2627, 0.7373, 1.0000, + 0.2667, 0.7333, 1.0000, + 0.2706, 0.7294, 1.0000, + 0.2745, 0.7255, 1.0000, + 0.2784, 0.7216, 1.0000, + 0.2824, 0.7176, 1.0000, + 0.2863, 0.7137, 1.0000, + 0.2902, 0.7098, 1.0000, + 0.2941, 0.7059, 1.0000, + 0.2980, 0.7020, 1.0000, + 0.3020, 0.6980, 1.0000, + 0.3059, 0.6941, 1.0000, + 0.3098, 0.6902, 1.0000, + 0.3137, 0.6863, 1.0000, + 0.3176, 0.6824, 1.0000, + 0.3216, 0.6784, 1.0000, + 0.3255, 0.6745, 1.0000, + 0.3294, 0.6706, 1.0000, + 0.3333, 0.6667, 1.0000, + 0.3373, 0.6627, 1.0000, + 0.3412, 0.6588, 1.0000, + 0.3451, 0.6549, 1.0000, + 0.3490, 0.6510, 1.0000, + 0.3529, 0.6471, 1.0000, + 0.3569, 0.6431, 1.0000, + 0.3608, 0.6392, 1.0000, + 0.3647, 0.6353, 1.0000, + 0.3686, 0.6314, 1.0000, + 0.3725, 0.6275, 1.0000, + 0.3765, 0.6235, 1.0000, + 0.3804, 0.6196, 1.0000, + 0.3843, 0.6157, 1.0000, + 0.3882, 0.6118, 1.0000, + 0.3922, 0.6078, 1.0000, + 0.3961, 0.6039, 1.0000, + 0.4000, 0.6000, 1.0000, + 0.4039, 0.5961, 1.0000, + 0.4078, 0.5922, 1.0000, + 0.4118, 0.5882, 1.0000, + 0.4157, 0.5843, 1.0000, + 0.4196, 0.5804, 1.0000, + 0.4235, 0.5765, 1.0000, + 0.4275, 0.5725, 1.0000, + 0.4314, 0.5686, 1.0000, + 0.4353, 0.5647, 1.0000, + 0.4392, 0.5608, 1.0000, + 0.4431, 0.5569, 1.0000, + 0.4471, 0.5529, 1.0000, + 0.4510, 0.5490, 1.0000, + 0.4549, 0.5451, 1.0000, + 0.4588, 0.5412, 1.0000, + 0.4627, 0.5373, 1.0000, + 0.4667, 0.5333, 1.0000, + 0.4706, 0.5294, 1.0000, + 0.4745, 0.5255, 1.0000, + 0.4784, 0.5216, 1.0000, + 0.4824, 0.5176, 1.0000, + 0.4863, 0.5137, 1.0000, + 0.4902, 0.5098, 1.0000, + 0.4941, 0.5059, 1.0000, + 0.4980, 0.5020, 1.0000, + 0.5020, 0.4980, 1.0000, + 0.5059, 0.4941, 1.0000, + 0.5098, 0.4902, 1.0000, + 0.5137, 0.4863, 1.0000, + 0.5176, 0.4824, 1.0000, + 0.5216, 0.4784, 1.0000, + 0.5255, 0.4745, 1.0000, + 0.5294, 0.4706, 1.0000, + 0.5333, 0.4667, 1.0000, + 0.5373, 0.4627, 1.0000, + 0.5412, 0.4588, 1.0000, + 0.5451, 0.4549, 1.0000, + 0.5490, 0.4510, 1.0000, + 0.5529, 0.4471, 1.0000, + 0.5569, 0.4431, 1.0000, + 0.5608, 0.4392, 1.0000, + 0.5647, 0.4353, 1.0000, + 0.5686, 0.4314, 1.0000, + 0.5725, 0.4275, 1.0000, + 0.5765, 0.4235, 1.0000, + 0.5804, 0.4196, 1.0000, + 0.5843, 0.4157, 1.0000, + 0.5882, 0.4118, 1.0000, + 0.5922, 0.4078, 1.0000, + 0.5961, 0.4039, 1.0000, + 0.6000, 0.4000, 1.0000, + 0.6039, 0.3961, 1.0000, + 0.6078, 0.3922, 1.0000, + 0.6118, 0.3882, 1.0000, + 0.6157, 0.3843, 1.0000, + 0.6196, 0.3804, 1.0000, + 0.6235, 0.3765, 1.0000, + 0.6275, 0.3725, 1.0000, + 0.6314, 0.3686, 1.0000, + 0.6353, 0.3647, 1.0000, + 0.6392, 0.3608, 1.0000, + 0.6431, 0.3569, 1.0000, + 0.6471, 0.3529, 1.0000, + 0.6510, 0.3490, 1.0000, + 0.6549, 0.3451, 1.0000, + 0.6588, 0.3412, 1.0000, + 0.6627, 0.3373, 1.0000, + 0.6667, 0.3333, 1.0000, + 0.6706, 0.3294, 1.0000, + 0.6745, 0.3255, 1.0000, + 0.6784, 0.3216, 1.0000, + 0.6824, 0.3176, 1.0000, + 0.6863, 0.3137, 1.0000, + 0.6902, 0.3098, 1.0000, + 0.6941, 0.3059, 1.0000, + 0.6980, 0.3020, 1.0000, + 0.7020, 0.2980, 1.0000, + 0.7059, 0.2941, 1.0000, + 0.7098, 0.2902, 1.0000, + 0.7137, 0.2863, 1.0000, + 0.7176, 0.2824, 1.0000, + 0.7216, 0.2784, 1.0000, + 0.7255, 0.2745, 1.0000, + 0.7294, 0.2706, 1.0000, + 0.7333, 0.2667, 1.0000, + 0.7373, 0.2627, 1.0000, + 0.7412, 0.2588, 1.0000, + 0.7451, 0.2549, 1.0000, + 0.7490, 0.2510, 1.0000, + 0.7529, 0.2471, 1.0000, + 0.7569, 0.2431, 1.0000, + 0.7608, 0.2392, 1.0000, + 0.7647, 0.2353, 1.0000, + 0.7686, 0.2314, 1.0000, + 0.7725, 0.2275, 1.0000, + 0.7765, 0.2235, 1.0000, + 0.7804, 0.2196, 1.0000, + 0.7843, 0.2157, 1.0000, + 0.7882, 0.2118, 1.0000, + 0.7922, 0.2078, 1.0000, + 0.7961, 0.2039, 1.0000, + 0.8000, 0.2000, 1.0000, + 0.8039, 0.1961, 1.0000, + 0.8078, 0.1922, 1.0000, + 0.8118, 0.1882, 1.0000, + 0.8157, 0.1843, 1.0000, + 0.8196, 0.1804, 1.0000, + 0.8235, 0.1765, 1.0000, + 0.8275, 0.1725, 1.0000, + 0.8314, 0.1686, 1.0000, + 0.8353, 0.1647, 1.0000, + 0.8392, 0.1608, 1.0000, + 0.8431, 0.1569, 1.0000, + 0.8471, 0.1529, 1.0000, + 0.8510, 0.1490, 1.0000, + 0.8549, 0.1451, 1.0000, + 0.8588, 0.1412, 1.0000, + 0.8627, 0.1373, 1.0000, + 0.8667, 0.1333, 1.0000, + 0.8706, 0.1294, 1.0000, + 0.8745, 0.1255, 1.0000, + 0.8784, 0.1216, 1.0000, + 0.8824, 0.1176, 1.0000, + 0.8863, 0.1137, 1.0000, + 0.8902, 0.1098, 1.0000, + 0.8941, 0.1059, 1.0000, + 0.8980, 0.1020, 1.0000, + 0.9020, 0.0980, 1.0000, + 0.9059, 0.0941, 1.0000, + 0.9098, 0.0902, 1.0000, + 0.9137, 0.0863, 1.0000, + 0.9176, 0.0824, 1.0000, + 0.9216, 0.0784, 1.0000, + 0.9255, 0.0745, 1.0000, + 0.9294, 0.0706, 1.0000, + 0.9333, 0.0667, 1.0000, + 0.9373, 0.0627, 1.0000, + 0.9412, 0.0588, 1.0000, + 0.9451, 0.0549, 1.0000, + 0.9490, 0.0510, 1.0000, + 0.9529, 0.0471, 1.0000, + 0.9569, 0.0431, 1.0000, + 0.9608, 0.0392, 1.0000, + 0.9647, 0.0353, 1.0000, + 0.9686, 0.0314, 1.0000, + 0.9725, 0.0275, 1.0000, + 0.9765, 0.0235, 1.0000, + 0.9804, 0.0196, 1.0000, + 0.9843, 0.0157, 1.0000, + 0.9882, 0.0118, 1.0000, + 0.9922, 0.0078, 1.0000, + 0.9961, 0.0039, 1.0000, + 1.0000, 0.0, 1.0000, +}; + +const float ColorMap::m_batlow[m_size] = +{ + 0.005193, 0.098238, 0.349842, + 0.009065, 0.104487, 0.350933, + 0.012963, 0.110779, 0.351992, + 0.016530, 0.116913, 0.353070, + 0.019936, 0.122985, 0.354120, + 0.023189, 0.129035, 0.355182, + 0.026291, 0.135044, 0.356210, + 0.029245, 0.140964, 0.357239, + 0.032053, 0.146774, 0.358239, + 0.034853, 0.152558, 0.359233, + 0.037449, 0.158313, 0.360216, + 0.039845, 0.163978, 0.361187, + 0.042104, 0.169557, 0.362151, + 0.044069, 0.175053, 0.363084, + 0.045905, 0.180460, 0.364007, + 0.047665, 0.185844, 0.364915, + 0.049378, 0.191076, 0.365810, + 0.050795, 0.196274, 0.366684, + 0.052164, 0.201323, 0.367524, + 0.053471, 0.206357, 0.368370, + 0.054721, 0.211234, 0.369184, + 0.055928, 0.216046, 0.369974, + 0.057033, 0.220754, 0.370750, + 0.058032, 0.225340, 0.371509, + 0.059164, 0.229842, 0.372252, + 0.060167, 0.234299, 0.372978, + 0.061052, 0.238625, 0.373691, + 0.062060, 0.242888, 0.374386, + 0.063071, 0.247085, 0.375050, + 0.063982, 0.251213, 0.375709, + 0.064936, 0.255264, 0.376362, + 0.065903, 0.259257, 0.376987, + 0.066899, 0.263188, 0.377594, + 0.067921, 0.267056, 0.378191, + 0.069002, 0.270922, 0.378774, + 0.070001, 0.274713, 0.379342, + 0.071115, 0.278497, 0.379895, + 0.072192, 0.282249, 0.380434, + 0.073440, 0.285942, 0.380957, + 0.074595, 0.289653, 0.381452, + 0.075833, 0.293321, 0.381922, + 0.077136, 0.296996, 0.382376, + 0.078517, 0.300622, 0.382814, + 0.079984, 0.304252, 0.383224, + 0.081553, 0.307858, 0.383598, + 0.083082, 0.311461, 0.383936, + 0.084778, 0.315043, 0.384240, + 0.086503, 0.318615, 0.384506, + 0.088353, 0.322167, 0.384731, + 0.090281, 0.325685, 0.384910, + 0.092304, 0.329220, 0.385040, + 0.094462, 0.332712, 0.385116, + 0.096618, 0.336161, 0.385134, + 0.099015, 0.339621, 0.385090, + 0.101481, 0.343036, 0.384981, + 0.104078, 0.346410, 0.384801, + 0.106842, 0.349774, 0.384548, + 0.109695, 0.353098, 0.384217, + 0.112655, 0.356391, 0.383807, + 0.115748, 0.359638, 0.383310, + 0.118992, 0.362849, 0.382713, + 0.122320, 0.366030, 0.382026, + 0.125889, 0.369160, 0.381259, + 0.129519, 0.372238, 0.380378, + 0.133298, 0.375282, 0.379395, + 0.137212, 0.378282, 0.378315, + 0.141260, 0.381240, 0.377135, + 0.145432, 0.384130, 0.375840, + 0.149706, 0.386975, 0.374449, + 0.154073, 0.389777, 0.372934, + 0.158620, 0.392531, 0.371320, + 0.163246, 0.395237, 0.369609, + 0.167952, 0.397889, 0.367784, + 0.172788, 0.400496, 0.365867, + 0.177752, 0.403041, 0.363833, + 0.182732, 0.405551, 0.361714, + 0.187886, 0.408003, 0.359484, + 0.193050, 0.410427, 0.357177, + 0.198310, 0.412798, 0.354767, + 0.203676, 0.415116, 0.352253, + 0.209075, 0.417412, 0.349677, + 0.214555, 0.419661, 0.347019, + 0.220112, 0.421864, 0.344261, + 0.225707, 0.424049, 0.341459, + 0.231362, 0.426197, 0.338572, + 0.237075, 0.428325, 0.335634, + 0.242795, 0.430418, 0.332635, + 0.248617, 0.432493, 0.329571, + 0.254452, 0.434529, 0.326434, + 0.260320, 0.436556, 0.323285, + 0.266241, 0.438555, 0.320085, + 0.272168, 0.440541, 0.316831, + 0.278171, 0.442524, 0.313552, + 0.284175, 0.444484, 0.310243, + 0.290214, 0.446420, 0.306889, + 0.296294, 0.448357, 0.303509, + 0.302379, 0.450282, 0.300122, + 0.308517, 0.452205, 0.296721, + 0.314648, 0.454107, 0.293279, + 0.320834, 0.456006, 0.289841, + 0.327007, 0.457900, 0.286377, + 0.333235, 0.459794, 0.282937, + 0.339469, 0.461685, 0.279468, + 0.345703, 0.463563, 0.275998, + 0.351976, 0.465440, 0.272492, + 0.358277, 0.467331, 0.269037, + 0.364589, 0.469213, 0.265543, + 0.370922, 0.471085, 0.262064, + 0.377291, 0.472952, 0.258588, + 0.383675, 0.474842, 0.255131, + 0.390070, 0.476711, 0.251665, + 0.396505, 0.478587, 0.248212, + 0.402968, 0.480466, 0.244731, + 0.409455, 0.482351, 0.241314, + 0.415967, 0.484225, 0.237895, + 0.422507, 0.486113, 0.234493, + 0.429094, 0.488011, 0.231096, + 0.435714, 0.489890, 0.227728, + 0.442365, 0.491795, 0.224354, + 0.449052, 0.493684, 0.221074, + 0.455774, 0.495585, 0.217774, + 0.462539, 0.497497, 0.214518, + 0.469368, 0.499393, 0.211318, + 0.476221, 0.501314, 0.208148, + 0.483123, 0.503216, 0.205037, + 0.490081, 0.505137, 0.201976, + 0.497089, 0.507058, 0.198994, + 0.504153, 0.508984, 0.196118, + 0.511253, 0.510898, 0.193296, + 0.518425, 0.512822, 0.190566, + 0.525637, 0.514746, 0.187990, + 0.532907, 0.516662, 0.185497, + 0.540225, 0.518584, 0.183099, + 0.547599, 0.520486, 0.180884, + 0.555024, 0.522391, 0.178854, + 0.562506, 0.524293, 0.176964, + 0.570016, 0.526186, 0.175273, + 0.577582, 0.528058, 0.173775, + 0.585199, 0.529927, 0.172493, + 0.592846, 0.531777, 0.171449, + 0.600520, 0.533605, 0.170648, + 0.608240, 0.535423, 0.170104, + 0.615972, 0.537231, 0.169826, + 0.623739, 0.539002, 0.169814, + 0.631513, 0.540752, 0.170075, + 0.639301, 0.542484, 0.170622, + 0.647098, 0.544183, 0.171465, + 0.654889, 0.545863, 0.172603, + 0.662691, 0.547503, 0.174044, + 0.670477, 0.549127, 0.175747, + 0.678244, 0.550712, 0.177803, + 0.685995, 0.552274, 0.180056, + 0.693720, 0.553797, 0.182610, + 0.701421, 0.555294, 0.185478, + 0.709098, 0.556772, 0.188546, + 0.716731, 0.558205, 0.191851, + 0.724322, 0.559628, 0.195408, + 0.731878, 0.561011, 0.199174, + 0.739393, 0.562386, 0.203179, + 0.746850, 0.563725, 0.207375, + 0.754268, 0.565033, 0.211761, + 0.761629, 0.566344, 0.216322, + 0.768942, 0.567630, 0.221045, + 0.776208, 0.568899, 0.225930, + 0.783416, 0.570162, 0.230962, + 0.790568, 0.571421, 0.236160, + 0.797665, 0.572682, 0.241490, + 0.804709, 0.573928, 0.246955, + 0.811692, 0.575187, 0.252572, + 0.818610, 0.576462, 0.258303, + 0.825472, 0.577725, 0.264197, + 0.832272, 0.579026, 0.270211, + 0.838999, 0.580339, 0.276353, + 0.845657, 0.581672, 0.282631, + 0.852247, 0.583037, 0.289036, + 0.858747, 0.584440, 0.295572, + 0.865168, 0.585882, 0.302255, + 0.871505, 0.587352, 0.309112, + 0.877741, 0.588873, 0.316081, + 0.883878, 0.590450, 0.323195, + 0.889900, 0.592087, 0.330454, + 0.895809, 0.593765, 0.337865, + 0.901590, 0.595507, 0.345429, + 0.907242, 0.597319, 0.353142, + 0.912746, 0.599191, 0.360986, + 0.918103, 0.601126, 0.368999, + 0.923300, 0.603137, 0.377139, + 0.928323, 0.605212, 0.385404, + 0.933176, 0.607369, 0.393817, + 0.937850, 0.609582, 0.402345, + 0.942332, 0.611867, 0.411006, + 0.946612, 0.614218, 0.419767, + 0.950697, 0.616649, 0.428624, + 0.954574, 0.619137, 0.437582, + 0.958244, 0.621671, 0.446604, + 0.961696, 0.624282, 0.455702, + 0.964943, 0.626934, 0.464860, + 0.967983, 0.629639, 0.474057, + 0.970804, 0.632394, 0.483290, + 0.973424, 0.635183, 0.492547, + 0.975835, 0.638012, 0.501826, + 0.978052, 0.640868, 0.511090, + 0.980079, 0.643752, 0.520350, + 0.981918, 0.646664, 0.529602, + 0.983574, 0.649590, 0.538819, + 0.985066, 0.652522, 0.547998, + 0.986392, 0.655470, 0.557142, + 0.987567, 0.658422, 0.566226, + 0.988596, 0.661378, 0.575265, + 0.989496, 0.664329, 0.584246, + 0.990268, 0.667280, 0.593174, + 0.990926, 0.670230, 0.602031, + 0.991479, 0.673165, 0.610835, + 0.991935, 0.676091, 0.619575, + 0.992305, 0.679007, 0.628251, + 0.992595, 0.681914, 0.636869, + 0.992813, 0.684815, 0.645423, + 0.992967, 0.687705, 0.653934, + 0.993064, 0.690579, 0.662398, + 0.993111, 0.693451, 0.670810, + 0.993112, 0.696314, 0.679177, + 0.993074, 0.699161, 0.687519, + 0.993002, 0.702006, 0.695831, + 0.992900, 0.704852, 0.704114, + 0.992771, 0.707689, 0.712380, + 0.992619, 0.710530, 0.720639, + 0.992447, 0.713366, 0.728892, + 0.992258, 0.716210, 0.737146, + 0.992054, 0.719049, 0.745403, + 0.991837, 0.721893, 0.753673, + 0.991607, 0.724754, 0.761959, + 0.991367, 0.727614, 0.770270, + 0.991116, 0.730489, 0.778606, + 0.990855, 0.733373, 0.786976, + 0.990586, 0.736265, 0.795371, + 0.990307, 0.739184, 0.803810, + 0.990018, 0.742102, 0.812285, + 0.989720, 0.745039, 0.820804, + 0.989411, 0.747997, 0.829372, + 0.989089, 0.750968, 0.837979, + 0.988754, 0.753949, 0.846627, + 0.988406, 0.756949, 0.855332, + 0.988046, 0.759964, 0.864078, + 0.987672, 0.762996, 0.872864, + 0.987280, 0.766047, 0.881699, + 0.986868, 0.769105, 0.890573, + 0.986435, 0.772184, 0.899493, + 0.985980, 0.775272, 0.908448, + 0.985503, 0.778378, 0.917444, + 0.985002, 0.781495, 0.926468, + 0.984473, 0.784624, 0.935531, + 0.983913, 0.787757, 0.944626, + 0.983322, 0.790905, 0.953748, + 0.982703, 0.794068, 0.962895, + 0.982048, 0.797228, 0.972070, + 0.981354, 0.800406, 0.981267 +}; + +const float ColorMap::m_hawaii[m_size] = +{ + 0.550541, 0.006842, 0.451980, + 0.551494, 0.015367, 0.447972, + 0.552426, 0.023795, 0.443998, + 0.553328, 0.032329, 0.440021, + 0.554227, 0.041170, 0.436063, + 0.555098, 0.049286, 0.432125, + 0.555948, 0.056667, 0.428188, + 0.556797, 0.063525, 0.424272, + 0.557619, 0.069970, 0.420377, + 0.558415, 0.076028, 0.416509, + 0.559210, 0.081936, 0.412663, + 0.559991, 0.087507, 0.408823, + 0.560746, 0.092811, 0.405012, + 0.561495, 0.098081, 0.401237, + 0.562235, 0.103128, 0.397471, + 0.562954, 0.108005, 0.393736, + 0.563663, 0.112872, 0.390025, + 0.564355, 0.117530, 0.386344, + 0.565032, 0.122122, 0.382698, + 0.565709, 0.126681, 0.379074, + 0.566380, 0.131171, 0.375474, + 0.567037, 0.135542, 0.371905, + 0.567679, 0.139872, 0.368378, + 0.568312, 0.144198, 0.364861, + 0.568939, 0.148416, 0.361384, + 0.569559, 0.152618, 0.357942, + 0.570171, 0.156806, 0.354519, + 0.570777, 0.160934, 0.351127, + 0.571377, 0.165008, 0.347764, + 0.571972, 0.169120, 0.344417, + 0.572562, 0.173131, 0.341120, + 0.573142, 0.177166, 0.337836, + 0.573711, 0.181138, 0.334602, + 0.574276, 0.185151, 0.331356, + 0.574840, 0.189095, 0.328170, + 0.575406, 0.193035, 0.324992, + 0.575967, 0.196978, 0.321854, + 0.576518, 0.200854, 0.318740, + 0.577060, 0.204783, 0.315654, + 0.577596, 0.208664, 0.312565, + 0.578135, 0.212545, 0.309542, + 0.578676, 0.216431, 0.306516, + 0.579214, 0.220287, 0.303496, + 0.579746, 0.224106, 0.300518, + 0.580271, 0.227977, 0.297566, + 0.580793, 0.231817, 0.294618, + 0.581315, 0.235646, 0.291715, + 0.581835, 0.239463, 0.288810, + 0.582353, 0.243268, 0.285910, + 0.582870, 0.247097, 0.283066, + 0.583386, 0.250916, 0.280201, + 0.583901, 0.254739, 0.277381, + 0.584416, 0.258531, 0.274552, + 0.584931, 0.262342, 0.271740, + 0.585443, 0.266156, 0.268980, + 0.585951, 0.269966, 0.266198, + 0.586456, 0.273771, 0.263439, + 0.586961, 0.277575, 0.260676, + 0.587466, 0.281374, 0.257925, + 0.587972, 0.285180, 0.255221, + 0.588478, 0.289013, 0.252494, + 0.588984, 0.292818, 0.249767, + 0.589491, 0.296652, 0.247081, + 0.589999, 0.300465, 0.244376, + 0.590507, 0.304300, 0.241716, + 0.591016, 0.308135, 0.239031, + 0.591526, 0.311969, 0.236379, + 0.592038, 0.315846, 0.233692, + 0.592548, 0.319698, 0.231058, + 0.593055, 0.323559, 0.228420, + 0.593562, 0.327429, 0.225773, + 0.594071, 0.331309, 0.223134, + 0.594583, 0.335229, 0.220510, + 0.595095, 0.339131, 0.217865, + 0.595609, 0.343048, 0.215226, + 0.596126, 0.346976, 0.212613, + 0.596645, 0.350921, 0.209994, + 0.597164, 0.354880, 0.207388, + 0.597680, 0.358830, 0.204776, + 0.598196, 0.362821, 0.202147, + 0.598721, 0.366829, 0.199533, + 0.599248, 0.370837, 0.196964, + 0.599771, 0.374879, 0.194370, + 0.600294, 0.378931, 0.191738, + 0.600819, 0.383009, 0.189149, + 0.601346, 0.387090, 0.186548, + 0.601874, 0.391215, 0.183949, + 0.602403, 0.395345, 0.181345, + 0.602933, 0.399486, 0.178782, + 0.603464, 0.403678, 0.176158, + 0.603995, 0.407873, 0.173594, + 0.604521, 0.412102, 0.171015, + 0.605043, 0.416348, 0.168436, + 0.605562, 0.420618, 0.165848, + 0.606084, 0.424928, 0.163317, + 0.606609, 0.429252, 0.160731, + 0.607129, 0.433600, 0.158195, + 0.607639, 0.437998, 0.155649, + 0.608144, 0.442412, 0.153086, + 0.608644, 0.446848, 0.150582, + 0.609134, 0.451324, 0.148071, + 0.609610, 0.455826, 0.145615, + 0.610079, 0.460356, 0.143119, + 0.610542, 0.464933, 0.140685, + 0.610991, 0.469544, 0.138267, + 0.611421, 0.474170, 0.135829, + 0.611833, 0.478839, 0.133514, + 0.612226, 0.483539, 0.131212, + 0.612600, 0.488287, 0.128920, + 0.612950, 0.493049, 0.126718, + 0.613275, 0.497875, 0.124574, + 0.613572, 0.502705, 0.122487, + 0.613837, 0.507592, 0.120512, + 0.614069, 0.512502, 0.118669, + 0.614264, 0.517459, 0.116848, + 0.614418, 0.522434, 0.115160, + 0.614530, 0.527456, 0.113657, + 0.614594, 0.532510, 0.112266, + 0.614607, 0.537595, 0.111032, + 0.614566, 0.542708, 0.109999, + 0.614468, 0.547849, 0.109114, + 0.614308, 0.553016, 0.108421, + 0.614082, 0.558212, 0.108010, + 0.613787, 0.563446, 0.107850, + 0.613419, 0.568682, 0.107943, + 0.612974, 0.573946, 0.108312, + 0.612449, 0.579232, 0.109026, + 0.611842, 0.584522, 0.110040, + 0.611148, 0.589820, 0.111320, + 0.610353, 0.595132, 0.112963, + 0.609471, 0.600443, 0.114856, + 0.608494, 0.605748, 0.117169, + 0.607411, 0.611060, 0.119811, + 0.606215, 0.616350, 0.122763, + 0.604930, 0.621618, 0.126124, + 0.603536, 0.626876, 0.129757, + 0.602026, 0.632107, 0.133692, + 0.600413, 0.637306, 0.137967, + 0.598689, 0.642469, 0.142496, + 0.596862, 0.647588, 0.147334, + 0.594916, 0.652662, 0.152416, + 0.592872, 0.657697, 0.157790, + 0.590707, 0.662667, 0.163419, + 0.588441, 0.667579, 0.169258, + 0.586085, 0.672429, 0.175280, + 0.583613, 0.677213, 0.181507, + 0.581049, 0.681916, 0.187985, + 0.578388, 0.686560, 0.194586, + 0.575646, 0.691121, 0.201310, + 0.572809, 0.695614, 0.208243, + 0.569878, 0.700018, 0.215285, + 0.566888, 0.704346, 0.222470, + 0.563814, 0.708597, 0.229738, + 0.560662, 0.712753, 0.237171, + 0.557458, 0.716845, 0.244622, + 0.554182, 0.720839, 0.252219, + 0.550853, 0.724766, 0.259874, + 0.547470, 0.728605, 0.267574, + 0.544043, 0.732376, 0.275394, + 0.540571, 0.736058, 0.283238, + 0.537067, 0.739685, 0.291141, + 0.533507, 0.743228, 0.299094, + 0.529936, 0.746702, 0.307079, + 0.526333, 0.750112, 0.315113, + 0.522696, 0.753461, 0.323192, + 0.519049, 0.756752, 0.331281, + 0.515367, 0.759983, 0.339437, + 0.511681, 0.763162, 0.347595, + 0.507990, 0.766293, 0.355785, + 0.504280, 0.769372, 0.363984, + 0.500550, 0.772410, 0.372217, + 0.496820, 0.775405, 0.380485, + 0.493085, 0.778365, 0.388763, + 0.489350, 0.781287, 0.397049, + 0.485614, 0.784180, 0.405376, + 0.481884, 0.787038, 0.413711, + 0.478142, 0.789866, 0.422057, + 0.474411, 0.792674, 0.430440, + 0.470680, 0.795455, 0.438824, + 0.466955, 0.798219, 0.447235, + 0.463220, 0.800964, 0.455667, + 0.459518, 0.803693, 0.464121, + 0.455810, 0.806409, 0.472577, + 0.452124, 0.809110, 0.481054, + 0.448436, 0.811796, 0.489555, + 0.444772, 0.814472, 0.498091, + 0.441108, 0.817144, 0.506616, + 0.437487, 0.819803, 0.515175, + 0.433858, 0.822465, 0.523755, + 0.430280, 0.825110, 0.532352, + 0.426720, 0.827756, 0.540960, + 0.423186, 0.830401, 0.549598, + 0.419708, 0.833036, 0.558241, + 0.416257, 0.835673, 0.566923, + 0.412868, 0.838305, 0.575612, + 0.409520, 0.840937, 0.584314, + 0.406245, 0.843562, 0.593044, + 0.403035, 0.846190, 0.601780, + 0.399905, 0.848819, 0.610541, + 0.396872, 0.851439, 0.619320, + 0.393950, 0.854061, 0.628104, + 0.391152, 0.856683, 0.636905, + 0.388472, 0.859301, 0.645709, + 0.385935, 0.861918, 0.654530, + 0.383585, 0.864526, 0.663367, + 0.381407, 0.867128, 0.672196, + 0.379424, 0.869728, 0.681023, + 0.377672, 0.872325, 0.689863, + 0.376170, 0.874907, 0.698686, + 0.374923, 0.877482, 0.707507, + 0.373981, 0.880045, 0.716318, + 0.373340, 0.882596, 0.725106, + 0.373043, 0.885136, 0.733865, + 0.373112, 0.887654, 0.742601, + 0.373570, 0.890156, 0.751300, + 0.374439, 0.892639, 0.759946, + 0.375723, 0.895095, 0.768546, + 0.377467, 0.897524, 0.777098, + 0.379671, 0.899923, 0.785572, + 0.382352, 0.902288, 0.793974, + 0.385527, 0.904619, 0.802283, + 0.389213, 0.906913, 0.810503, + 0.393385, 0.909161, 0.818619, + 0.398074, 0.911369, 0.826627, + 0.403255, 0.913528, 0.834507, + 0.408926, 0.915628, 0.842255, + 0.415083, 0.917688, 0.849859, + 0.421704, 0.919678, 0.857309, + 0.428791, 0.921615, 0.864606, + 0.436305, 0.923489, 0.871734, + 0.444231, 0.925293, 0.878682, + 0.452541, 0.927032, 0.885454, + 0.461203, 0.928705, 0.892037, + 0.470211, 0.930311, 0.898424, + 0.479521, 0.931839, 0.904620, + 0.489103, 0.933297, 0.910617, + 0.498950, 0.934685, 0.916408, + 0.509019, 0.936004, 0.922005, + 0.519281, 0.937246, 0.927394, + 0.529715, 0.938416, 0.932588, + 0.540292, 0.939517, 0.937592, + 0.550997, 0.940549, 0.942401, + 0.561804, 0.941509, 0.947020, + 0.572686, 0.942411, 0.951459, + 0.583621, 0.943243, 0.955728, + 0.594606, 0.944015, 0.959825, + 0.605610, 0.944731, 0.963765, + 0.616637, 0.945388, 0.967563, + 0.627648, 0.945989, 0.971214, + 0.638645, 0.946543, 0.974739, + 0.649620, 0.947052, 0.978146, + 0.660548, 0.947515, 0.981449, + 0.671439, 0.947934, 0.984653, + 0.682276, 0.948316, 0.987765, + 0.693064, 0.948662, 0.990803, + 0.703779, 0.948977, 0.993775, +}; + +const float ColorMap::m_acton[m_size] = +{ + 0.180627, 0.129916, 0.300244, + 0.184610, 0.133361, 0.303782, + 0.188588, 0.136829, 0.307330, + 0.192547, 0.140323, 0.310900, + 0.196548, 0.143832, 0.314443, + 0.200488, 0.147341, 0.318015, + 0.204515, 0.150846, 0.321581, + 0.208493, 0.154369, 0.325153, + 0.212499, 0.157916, 0.328752, + 0.216523, 0.161488, 0.332345, + 0.220543, 0.164997, 0.335928, + 0.224526, 0.168579, 0.339540, + 0.228599, 0.172138, 0.343143, + 0.232627, 0.175706, 0.346749, + 0.236700, 0.179309, 0.350370, + 0.240738, 0.182887, 0.353978, + 0.244814, 0.186502, 0.357622, + 0.248930, 0.190104, 0.361237, + 0.253030, 0.193733, 0.364873, + 0.257149, 0.197344, 0.368523, + 0.261278, 0.200942, 0.372155, + 0.265425, 0.204605, 0.375810, + 0.269603, 0.208226, 0.379461, + 0.273783, 0.211881, 0.383124, + 0.277979, 0.215505, 0.386769, + 0.282205, 0.219155, 0.390435, + 0.286418, 0.222807, 0.394097, + 0.290686, 0.226458, 0.397768, + 0.294964, 0.230077, 0.401431, + 0.299282, 0.233732, 0.405095, + 0.303592, 0.237416, 0.408762, + 0.307941, 0.241039, 0.412436, + 0.312311, 0.244675, 0.416090, + 0.316729, 0.248352, 0.419755, + 0.321155, 0.251983, 0.423403, + 0.325598, 0.255610, 0.427069, + 0.330097, 0.259251, 0.430713, + 0.334616, 0.262862, 0.434349, + 0.339156, 0.266474, 0.437993, + 0.343715, 0.270084, 0.441616, + 0.348325, 0.273673, 0.445226, + 0.352962, 0.277246, 0.448836, + 0.357638, 0.280787, 0.452429, + 0.362328, 0.284342, 0.455994, + 0.367058, 0.287868, 0.459557, + 0.371818, 0.291393, 0.463085, + 0.376633, 0.294861, 0.466617, + 0.381462, 0.298321, 0.470119, + 0.386314, 0.301752, 0.473581, + 0.391226, 0.305175, 0.477035, + 0.396151, 0.308549, 0.480453, + 0.401118, 0.311869, 0.483838, + 0.406106, 0.315187, 0.487202, + 0.411121, 0.318455, 0.490524, + 0.416168, 0.321674, 0.493799, + 0.421236, 0.324852, 0.497039, + 0.426337, 0.327992, 0.500233, + 0.431473, 0.331069, 0.503383, + 0.436612, 0.334121, 0.506490, + 0.441778, 0.337097, 0.509547, + 0.446951, 0.340021, 0.512544, + 0.452154, 0.342888, 0.515484, + 0.457349, 0.345672, 0.518381, + 0.462561, 0.348416, 0.521200, + 0.467788, 0.351091, 0.523965, + 0.473007, 0.353679, 0.526669, + 0.478238, 0.356221, 0.529300, + 0.483458, 0.358663, 0.531858, + 0.488682, 0.361046, 0.534347, + 0.493892, 0.363356, 0.536777, + 0.499089, 0.365589, 0.539117, + 0.504283, 0.367733, 0.541384, + 0.509444, 0.369805, 0.543590, + 0.514595, 0.371793, 0.545716, + 0.519714, 0.373712, 0.547758, + 0.524818, 0.375534, 0.549728, + 0.529895, 0.377288, 0.551623, + 0.534937, 0.378952, 0.553431, + 0.539948, 0.380544, 0.555173, + 0.544933, 0.382041, 0.556844, + 0.549880, 0.383480, 0.558423, + 0.554790, 0.384818, 0.559949, + 0.559666, 0.386090, 0.561388, + 0.564500, 0.387290, 0.562768, + 0.569294, 0.388425, 0.564070, + 0.574054, 0.389478, 0.565293, + 0.578778, 0.390463, 0.566473, + 0.583458, 0.391391, 0.567579, + 0.588099, 0.392240, 0.568620, + 0.592717, 0.393035, 0.569606, + 0.597284, 0.393772, 0.570538, + 0.601814, 0.394456, 0.571418, + 0.606313, 0.395091, 0.572252, + 0.610786, 0.395679, 0.573036, + 0.615216, 0.396214, 0.573769, + 0.619634, 0.396704, 0.574463, + 0.624015, 0.397168, 0.575129, + 0.628373, 0.397605, 0.575767, + 0.632707, 0.398012, 0.576370, + 0.637028, 0.398390, 0.576941, + 0.641325, 0.398748, 0.577490, + 0.645618, 0.399094, 0.578027, + 0.649908, 0.399435, 0.578559, + 0.654185, 0.399779, 0.579086, + 0.658464, 0.400135, 0.579609, + 0.662752, 0.400504, 0.580132, + 0.667036, 0.400885, 0.580663, + 0.671339, 0.401281, 0.581210, + 0.675658, 0.401708, 0.581780, + 0.679991, 0.402185, 0.582378, + 0.684352, 0.402720, 0.583011, + 0.688751, 0.403316, 0.583686, + 0.693170, 0.403970, 0.584410, + 0.697618, 0.404697, 0.585192, + 0.702109, 0.405530, 0.586030, + 0.706646, 0.406448, 0.586932, + 0.711216, 0.407470, 0.587915, + 0.715827, 0.408615, 0.588983, + 0.720469, 0.409892, 0.590145, + 0.725160, 0.411292, 0.591407, + 0.729876, 0.412855, 0.592772, + 0.734623, 0.414551, 0.594233, + 0.739395, 0.416419, 0.595813, + 0.744169, 0.418444, 0.597513, + 0.748950, 0.420648, 0.599331, + 0.753728, 0.423021, 0.601262, + 0.758484, 0.425579, 0.603322, + 0.763197, 0.428302, 0.605491, + 0.767861, 0.431199, 0.607790, + 0.772460, 0.434246, 0.610185, + 0.776977, 0.437474, 0.612689, + 0.781382, 0.440823, 0.615295, + 0.785670, 0.444338, 0.617993, + 0.789819, 0.447951, 0.620776, + 0.793821, 0.451698, 0.623624, + 0.797645, 0.455528, 0.626525, + 0.801296, 0.459445, 0.629480, + 0.804757, 0.463415, 0.632479, + 0.808016, 0.467455, 0.635498, + 0.811068, 0.471522, 0.638538, + 0.813906, 0.475607, 0.641580, + 0.816539, 0.479697, 0.644622, + 0.818947, 0.483773, 0.647656, + 0.821153, 0.487856, 0.650671, + 0.823150, 0.491898, 0.653657, + 0.824942, 0.495898, 0.656621, + 0.826547, 0.499867, 0.659541, + 0.827958, 0.503800, 0.662432, + 0.829202, 0.507681, 0.665273, + 0.830272, 0.511501, 0.668084, + 0.831190, 0.515276, 0.670847, + 0.831962, 0.519007, 0.673568, + 0.832595, 0.522672, 0.676250, + 0.833109, 0.526298, 0.678890, + 0.833513, 0.529868, 0.681492, + 0.833814, 0.533389, 0.684062, + 0.834021, 0.536885, 0.686602, + 0.834145, 0.540322, 0.689113, + 0.834196, 0.543731, 0.691583, + 0.834181, 0.547106, 0.694028, + 0.834110, 0.550453, 0.696461, + 0.833989, 0.553769, 0.698860, + 0.833825, 0.557074, 0.701248, + 0.833624, 0.560346, 0.703619, + 0.833394, 0.563609, 0.705976, + 0.833139, 0.566852, 0.708319, + 0.832866, 0.570077, 0.710651, + 0.832581, 0.573311, 0.712971, + 0.832288, 0.576535, 0.715295, + 0.831989, 0.579748, 0.717611, + 0.831689, 0.582952, 0.719918, + 0.831389, 0.586173, 0.722221, + 0.831094, 0.589380, 0.724534, + 0.830808, 0.592613, 0.726841, + 0.830534, 0.595831, 0.729152, + 0.830274, 0.599067, 0.731464, + 0.830031, 0.602304, 0.733784, + 0.829807, 0.605550, 0.736101, + 0.829605, 0.608823, 0.738432, + 0.829424, 0.612085, 0.740764, + 0.829268, 0.615369, 0.743111, + 0.829138, 0.618676, 0.745453, + 0.829036, 0.621976, 0.747814, + 0.828962, 0.625307, 0.750172, + 0.828918, 0.628653, 0.752543, + 0.828906, 0.632008, 0.754931, + 0.828926, 0.635378, 0.757317, + 0.828980, 0.638770, 0.759714, + 0.829067, 0.642178, 0.762124, + 0.829189, 0.645596, 0.764546, + 0.829346, 0.649044, 0.766973, + 0.829539, 0.652496, 0.769411, + 0.829768, 0.655978, 0.771864, + 0.830034, 0.659466, 0.774318, + 0.830337, 0.662981, 0.776794, + 0.830678, 0.666500, 0.779269, + 0.831057, 0.670054, 0.781761, + 0.831474, 0.673612, 0.784262, + 0.831926, 0.677196, 0.786770, + 0.832413, 0.680782, 0.789286, + 0.832939, 0.684396, 0.791810, + 0.833506, 0.688032, 0.794350, + 0.834109, 0.691677, 0.796891, + 0.834745, 0.695339, 0.799452, + 0.835417, 0.699014, 0.802010, + 0.836126, 0.702712, 0.804586, + 0.836872, 0.706423, 0.807167, + 0.837653, 0.710145, 0.809754, + 0.838462, 0.713881, 0.812350, + 0.839311, 0.717639, 0.814955, + 0.840192, 0.721397, 0.817566, + 0.841101, 0.725185, 0.820185, + 0.842045, 0.728973, 0.822816, + 0.843016, 0.732783, 0.825446, + 0.844017, 0.736597, 0.828087, + 0.845051, 0.740430, 0.830738, + 0.846109, 0.744271, 0.833390, + 0.847196, 0.748128, 0.836049, + 0.848312, 0.751992, 0.838712, + 0.849455, 0.755867, 0.841386, + 0.850615, 0.759748, 0.844060, + 0.851806, 0.763646, 0.846740, + 0.853017, 0.767550, 0.849432, + 0.854249, 0.771471, 0.852124, + 0.855508, 0.775390, 0.854819, + 0.856778, 0.779324, 0.857517, + 0.858072, 0.783265, 0.860226, + 0.859387, 0.787218, 0.862937, + 0.860717, 0.791171, 0.865647, + 0.862068, 0.795136, 0.868370, + 0.863431, 0.799112, 0.871086, + 0.864804, 0.803086, 0.873811, + 0.866199, 0.807076, 0.876538, + 0.867604, 0.811065, 0.879271, + 0.869025, 0.815063, 0.882009, + 0.870449, 0.819064, 0.884748, + 0.871896, 0.823078, 0.887487, + 0.873345, 0.827095, 0.890233, + 0.874806, 0.831118, 0.892981, + 0.876275, 0.835142, 0.895733, + 0.877755, 0.839173, 0.898483, + 0.879238, 0.843207, 0.901240, + 0.880732, 0.847246, 0.903997, + 0.882230, 0.851290, 0.906760, + 0.883737, 0.855346, 0.909522, + 0.885244, 0.859397, 0.912287, + 0.886759, 0.863458, 0.915053, + 0.888274, 0.867515, 0.917827, + 0.889793, 0.871582, 0.920592, + 0.891315, 0.875649, 0.923371, + 0.892840, 0.879718, 0.926144, + 0.894368, 0.883797, 0.928919, + 0.895893, 0.887870, 0.931699, + 0.897421, 0.891954, 0.934479, + 0.898946, 0.896037, 0.937266, + 0.900472, 0.900123, 0.940051, +}; + +const float ColorMap::m_imola[m_size] = +{ + 0.101441, 0.200110, 0.700194, + 0.103275, 0.203014, 0.698806, + 0.104955, 0.205896, 0.697423, + 0.106727, 0.208726, 0.696046, + 0.108299, 0.211567, 0.694659, + 0.109977, 0.214366, 0.693287, + 0.111521, 0.217161, 0.691913, + 0.113070, 0.219940, 0.690533, + 0.114496, 0.222699, 0.689172, + 0.116032, 0.225438, 0.687800, + 0.117443, 0.228185, 0.686435, + 0.118906, 0.230894, 0.685075, + 0.120279, 0.233585, 0.683714, + 0.121658, 0.236312, 0.682359, + 0.123038, 0.238984, 0.681006, + 0.124419, 0.241675, 0.679659, + 0.125813, 0.244322, 0.678320, + 0.127111, 0.246997, 0.676983, + 0.128474, 0.249641, 0.675640, + 0.129827, 0.252304, 0.674306, + 0.131137, 0.254955, 0.672979, + 0.132446, 0.257568, 0.671645, + 0.133757, 0.260201, 0.670328, + 0.135070, 0.262823, 0.669002, + 0.136327, 0.265434, 0.667684, + 0.137679, 0.268035, 0.666360, + 0.138935, 0.270668, 0.665046, + 0.140231, 0.273263, 0.663742, + 0.141543, 0.275872, 0.662432, + 0.142820, 0.278454, 0.661119, + 0.144107, 0.281028, 0.659816, + 0.145415, 0.283628, 0.658510, + 0.146648, 0.286201, 0.657214, + 0.147947, 0.288802, 0.655913, + 0.149229, 0.291388, 0.654606, + 0.150503, 0.293949, 0.653308, + 0.151764, 0.296539, 0.652018, + 0.153035, 0.299117, 0.650721, + 0.154318, 0.301673, 0.649430, + 0.155613, 0.304252, 0.648128, + 0.156880, 0.306831, 0.646841, + 0.158164, 0.309412, 0.645542, + 0.159416, 0.311952, 0.644249, + 0.160695, 0.314529, 0.642960, + 0.162002, 0.317106, 0.641664, + 0.163274, 0.319678, 0.640376, + 0.164543, 0.322244, 0.639079, + 0.165794, 0.324799, 0.637784, + 0.167115, 0.327367, 0.636491, + 0.168375, 0.329947, 0.635186, + 0.169688, 0.332517, 0.633890, + 0.170957, 0.335083, 0.632583, + 0.172248, 0.337633, 0.631277, + 0.173551, 0.340199, 0.629962, + 0.174877, 0.342765, 0.628648, + 0.176151, 0.345314, 0.627322, + 0.177520, 0.347876, 0.625992, + 0.178838, 0.350435, 0.624657, + 0.180160, 0.352981, 0.623315, + 0.181504, 0.355536, 0.621949, + 0.182872, 0.358073, 0.620594, + 0.184263, 0.360596, 0.619216, + 0.185685, 0.363136, 0.617815, + 0.187076, 0.365668, 0.616413, + 0.188513, 0.368188, 0.614982, + 0.189944, 0.370692, 0.613540, + 0.191409, 0.373200, 0.612083, + 0.192907, 0.375695, 0.610606, + 0.194447, 0.378180, 0.609108, + 0.195967, 0.380661, 0.607582, + 0.197527, 0.383122, 0.606021, + 0.199102, 0.385560, 0.604456, + 0.200717, 0.388001, 0.602850, + 0.202374, 0.390426, 0.601219, + 0.204080, 0.392837, 0.599568, + 0.205772, 0.395241, 0.597878, + 0.207501, 0.397623, 0.596167, + 0.209253, 0.399988, 0.594425, + 0.211048, 0.402341, 0.592662, + 0.212851, 0.404685, 0.590855, + 0.214697, 0.407021, 0.589024, + 0.216590, 0.409339, 0.587173, + 0.218472, 0.411640, 0.585301, + 0.220405, 0.413936, 0.583386, + 0.222326, 0.416219, 0.581455, + 0.224273, 0.418485, 0.579507, + 0.226292, 0.420749, 0.577521, + 0.228298, 0.423003, 0.575529, + 0.230292, 0.425262, 0.573509, + 0.232354, 0.427507, 0.571468, + 0.234418, 0.429738, 0.569412, + 0.236494, 0.431981, 0.567351, + 0.238559, 0.434195, 0.565258, + 0.240659, 0.436433, 0.563176, + 0.242782, 0.438655, 0.561063, + 0.244908, 0.440883, 0.558948, + 0.247068, 0.443126, 0.556836, + 0.249228, 0.445357, 0.554707, + 0.251414, 0.447595, 0.552578, + 0.253591, 0.449854, 0.550447, + 0.255782, 0.452118, 0.548322, + 0.257992, 0.454385, 0.546194, + 0.260238, 0.456657, 0.544064, + 0.262487, 0.458956, 0.541944, + 0.264746, 0.461264, 0.539834, + 0.267031, 0.463588, 0.537747, + 0.269365, 0.465931, 0.535653, + 0.271670, 0.468302, 0.533584, + 0.274045, 0.470701, 0.531541, + 0.276437, 0.473109, 0.529517, + 0.278846, 0.475569, 0.527507, + 0.281273, 0.478037, 0.525529, + 0.283755, 0.480545, 0.523580, + 0.286254, 0.483087, 0.521652, + 0.288817, 0.485665, 0.519766, + 0.291406, 0.488292, 0.517923, + 0.294012, 0.490955, 0.516096, + 0.296691, 0.493641, 0.514334, + 0.299399, 0.496376, 0.512590, + 0.302129, 0.499165, 0.510895, + 0.304936, 0.502001, 0.509249, + 0.307762, 0.504874, 0.507643, + 0.310668, 0.507797, 0.506069, + 0.313590, 0.510755, 0.504562, + 0.316562, 0.513775, 0.503071, + 0.319579, 0.516828, 0.501649, + 0.322646, 0.519921, 0.500247, + 0.325731, 0.523075, 0.498902, + 0.328896, 0.526263, 0.497597, + 0.332081, 0.529491, 0.496307, + 0.335316, 0.532757, 0.495080, + 0.338565, 0.536058, 0.493873, + 0.341872, 0.539398, 0.492693, + 0.345206, 0.542783, 0.491567, + 0.348575, 0.546195, 0.490446, + 0.351966, 0.549630, 0.489345, + 0.355415, 0.553094, 0.488287, + 0.358852, 0.556596, 0.487239, + 0.362345, 0.560123, 0.486202, + 0.365854, 0.563673, 0.485185, + 0.369381, 0.567240, 0.484185, + 0.372929, 0.570823, 0.483199, + 0.376515, 0.574431, 0.482229, + 0.380098, 0.578061, 0.481253, + 0.383710, 0.581709, 0.480296, + 0.387326, 0.585380, 0.479342, + 0.390980, 0.589046, 0.478387, + 0.394629, 0.592751, 0.477439, + 0.398306, 0.596451, 0.476496, + 0.401978, 0.600169, 0.475558, + 0.405692, 0.603906, 0.474615, + 0.409398, 0.607651, 0.473662, + 0.413126, 0.611403, 0.472723, + 0.416860, 0.615163, 0.471790, + 0.420597, 0.618953, 0.470843, + 0.424359, 0.622735, 0.469902, + 0.428133, 0.626534, 0.468954, + 0.431922, 0.630349, 0.467997, + 0.435709, 0.634172, 0.467055, + 0.439505, 0.637999, 0.466094, + 0.443323, 0.641840, 0.465140, + 0.447143, 0.645690, 0.464197, + 0.450983, 0.649560, 0.463220, + 0.454835, 0.653426, 0.462265, + 0.458687, 0.657318, 0.461305, + 0.462548, 0.661207, 0.460331, + 0.466433, 0.665110, 0.459373, + 0.470328, 0.669029, 0.458397, + 0.474226, 0.672957, 0.457422, + 0.478136, 0.676893, 0.456446, + 0.482064, 0.680831, 0.455471, + 0.485991, 0.684792, 0.454495, + 0.489937, 0.688766, 0.453502, + 0.493897, 0.692746, 0.452527, + 0.497874, 0.696730, 0.451532, + 0.501847, 0.700731, 0.450538, + 0.505825, 0.704741, 0.449545, + 0.509834, 0.708768, 0.448549, + 0.513854, 0.712792, 0.447538, + 0.517878, 0.716845, 0.446543, + 0.521903, 0.720893, 0.445532, + 0.525961, 0.724966, 0.444538, + 0.530021, 0.729039, 0.443523, + 0.534088, 0.733130, 0.442513, + 0.538185, 0.737229, 0.441493, + 0.542279, 0.741336, 0.440469, + 0.546397, 0.745461, 0.439450, + 0.550519, 0.749592, 0.438427, + 0.554662, 0.753743, 0.437409, + 0.558814, 0.757902, 0.436376, + 0.563001, 0.762067, 0.435350, + 0.567196, 0.766250, 0.434306, + 0.571406, 0.770443, 0.433272, + 0.575652, 0.774644, 0.432249, + 0.579917, 0.778862, 0.431207, + 0.584205, 0.783089, 0.430163, + 0.588525, 0.787331, 0.429123, + 0.592898, 0.791580, 0.428085, + 0.597297, 0.795841, 0.427048, + 0.601739, 0.800118, 0.425999, + 0.606235, 0.804401, 0.424971, + 0.610797, 0.808695, 0.423927, + 0.615410, 0.812993, 0.422894, + 0.620108, 0.817305, 0.421867, + 0.624872, 0.821627, 0.420848, + 0.629725, 0.825948, 0.419844, + 0.634681, 0.830279, 0.418834, + 0.639735, 0.834610, 0.417842, + 0.644895, 0.838937, 0.416866, + 0.650187, 0.843264, 0.415892, + 0.655603, 0.847585, 0.414941, + 0.661151, 0.851901, 0.414009, + 0.666842, 0.856200, 0.413104, + 0.672692, 0.860483, 0.412213, + 0.678680, 0.864742, 0.411341, + 0.684831, 0.868982, 0.410515, + 0.691137, 0.873182, 0.409702, + 0.697607, 0.877350, 0.408922, + 0.704236, 0.881483, 0.408178, + 0.711022, 0.885567, 0.407469, + 0.717955, 0.889601, 0.406798, + 0.725038, 0.893583, 0.406169, + 0.732260, 0.897513, 0.405572, + 0.739616, 0.901376, 0.405005, + 0.747090, 0.905184, 0.404485, + 0.754686, 0.908919, 0.404012, + 0.762378, 0.912595, 0.403574, + 0.770173, 0.916201, 0.403168, + 0.778055, 0.919746, 0.402796, + 0.786012, 0.923226, 0.402459, + 0.794034, 0.926637, 0.402157, + 0.802106, 0.929992, 0.401890, + 0.810230, 0.933282, 0.401656, + 0.818395, 0.936527, 0.401452, + 0.826599, 0.939711, 0.401272, + 0.834823, 0.942847, 0.401115, + 0.843067, 0.945933, 0.400978, + 0.851325, 0.948981, 0.400859, + 0.859601, 0.951989, 0.400755, + 0.867883, 0.954967, 0.400665, + 0.876163, 0.957911, 0.400588, + 0.884451, 0.960825, 0.400522, + 0.892736, 0.963712, 0.400466, + 0.901018, 0.966580, 0.400418, + 0.909297, 0.969428, 0.400377, + 0.917577, 0.972263, 0.400342, + 0.925845, 0.975074, 0.400311, + 0.934106, 0.977879, 0.400283, + 0.942371, 0.980672, 0.400258, + 0.950623, 0.983452, 0.400235, + 0.958870, 0.986227, 0.400212, + 0.967110, 0.988992, 0.400190, + 0.975342, 0.991749, 0.400168, + 0.983572, 0.994500, 0.400145, + 0.991797, 0.997245, 0.400120, + 1.000000, 0.999989, 0.400094, +}; + +const float ColorMap::m_tokyo[m_size] = +{ + 0.103874, 0.056805, 0.202430, + 0.109754, 0.059104, 0.205635, + 0.115663, 0.061046, 0.208843, + 0.121598, 0.063055, 0.212092, + 0.127575, 0.064935, 0.215312, + 0.133515, 0.066895, 0.218581, + 0.139386, 0.068939, 0.221840, + 0.145346, 0.070894, 0.225122, + 0.151193, 0.072938, 0.228453, + 0.157077, 0.074970, 0.231771, + 0.162979, 0.077038, 0.235110, + 0.168863, 0.079188, 0.238435, + 0.174746, 0.081435, 0.241823, + 0.180609, 0.083639, 0.245194, + 0.186520, 0.085843, 0.248633, + 0.192425, 0.088221, 0.252056, + 0.198334, 0.090586, 0.255503, + 0.204293, 0.092949, 0.258980, + 0.210211, 0.095479, 0.262461, + 0.216186, 0.098018, 0.265979, + 0.222124, 0.100564, 0.269515, + 0.228121, 0.103253, 0.273058, + 0.234096, 0.105965, 0.276636, + 0.240087, 0.108708, 0.280212, + 0.246101, 0.111594, 0.283828, + 0.252139, 0.114453, 0.287452, + 0.258153, 0.117464, 0.291117, + 0.264210, 0.120519, 0.294769, + 0.270259, 0.123649, 0.298454, + 0.276307, 0.126865, 0.302145, + 0.282356, 0.130211, 0.305884, + 0.288387, 0.133541, 0.309605, + 0.294424, 0.136973, 0.313331, + 0.300463, 0.140494, 0.317072, + 0.306506, 0.144083, 0.320832, + 0.312496, 0.147750, 0.324572, + 0.318504, 0.151468, 0.328340, + 0.324470, 0.155292, 0.332107, + 0.330424, 0.159176, 0.335860, + 0.336337, 0.163148, 0.339626, + 0.342232, 0.167175, 0.343369, + 0.348076, 0.171244, 0.347114, + 0.353869, 0.175399, 0.350843, + 0.359631, 0.179615, 0.354554, + 0.365331, 0.183889, 0.358244, + 0.370972, 0.188248, 0.361925, + 0.376573, 0.192607, 0.365573, + 0.382077, 0.197055, 0.369202, + 0.387525, 0.201501, 0.372794, + 0.392902, 0.206057, 0.376376, + 0.398204, 0.210595, 0.379900, + 0.403416, 0.215176, 0.383404, + 0.408536, 0.219817, 0.386848, + 0.413574, 0.224434, 0.390266, + 0.418507, 0.229127, 0.393641, + 0.423351, 0.233801, 0.396964, + 0.428105, 0.238504, 0.400253, + 0.432750, 0.243215, 0.403484, + 0.437290, 0.247962, 0.406658, + 0.441721, 0.252674, 0.409786, + 0.446034, 0.257394, 0.412860, + 0.450256, 0.262114, 0.415866, + 0.454365, 0.266821, 0.418823, + 0.458352, 0.271520, 0.421720, + 0.462234, 0.276236, 0.424573, + 0.466002, 0.280883, 0.427366, + 0.469677, 0.285545, 0.430087, + 0.473210, 0.290189, 0.432760, + 0.476659, 0.294809, 0.435375, + 0.479995, 0.299421, 0.437928, + 0.483211, 0.303979, 0.440413, + 0.486332, 0.308535, 0.442859, + 0.489342, 0.313046, 0.445236, + 0.492259, 0.317537, 0.447556, + 0.495074, 0.321996, 0.449837, + 0.497794, 0.326417, 0.452061, + 0.500399, 0.330819, 0.454223, + 0.502919, 0.335210, 0.456331, + 0.505351, 0.339545, 0.458395, + 0.507706, 0.343834, 0.460403, + 0.509958, 0.348119, 0.462373, + 0.512132, 0.352358, 0.464307, + 0.514235, 0.356582, 0.466168, + 0.516239, 0.360748, 0.468000, + 0.518191, 0.364909, 0.469800, + 0.520050, 0.369042, 0.471546, + 0.521848, 0.373130, 0.473241, + 0.523591, 0.377205, 0.474929, + 0.525257, 0.381250, 0.476556, + 0.526870, 0.385250, 0.478154, + 0.528414, 0.389248, 0.479726, + 0.529905, 0.393206, 0.481251, + 0.531341, 0.397143, 0.482757, + 0.532731, 0.401075, 0.484226, + 0.534059, 0.404954, 0.485673, + 0.535350, 0.408831, 0.487099, + 0.536606, 0.412692, 0.488492, + 0.537810, 0.416517, 0.489853, + 0.538970, 0.420326, 0.491215, + 0.540090, 0.424120, 0.492521, + 0.541178, 0.427906, 0.493829, + 0.542238, 0.431673, 0.495114, + 0.543262, 0.435417, 0.496367, + 0.544252, 0.439136, 0.497628, + 0.545217, 0.442864, 0.498851, + 0.546158, 0.446565, 0.500055, + 0.547062, 0.450259, 0.501264, + 0.547950, 0.453939, 0.502441, + 0.548817, 0.457607, 0.503609, + 0.549655, 0.461272, 0.504773, + 0.550476, 0.464923, 0.505906, + 0.551279, 0.468564, 0.507047, + 0.552068, 0.472205, 0.508179, + 0.552830, 0.475831, 0.509286, + 0.553581, 0.479451, 0.510385, + 0.554326, 0.483058, 0.511479, + 0.555048, 0.486669, 0.512567, + 0.555755, 0.490274, 0.513652, + 0.556462, 0.493871, 0.514722, + 0.557162, 0.497470, 0.515776, + 0.557835, 0.501057, 0.516843, + 0.558500, 0.504647, 0.517897, + 0.559168, 0.508232, 0.518945, + 0.559829, 0.511802, 0.519974, + 0.560474, 0.515380, 0.521015, + 0.561112, 0.518967, 0.522039, + 0.561754, 0.522535, 0.523077, + 0.562388, 0.526115, 0.524096, + 0.563011, 0.529688, 0.525114, + 0.563633, 0.533255, 0.526133, + 0.564247, 0.536837, 0.527144, + 0.564853, 0.540403, 0.528148, + 0.565463, 0.543976, 0.529160, + 0.566077, 0.547547, 0.530157, + 0.566689, 0.551124, 0.531156, + 0.567294, 0.554700, 0.532156, + 0.567895, 0.558269, 0.533147, + 0.568496, 0.561859, 0.534136, + 0.569099, 0.565427, 0.535129, + 0.569703, 0.569012, 0.536120, + 0.570308, 0.572605, 0.537115, + 0.570916, 0.576196, 0.538101, + 0.571526, 0.579783, 0.539079, + 0.572140, 0.583367, 0.540059, + 0.572757, 0.586964, 0.541042, + 0.573371, 0.590563, 0.542024, + 0.573986, 0.594170, 0.543012, + 0.574608, 0.597777, 0.543990, + 0.575244, 0.601388, 0.544972, + 0.575888, 0.605007, 0.545958, + 0.576531, 0.608637, 0.546936, + 0.577173, 0.612254, 0.547919, + 0.577825, 0.615890, 0.548907, + 0.578494, 0.619537, 0.549888, + 0.579177, 0.623182, 0.550875, + 0.579866, 0.626832, 0.551868, + 0.580561, 0.630500, 0.552855, + 0.581270, 0.634172, 0.553852, + 0.581995, 0.637846, 0.554854, + 0.582737, 0.641532, 0.555851, + 0.583497, 0.645232, 0.556875, + 0.584276, 0.648949, 0.557885, + 0.585079, 0.652666, 0.558907, + 0.585899, 0.656410, 0.559947, + 0.586736, 0.660155, 0.560982, + 0.587602, 0.663922, 0.562043, + 0.588497, 0.667703, 0.563104, + 0.589424, 0.671494, 0.564178, + 0.590386, 0.675311, 0.565256, + 0.591385, 0.679143, 0.566370, + 0.592425, 0.683002, 0.567492, + 0.593494, 0.686879, 0.568627, + 0.594610, 0.690776, 0.569787, + 0.595776, 0.694703, 0.570973, + 0.596998, 0.698658, 0.572187, + 0.598263, 0.702644, 0.573423, + 0.599602, 0.706660, 0.574680, + 0.600992, 0.710704, 0.575991, + 0.602456, 0.714779, 0.577314, + 0.603999, 0.718897, 0.578684, + 0.605602, 0.723049, 0.580099, + 0.607310, 0.727246, 0.581547, + 0.609095, 0.731483, 0.583047, + 0.610971, 0.735764, 0.584603, + 0.612942, 0.740100, 0.586210, + 0.615027, 0.744473, 0.587867, + 0.617229, 0.748900, 0.589592, + 0.619550, 0.753381, 0.591390, + 0.621980, 0.757921, 0.593253, + 0.624560, 0.762508, 0.595185, + 0.627269, 0.767157, 0.597203, + 0.630128, 0.771868, 0.599299, + 0.633135, 0.776637, 0.601476, + 0.636305, 0.781462, 0.603756, + 0.639633, 0.786350, 0.606115, + 0.643130, 0.791291, 0.608593, + 0.646810, 0.796294, 0.611159, + 0.650666, 0.801356, 0.613824, + 0.654704, 0.806477, 0.616616, + 0.658936, 0.811644, 0.619512, + 0.663368, 0.816862, 0.622512, + 0.667984, 0.822127, 0.625636, + 0.672796, 0.827426, 0.628877, + 0.677803, 0.832762, 0.632232, + 0.683000, 0.838129, 0.635702, + 0.688394, 0.843511, 0.639291, + 0.693965, 0.848913, 0.642991, + 0.699729, 0.854314, 0.646806, + 0.705669, 0.859715, 0.650725, + 0.711776, 0.865095, 0.654744, + 0.718039, 0.870453, 0.658868, + 0.724452, 0.875781, 0.663094, + 0.731000, 0.881058, 0.667395, + 0.737676, 0.886282, 0.671776, + 0.744467, 0.891433, 0.676236, + 0.751359, 0.896511, 0.680752, + 0.758327, 0.901496, 0.685338, + 0.765368, 0.906386, 0.689964, + 0.772456, 0.911166, 0.694625, + 0.779589, 0.915826, 0.699323, + 0.786752, 0.920367, 0.704045, + 0.793919, 0.924774, 0.708782, + 0.801076, 0.929045, 0.713515, + 0.808223, 0.933171, 0.718248, + 0.815338, 0.937156, 0.722969, + 0.822415, 0.940982, 0.727678, + 0.829438, 0.944660, 0.732366, + 0.836396, 0.948178, 0.737015, + 0.843285, 0.951544, 0.741630, + 0.850102, 0.954757, 0.746210, + 0.856831, 0.957816, 0.750750, + 0.863479, 0.960723, 0.755237, + 0.870023, 0.963479, 0.759670, + 0.876480, 0.966091, 0.764060, + 0.882838, 0.968566, 0.768392, + 0.889102, 0.970900, 0.772676, + 0.895264, 0.973107, 0.776909, + 0.901325, 0.975180, 0.781079, + 0.907294, 0.977138, 0.785201, + 0.913165, 0.978977, 0.789269, + 0.918939, 0.980708, 0.793287, + 0.924629, 0.982335, 0.797248, + 0.930235, 0.983860, 0.801169, + 0.935755, 0.985295, 0.805042, + 0.941192, 0.986643, 0.808876, + 0.946558, 0.987907, 0.812660, + 0.951853, 0.989101, 0.816415, + 0.957082, 0.990222, 0.820124, + 0.962248, 0.991280, 0.823806, + 0.967363, 0.992281, 0.827457, + 0.972422, 0.993229, 0.831083, + 0.977430, 0.994125, 0.834679, + 0.982400, 0.994978, 0.838253, + 0.987328, 0.995795, 0.841810, + 0.992221, 0.996578, 0.845345, + 0.997080, 0.997331, 0.848870, +}; + +const float ColorMap::m_lapaz[m_size] = +{ + 0.103516, 0.047787, 0.393530, + 0.104891, 0.053521, 0.396743, + 0.106384, 0.059148, 0.399962, + 0.107720, 0.064483, 0.403185, + 0.109104, 0.069760, 0.406401, + 0.110449, 0.074827, 0.409614, + 0.111749, 0.079829, 0.412831, + 0.113050, 0.084796, 0.416030, + 0.114243, 0.089643, 0.419241, + 0.115510, 0.094446, 0.422431, + 0.116730, 0.099126, 0.425637, + 0.117933, 0.103813, 0.428830, + 0.119106, 0.108384, 0.432027, + 0.120235, 0.113025, 0.435205, + 0.121361, 0.117507, 0.438371, + 0.122480, 0.121983, 0.441541, + 0.123592, 0.126483, 0.444704, + 0.124696, 0.130936, 0.447839, + 0.125807, 0.135324, 0.450989, + 0.126828, 0.139672, 0.454121, + 0.127907, 0.144071, 0.457236, + 0.128916, 0.148382, 0.460341, + 0.129993, 0.152699, 0.463441, + 0.130992, 0.157010, 0.466534, + 0.131988, 0.161319, 0.469615, + 0.132983, 0.165534, 0.472664, + 0.133979, 0.169826, 0.475718, + 0.134969, 0.174047, 0.478744, + 0.135893, 0.178292, 0.481764, + 0.136892, 0.182461, 0.484757, + 0.137889, 0.186673, 0.487756, + 0.138819, 0.190854, 0.490726, + 0.139754, 0.195043, 0.493668, + 0.140766, 0.199185, 0.496597, + 0.141712, 0.203360, 0.499513, + 0.142665, 0.207508, 0.502416, + 0.143626, 0.211653, 0.505290, + 0.144593, 0.215776, 0.508162, + 0.145575, 0.219893, 0.510990, + 0.146497, 0.223982, 0.513817, + 0.147494, 0.228107, 0.516608, + 0.148441, 0.232196, 0.519386, + 0.149461, 0.236283, 0.522132, + 0.150439, 0.240331, 0.524870, + 0.151423, 0.244401, 0.527578, + 0.152426, 0.248494, 0.530265, + 0.153444, 0.252535, 0.532927, + 0.154482, 0.256572, 0.535558, + 0.155540, 0.260614, 0.538183, + 0.156604, 0.264639, 0.540764, + 0.157646, 0.268677, 0.543330, + 0.158755, 0.272676, 0.545871, + 0.159813, 0.276698, 0.548383, + 0.160964, 0.280675, 0.550863, + 0.162111, 0.284675, 0.553315, + 0.163261, 0.288671, 0.555744, + 0.164432, 0.292642, 0.558150, + 0.165586, 0.296623, 0.560533, + 0.166843, 0.300573, 0.562887, + 0.168054, 0.304534, 0.565194, + 0.169354, 0.308487, 0.567495, + 0.170614, 0.312410, 0.569751, + 0.171920, 0.316363, 0.571989, + 0.173261, 0.320291, 0.574188, + 0.174648, 0.324189, 0.576375, + 0.176006, 0.328102, 0.578508, + 0.177479, 0.332008, 0.580625, + 0.178923, 0.335895, 0.582704, + 0.180394, 0.339788, 0.584762, + 0.181911, 0.343652, 0.586779, + 0.183476, 0.347530, 0.588765, + 0.185095, 0.351381, 0.590726, + 0.186704, 0.355244, 0.592662, + 0.188390, 0.359065, 0.594548, + 0.190072, 0.362901, 0.596411, + 0.191813, 0.366733, 0.598235, + 0.193613, 0.370536, 0.600036, + 0.195424, 0.374356, 0.601796, + 0.197303, 0.378144, 0.603531, + 0.199191, 0.381929, 0.605220, + 0.201145, 0.385703, 0.606888, + 0.203166, 0.389479, 0.608518, + 0.205211, 0.393235, 0.610101, + 0.207319, 0.396980, 0.611662, + 0.209453, 0.400739, 0.613179, + 0.211665, 0.404450, 0.614666, + 0.213901, 0.408173, 0.616123, + 0.216213, 0.411881, 0.617536, + 0.218546, 0.415577, 0.618923, + 0.220963, 0.419268, 0.620264, + 0.223407, 0.422932, 0.621560, + 0.225926, 0.426601, 0.622832, + 0.228495, 0.430255, 0.624066, + 0.231110, 0.433884, 0.625253, + 0.233772, 0.437528, 0.626406, + 0.236537, 0.441129, 0.627522, + 0.239324, 0.444741, 0.628602, + 0.242179, 0.448322, 0.629632, + 0.245082, 0.451902, 0.630636, + 0.248092, 0.455457, 0.631588, + 0.251124, 0.459003, 0.632503, + 0.254223, 0.462523, 0.633378, + 0.257378, 0.466038, 0.634215, + 0.260601, 0.469550, 0.635000, + 0.263901, 0.473016, 0.635749, + 0.267221, 0.476488, 0.636460, + 0.270662, 0.479939, 0.637121, + 0.274124, 0.483358, 0.637738, + 0.277664, 0.486772, 0.638314, + 0.281249, 0.490161, 0.638845, + 0.284909, 0.493528, 0.639333, + 0.288645, 0.496877, 0.639778, + 0.292426, 0.500203, 0.640175, + 0.296276, 0.503508, 0.640524, + 0.300180, 0.506793, 0.640826, + 0.304147, 0.510052, 0.641084, + 0.308175, 0.513289, 0.641296, + 0.312252, 0.516494, 0.641462, + 0.316418, 0.519677, 0.641582, + 0.320626, 0.522837, 0.641654, + 0.324864, 0.525966, 0.641679, + 0.329198, 0.529070, 0.641656, + 0.333561, 0.532136, 0.641585, + 0.337970, 0.535170, 0.641466, + 0.342455, 0.538188, 0.641300, + 0.346979, 0.541154, 0.641087, + 0.351541, 0.544103, 0.640829, + 0.356170, 0.547014, 0.640524, + 0.360819, 0.549891, 0.640171, + 0.365541, 0.552731, 0.639769, + 0.370290, 0.555531, 0.639318, + 0.375088, 0.558300, 0.638823, + 0.379928, 0.561035, 0.638283, + 0.384797, 0.563739, 0.637697, + 0.389709, 0.566390, 0.637069, + 0.394658, 0.569000, 0.636397, + 0.399630, 0.571580, 0.635673, + 0.404644, 0.574114, 0.634914, + 0.409695, 0.576622, 0.634116, + 0.414760, 0.579073, 0.633268, + 0.419859, 0.581482, 0.632384, + 0.424982, 0.583855, 0.631463, + 0.430122, 0.586191, 0.630505, + 0.435295, 0.588472, 0.629501, + 0.440469, 0.590724, 0.628476, + 0.445670, 0.592938, 0.627405, + 0.450901, 0.595097, 0.626305, + 0.456135, 0.597226, 0.625177, + 0.461391, 0.599309, 0.624023, + 0.466657, 0.601347, 0.622832, + 0.471940, 0.603355, 0.621614, + 0.477222, 0.605313, 0.620388, + 0.482525, 0.607248, 0.619130, + 0.487841, 0.609136, 0.617842, + 0.493147, 0.610986, 0.616551, + 0.498489, 0.612795, 0.615232, + 0.503819, 0.614576, 0.613904, + 0.509165, 0.616333, 0.612569, + 0.514519, 0.618048, 0.611232, + 0.519869, 0.619742, 0.609876, + 0.525243, 0.621396, 0.608531, + 0.530620, 0.623035, 0.607177, + 0.536002, 0.624643, 0.605815, + 0.541393, 0.626229, 0.604481, + 0.546807, 0.627800, 0.603143, + 0.552224, 0.629346, 0.601817, + 0.557650, 0.630889, 0.600512, + 0.563095, 0.632409, 0.599231, + 0.568544, 0.633927, 0.597963, + 0.574017, 0.635428, 0.596735, + 0.579518, 0.636939, 0.595529, + 0.585029, 0.638440, 0.594365, + 0.590556, 0.639951, 0.593245, + 0.596118, 0.641459, 0.592171, + 0.601704, 0.642989, 0.591137, + 0.607328, 0.644526, 0.590163, + 0.612966, 0.646088, 0.589253, + 0.618659, 0.647671, 0.588412, + 0.624377, 0.649288, 0.587646, + 0.630138, 0.650926, 0.586960, + 0.635943, 0.652601, 0.586361, + 0.641792, 0.654323, 0.585856, + 0.647693, 0.656096, 0.585447, + 0.653648, 0.657914, 0.585142, + 0.659659, 0.659784, 0.584952, + 0.665727, 0.661724, 0.584884, + 0.671854, 0.663726, 0.584946, + 0.678048, 0.665796, 0.585146, + 0.684298, 0.667952, 0.585491, + 0.690623, 0.670187, 0.585987, + 0.697019, 0.672507, 0.586641, + 0.703483, 0.674915, 0.587468, + 0.710016, 0.677433, 0.588473, + 0.716622, 0.680035, 0.589666, + 0.723287, 0.682758, 0.591058, + 0.730033, 0.685590, 0.592654, + 0.736837, 0.688533, 0.594444, + 0.743708, 0.691588, 0.596458, + 0.750639, 0.694763, 0.598689, + 0.757620, 0.698063, 0.601149, + 0.764653, 0.701485, 0.603849, + 0.771730, 0.705033, 0.606775, + 0.778837, 0.708706, 0.609936, + 0.785977, 0.712489, 0.613340, + 0.793132, 0.716409, 0.616996, + 0.800292, 0.720435, 0.620887, + 0.807448, 0.724584, 0.625012, + 0.814587, 0.728840, 0.629376, + 0.821705, 0.733207, 0.633983, + 0.828779, 0.737670, 0.638805, + 0.835797, 0.742236, 0.643850, + 0.842753, 0.746885, 0.649121, + 0.849627, 0.751621, 0.654582, + 0.856405, 0.756422, 0.660250, + 0.863083, 0.761293, 0.666100, + 0.869635, 0.766222, 0.672129, + 0.876063, 0.771202, 0.678318, + 0.882346, 0.776217, 0.684656, + 0.888482, 0.781266, 0.691134, + 0.894454, 0.786340, 0.697742, + 0.900253, 0.791422, 0.704464, + 0.905879, 0.796513, 0.711290, + 0.911317, 0.801607, 0.718196, + 0.916568, 0.806696, 0.725189, + 0.921627, 0.811765, 0.732241, + 0.926488, 0.816815, 0.739352, + 0.931155, 0.821841, 0.746497, + 0.935622, 0.826833, 0.753683, + 0.939894, 0.831794, 0.760897, + 0.943965, 0.836710, 0.768122, + 0.947844, 0.841589, 0.775364, + 0.951530, 0.846418, 0.782609, + 0.955031, 0.851205, 0.789852, + 0.958348, 0.855950, 0.797083, + 0.961481, 0.860641, 0.804310, + 0.964444, 0.865281, 0.811517, + 0.967245, 0.869874, 0.818701, + 0.969877, 0.874421, 0.825872, + 0.972359, 0.878918, 0.833018, + 0.974685, 0.883371, 0.840145, + 0.976874, 0.887774, 0.847236, + 0.978925, 0.892139, 0.854310, + 0.980850, 0.896458, 0.861358, + 0.982652, 0.900735, 0.868388, + 0.984338, 0.904979, 0.875383, + 0.985912, 0.909180, 0.882357, + 0.987387, 0.913355, 0.889315, + 0.988763, 0.917498, 0.896251, + 0.990052, 0.921606, 0.903168, + 0.991256, 0.925692, 0.910067, + 0.992385, 0.929753, 0.916951, + 0.993443, 0.933791, 0.923821, + 0.994431, 0.937817, 0.930680, + 0.995360, 0.941823, 0.937528, + 0.996235, 0.945814, 0.944370, + 0.997061, 0.949794, 0.951206, +}; + +const float ColorMap::m_buda[m_size] = +{ + 0.700151, 0.002745, 0.700612, + 0.700191, 0.010833, 0.697186, + 0.700226, 0.019196, 0.693784, + 0.700255, 0.027497, 0.690410, + 0.700279, 0.036129, 0.687067, + 0.700299, 0.044535, 0.683750, + 0.700315, 0.052201, 0.680467, + 0.700328, 0.059479, 0.677234, + 0.700339, 0.066138, 0.674024, + 0.700347, 0.072500, 0.670865, + 0.700355, 0.078557, 0.667747, + 0.700362, 0.084489, 0.664665, + 0.700369, 0.090118, 0.661647, + 0.700378, 0.095602, 0.658662, + 0.700389, 0.100919, 0.655740, + 0.700403, 0.106180, 0.652854, + 0.700422, 0.111272, 0.650035, + 0.700447, 0.116281, 0.647260, + 0.700479, 0.121141, 0.644532, + 0.700521, 0.126029, 0.641864, + 0.700575, 0.130794, 0.639246, + 0.700642, 0.135455, 0.636684, + 0.700726, 0.140079, 0.634169, + 0.700829, 0.144685, 0.631701, + 0.700953, 0.149193, 0.629284, + 0.701102, 0.153641, 0.626923, + 0.701279, 0.158084, 0.624615, + 0.701486, 0.162466, 0.622349, + 0.701729, 0.166801, 0.620147, + 0.702012, 0.171076, 0.617979, + 0.702337, 0.175336, 0.615867, + 0.702707, 0.179555, 0.613800, + 0.703122, 0.183741, 0.611790, + 0.703582, 0.187936, 0.609823, + 0.704093, 0.192033, 0.607911, + 0.704658, 0.196154, 0.606029, + 0.705275, 0.200200, 0.604213, + 0.705946, 0.204298, 0.602424, + 0.706671, 0.208310, 0.600685, + 0.707439, 0.212332, 0.598992, + 0.708265, 0.216333, 0.597337, + 0.709140, 0.220302, 0.595714, + 0.710053, 0.224219, 0.594134, + 0.711015, 0.228186, 0.592595, + 0.712011, 0.232099, 0.591073, + 0.713044, 0.235997, 0.589585, + 0.714113, 0.239864, 0.588131, + 0.715214, 0.243717, 0.586706, + 0.716339, 0.247586, 0.585309, + 0.717485, 0.251421, 0.583920, + 0.718642, 0.255233, 0.582557, + 0.719825, 0.259028, 0.581216, + 0.721012, 0.262798, 0.579897, + 0.722212, 0.266557, 0.578580, + 0.723423, 0.270315, 0.577284, + 0.724641, 0.274034, 0.576009, + 0.725860, 0.277747, 0.574723, + 0.727075, 0.281433, 0.573468, + 0.728294, 0.285107, 0.572216, + 0.729516, 0.288786, 0.570967, + 0.730731, 0.292428, 0.569729, + 0.731948, 0.296063, 0.568502, + 0.733159, 0.299689, 0.567287, + 0.734368, 0.303280, 0.566067, + 0.735568, 0.306883, 0.564853, + 0.736769, 0.310471, 0.563661, + 0.737963, 0.314024, 0.562464, + 0.739164, 0.317574, 0.561261, + 0.740347, 0.321115, 0.560077, + 0.741527, 0.324632, 0.558885, + 0.742714, 0.328153, 0.557711, + 0.743886, 0.331654, 0.556532, + 0.745056, 0.335166, 0.555357, + 0.746225, 0.338633, 0.554196, + 0.747393, 0.342111, 0.553026, + 0.748552, 0.345565, 0.551874, + 0.749704, 0.349024, 0.550714, + 0.750868, 0.352465, 0.549561, + 0.752015, 0.355908, 0.548416, + 0.753158, 0.359324, 0.547260, + 0.754311, 0.362743, 0.546124, + 0.755451, 0.366162, 0.544977, + 0.756589, 0.369556, 0.543837, + 0.757726, 0.372949, 0.542705, + 0.758860, 0.376350, 0.541561, + 0.759990, 0.379722, 0.540437, + 0.761122, 0.383103, 0.539305, + 0.762246, 0.386459, 0.538189, + 0.763371, 0.389823, 0.537065, + 0.764497, 0.393183, 0.535933, + 0.765621, 0.396533, 0.534816, + 0.766735, 0.399878, 0.533691, + 0.767850, 0.403228, 0.532586, + 0.768965, 0.406563, 0.531468, + 0.770080, 0.409898, 0.530357, + 0.771197, 0.413225, 0.529252, + 0.772302, 0.416545, 0.528136, + 0.773408, 0.419864, 0.527036, + 0.774514, 0.423171, 0.525928, + 0.775620, 0.426487, 0.524822, + 0.776727, 0.429800, 0.523723, + 0.777826, 0.433105, 0.522621, + 0.778921, 0.436413, 0.521521, + 0.780016, 0.439713, 0.520427, + 0.781110, 0.443016, 0.519342, + 0.782202, 0.446308, 0.518256, + 0.783285, 0.449611, 0.517171, + 0.784373, 0.452911, 0.516077, + 0.785450, 0.456202, 0.515007, + 0.786526, 0.459505, 0.513939, + 0.787590, 0.462792, 0.512865, + 0.788657, 0.466096, 0.511800, + 0.789713, 0.469411, 0.510744, + 0.790763, 0.472703, 0.509696, + 0.791807, 0.476015, 0.508661, + 0.792845, 0.479325, 0.507622, + 0.793877, 0.482635, 0.506589, + 0.794892, 0.485949, 0.505565, + 0.795900, 0.489268, 0.504572, + 0.796901, 0.492594, 0.503560, + 0.797894, 0.495924, 0.502571, + 0.798879, 0.499261, 0.501599, + 0.799853, 0.502601, 0.500622, + 0.800811, 0.505942, 0.499654, + 0.801763, 0.509301, 0.498715, + 0.802706, 0.512652, 0.497778, + 0.803641, 0.516005, 0.496832, + 0.804568, 0.519378, 0.495907, + 0.805481, 0.522745, 0.495000, + 0.806395, 0.526120, 0.494092, + 0.807296, 0.529496, 0.493183, + 0.808190, 0.532873, 0.492294, + 0.809081, 0.536254, 0.491419, + 0.809960, 0.539633, 0.490532, + 0.810839, 0.543029, 0.489643, + 0.811716, 0.546423, 0.488777, + 0.812583, 0.549811, 0.487916, + 0.813449, 0.553199, 0.487046, + 0.814314, 0.556601, 0.486178, + 0.815178, 0.560003, 0.485315, + 0.816042, 0.563405, 0.484456, + 0.816898, 0.566806, 0.483600, + 0.817752, 0.570200, 0.482748, + 0.818608, 0.573611, 0.481898, + 0.819464, 0.577019, 0.481035, + 0.820320, 0.580426, 0.480189, + 0.821177, 0.583832, 0.479337, + 0.822036, 0.587241, 0.478479, + 0.822888, 0.590654, 0.477626, + 0.823741, 0.594071, 0.476774, + 0.824596, 0.597489, 0.475924, + 0.825452, 0.600903, 0.475075, + 0.826310, 0.604330, 0.474213, + 0.827167, 0.607752, 0.473350, + 0.828020, 0.611173, 0.472504, + 0.828883, 0.614590, 0.471655, + 0.829740, 0.618022, 0.470796, + 0.830598, 0.621451, 0.469943, + 0.831459, 0.624886, 0.469087, + 0.832315, 0.628325, 0.468218, + 0.833174, 0.631759, 0.467368, + 0.834038, 0.635197, 0.466506, + 0.834897, 0.638641, 0.465638, + 0.835759, 0.642088, 0.464790, + 0.836622, 0.645532, 0.463931, + 0.837488, 0.648990, 0.463055, + 0.838348, 0.652439, 0.462201, + 0.839214, 0.655902, 0.461342, + 0.840083, 0.659357, 0.460469, + 0.840947, 0.662826, 0.459614, + 0.841815, 0.666286, 0.458749, + 0.842684, 0.669764, 0.457878, + 0.843548, 0.673236, 0.457012, + 0.844421, 0.676714, 0.456145, + 0.845291, 0.680187, 0.455282, + 0.846160, 0.683675, 0.454415, + 0.847033, 0.687166, 0.453536, + 0.847907, 0.690654, 0.452675, + 0.848784, 0.694149, 0.451800, + 0.849662, 0.697652, 0.450924, + 0.850534, 0.701158, 0.450049, + 0.851410, 0.704667, 0.449178, + 0.852296, 0.708180, 0.448296, + 0.853170, 0.711699, 0.447413, + 0.854049, 0.715220, 0.446541, + 0.854936, 0.718742, 0.445655, + 0.855818, 0.722269, 0.444788, + 0.856699, 0.725811, 0.443905, + 0.857584, 0.729347, 0.443021, + 0.858470, 0.732893, 0.442142, + 0.859359, 0.736436, 0.441247, + 0.860250, 0.739997, 0.440361, + 0.861136, 0.743552, 0.439474, + 0.862032, 0.747115, 0.438584, + 0.862924, 0.750684, 0.437706, + 0.863818, 0.754257, 0.436810, + 0.864708, 0.757833, 0.435919, + 0.865604, 0.761413, 0.435028, + 0.866502, 0.765006, 0.434120, + 0.867400, 0.768593, 0.433228, + 0.868307, 0.772195, 0.432343, + 0.869206, 0.775798, 0.431443, + 0.870103, 0.779408, 0.430538, + 0.871012, 0.783023, 0.429635, + 0.871923, 0.786651, 0.428733, + 0.872831, 0.790273, 0.427834, + 0.873742, 0.793911, 0.426929, + 0.874658, 0.797545, 0.426015, + 0.875582, 0.801192, 0.425119, + 0.876504, 0.804847, 0.424205, + 0.877438, 0.808510, 0.423294, + 0.878381, 0.812177, 0.422386, + 0.879330, 0.815859, 0.421480, + 0.880295, 0.819541, 0.420578, + 0.881278, 0.823243, 0.419683, + 0.882276, 0.826956, 0.418775, + 0.883302, 0.830680, 0.417880, + 0.884356, 0.834420, 0.416995, + 0.885442, 0.838174, 0.416101, + 0.886573, 0.841950, 0.415222, + 0.887744, 0.845741, 0.414352, + 0.888981, 0.849563, 0.413497, + 0.890275, 0.853401, 0.412658, + 0.891647, 0.857274, 0.411817, + 0.893098, 0.861176, 0.411008, + 0.894649, 0.865109, 0.410221, + 0.896296, 0.869084, 0.409443, + 0.898055, 0.873088, 0.408695, + 0.899941, 0.877132, 0.407976, + 0.901952, 0.881224, 0.407286, + 0.904103, 0.885355, 0.406631, + 0.906404, 0.889529, 0.406013, + 0.908850, 0.893747, 0.405420, + 0.911455, 0.898012, 0.404859, + 0.914220, 0.902322, 0.404346, + 0.917142, 0.906675, 0.403876, + 0.920217, 0.911070, 0.403439, + 0.923457, 0.915502, 0.403034, + 0.926841, 0.919982, 0.402665, + 0.930380, 0.924494, 0.402333, + 0.934052, 0.929044, 0.402037, + 0.937866, 0.933626, 0.401778, + 0.941799, 0.938244, 0.401552, + 0.945848, 0.942888, 0.401355, + 0.950004, 0.947555, 0.401183, + 0.954258, 0.952243, 0.401033, + 0.958599, 0.956957, 0.400902, + 0.963014, 0.961688, 0.400787, + 0.967502, 0.966439, 0.400686, + 0.972046, 0.971205, 0.400598, + 0.976641, 0.975983, 0.400519, + 0.981286, 0.980779, 0.400448, + 0.985967, 0.985584, 0.400383, + 0.990680, 0.990397, 0.400321, + 0.995418, 0.995218, 0.400262, + 1.000000, 1.000000, 0.400203, +}; + +const float ColorMap::m_devon[m_size] = +{ + 0.171032, 0.100402, 0.299782, + 0.170868, 0.104144, 0.303372, + 0.170677, 0.107856, 0.306989, + 0.170463, 0.111594, 0.310614, + 0.170227, 0.115240, 0.314216, + 0.169972, 0.118939, 0.317840, + 0.169698, 0.122543, 0.321462, + 0.169404, 0.126240, 0.325088, + 0.169084, 0.129892, 0.328738, + 0.168737, 0.133501, 0.332383, + 0.168373, 0.137129, 0.336012, + 0.168007, 0.140767, 0.339673, + 0.167646, 0.144378, 0.343319, + 0.167278, 0.147990, 0.346977, + 0.166888, 0.151600, 0.350639, + 0.166476, 0.155235, 0.354294, + 0.166051, 0.158875, 0.357969, + 0.165623, 0.162508, 0.361639, + 0.165203, 0.166121, 0.365312, + 0.164797, 0.169787, 0.369002, + 0.164391, 0.173411, 0.372682, + 0.163968, 0.177075, 0.376391, + 0.163527, 0.180716, 0.380085, + 0.163081, 0.184398, 0.383795, + 0.162637, 0.188101, 0.387502, + 0.162198, 0.191754, 0.391238, + 0.161760, 0.195449, 0.394962, + 0.161310, 0.199141, 0.398700, + 0.160837, 0.202866, 0.402443, + 0.160362, 0.206620, 0.406214, + 0.159908, 0.210329, 0.409984, + 0.159471, 0.214084, 0.413758, + 0.159039, 0.217839, 0.417555, + 0.158601, 0.221601, 0.421349, + 0.158144, 0.225384, 0.425181, + 0.157676, 0.229179, 0.429013, + 0.157228, 0.232977, 0.432870, + 0.156806, 0.236829, 0.436744, + 0.156386, 0.240632, 0.440631, + 0.155952, 0.244485, 0.444564, + 0.155517, 0.248387, 0.448507, + 0.155097, 0.252261, 0.452491, + 0.154694, 0.256148, 0.456492, + 0.154309, 0.260084, 0.460536, + 0.153943, 0.264033, 0.464642, + 0.153600, 0.267966, 0.468777, + 0.153286, 0.271942, 0.472958, + 0.153005, 0.275975, 0.477209, + 0.152762, 0.279975, 0.481516, + 0.152563, 0.284007, 0.485886, + 0.152415, 0.288059, 0.490337, + 0.152323, 0.292137, 0.494857, + 0.152295, 0.296219, 0.499444, + 0.152339, 0.300310, 0.504136, + 0.152463, 0.304414, 0.508899, + 0.152674, 0.308529, 0.513749, + 0.152981, 0.312618, 0.518688, + 0.153392, 0.316727, 0.523706, + 0.153915, 0.320817, 0.528817, + 0.154555, 0.324864, 0.533993, + 0.155323, 0.328919, 0.539260, + 0.156229, 0.332926, 0.544598, + 0.157206, 0.336879, 0.549998, + 0.158378, 0.340797, 0.555448, + 0.159616, 0.344655, 0.560957, + 0.161055, 0.348463, 0.566511, + 0.162589, 0.352195, 0.572090, + 0.164258, 0.355879, 0.577689, + 0.166003, 0.359470, 0.583313, + 0.167904, 0.362993, 0.588941, + 0.169932, 0.366457, 0.594581, + 0.172025, 0.369823, 0.600212, + 0.174249, 0.373130, 0.605830, + 0.176531, 0.376379, 0.611449, + 0.178949, 0.379540, 0.617045, + 0.181403, 0.382648, 0.622622, + 0.183976, 0.385696, 0.628190, + 0.186619, 0.388711, 0.633736, + 0.189341, 0.391669, 0.639262, + 0.192134, 0.394587, 0.644771, + 0.195027, 0.397481, 0.650276, + 0.197982, 0.400359, 0.655767, + 0.201030, 0.403213, 0.661243, + 0.204231, 0.406065, 0.666711, + 0.207483, 0.408906, 0.672184, + 0.210863, 0.411761, 0.677651, + 0.214378, 0.414631, 0.683108, + 0.218033, 0.417528, 0.688571, + 0.221826, 0.420436, 0.694021, + 0.225816, 0.423384, 0.699481, + 0.229930, 0.426373, 0.704939, + 0.234292, 0.429408, 0.710390, + 0.238813, 0.432492, 0.715836, + 0.243553, 0.435617, 0.721257, + 0.248547, 0.438782, 0.726672, + 0.253725, 0.442028, 0.732066, + 0.259152, 0.445303, 0.737422, + 0.264778, 0.448649, 0.742750, + 0.270677, 0.452043, 0.748029, + 0.276773, 0.455480, 0.753250, + 0.283095, 0.458971, 0.758422, + 0.289622, 0.462495, 0.763516, + 0.296351, 0.466068, 0.768535, + 0.303257, 0.469688, 0.773474, + 0.310367, 0.473307, 0.778324, + 0.317597, 0.476974, 0.783072, + 0.324970, 0.480647, 0.787721, + 0.332494, 0.484334, 0.792267, + 0.340093, 0.488042, 0.796701, + 0.347786, 0.491739, 0.801030, + 0.355555, 0.495425, 0.805247, + 0.363359, 0.499108, 0.809363, + 0.371217, 0.502778, 0.813363, + 0.379113, 0.506438, 0.817267, + 0.387014, 0.510082, 0.821072, + 0.394935, 0.513709, 0.824780, + 0.402847, 0.517310, 0.828402, + 0.410758, 0.520881, 0.831943, + 0.418634, 0.524436, 0.835398, + 0.426504, 0.527966, 0.838782, + 0.434346, 0.531477, 0.842105, + 0.442172, 0.534964, 0.845359, + 0.449948, 0.538437, 0.848559, + 0.457695, 0.541872, 0.851707, + 0.465406, 0.545307, 0.854810, + 0.473085, 0.548724, 0.857863, + 0.480729, 0.552119, 0.860879, + 0.488341, 0.555493, 0.863862, + 0.495894, 0.558869, 0.866797, + 0.503414, 0.562249, 0.869705, + 0.510895, 0.565597, 0.872582, + 0.518335, 0.568956, 0.875424, + 0.525716, 0.572320, 0.878233, + 0.533049, 0.575682, 0.881008, + 0.540328, 0.579042, 0.883754, + 0.547548, 0.582405, 0.886461, + 0.554710, 0.585792, 0.889130, + 0.561804, 0.589169, 0.891760, + 0.568813, 0.592583, 0.894351, + 0.575760, 0.595989, 0.896895, + 0.582600, 0.599423, 0.899390, + 0.589354, 0.602864, 0.901830, + 0.596014, 0.606322, 0.904217, + 0.602558, 0.609800, 0.906550, + 0.608993, 0.613288, 0.908815, + 0.615285, 0.616803, 0.911017, + 0.621456, 0.620324, 0.913150, + 0.627490, 0.623854, 0.915206, + 0.633377, 0.627389, 0.917197, + 0.639110, 0.630941, 0.919099, + 0.644686, 0.634493, 0.920929, + 0.650114, 0.638043, 0.922685, + 0.655374, 0.641594, 0.924355, + 0.660476, 0.645145, 0.925951, + 0.665423, 0.648695, 0.927463, + 0.670216, 0.652234, 0.928904, + 0.674845, 0.655767, 0.930274, + 0.679329, 0.659281, 0.931567, + 0.683675, 0.662794, 0.932793, + 0.687888, 0.666281, 0.933957, + 0.691973, 0.669769, 0.935062, + 0.695936, 0.673235, 0.936114, + 0.699783, 0.676690, 0.937110, + 0.703538, 0.680123, 0.938058, + 0.707197, 0.683555, 0.938964, + 0.710775, 0.686972, 0.939834, + 0.714271, 0.690376, 0.940666, + 0.717713, 0.693771, 0.941466, + 0.721083, 0.697160, 0.942246, + 0.724414, 0.700543, 0.942999, + 0.727695, 0.703915, 0.943730, + 0.730940, 0.707284, 0.944446, + 0.734158, 0.710651, 0.945148, + 0.737345, 0.714009, 0.945832, + 0.740512, 0.717375, 0.946509, + 0.743663, 0.720725, 0.947182, + 0.746797, 0.724085, 0.947844, + 0.749918, 0.727443, 0.948500, + 0.753034, 0.730802, 0.949152, + 0.756148, 0.734165, 0.949802, + 0.759251, 0.737524, 0.950450, + 0.762355, 0.740887, 0.951095, + 0.765464, 0.744257, 0.951736, + 0.768558, 0.747631, 0.952378, + 0.771667, 0.751005, 0.953024, + 0.774765, 0.754380, 0.953665, + 0.777876, 0.757757, 0.954305, + 0.780981, 0.761139, 0.954947, + 0.784096, 0.764526, 0.955591, + 0.787207, 0.767912, 0.956232, + 0.790319, 0.771312, 0.956872, + 0.793444, 0.774703, 0.957517, + 0.796559, 0.778108, 0.958163, + 0.799691, 0.781515, 0.958806, + 0.802814, 0.784924, 0.959450, + 0.805948, 0.788334, 0.960097, + 0.809088, 0.791749, 0.960744, + 0.812223, 0.795169, 0.961389, + 0.815367, 0.798594, 0.962035, + 0.818510, 0.802020, 0.962686, + 0.821666, 0.805450, 0.963336, + 0.824815, 0.808891, 0.963984, + 0.827972, 0.812327, 0.964634, + 0.831138, 0.815773, 0.965286, + 0.834302, 0.819214, 0.965939, + 0.837470, 0.822670, 0.966593, + 0.840640, 0.826123, 0.967249, + 0.843810, 0.829585, 0.967905, + 0.846989, 0.833043, 0.968558, + 0.850174, 0.836510, 0.969212, + 0.853356, 0.839984, 0.969868, + 0.856547, 0.843451, 0.970526, + 0.859742, 0.846929, 0.971184, + 0.862940, 0.850412, 0.971844, + 0.866137, 0.853895, 0.972506, + 0.869340, 0.857386, 0.973165, + 0.872548, 0.860879, 0.973823, + 0.875758, 0.864379, 0.974484, + 0.878969, 0.867881, 0.975146, + 0.882187, 0.871384, 0.975809, + 0.885409, 0.874890, 0.976473, + 0.888635, 0.878403, 0.977140, + 0.891863, 0.881920, 0.977804, + 0.895095, 0.885439, 0.978468, + 0.898326, 0.888964, 0.979137, + 0.901566, 0.892492, 0.979807, + 0.904809, 0.896022, 0.980474, + 0.908057, 0.899559, 0.981142, + 0.911303, 0.903098, 0.981813, + 0.914556, 0.906639, 0.982484, + 0.917813, 0.910187, 0.983151, + 0.921066, 0.913737, 0.983823, + 0.924330, 0.917292, 0.984498, + 0.927593, 0.920843, 0.985170, + 0.930866, 0.924406, 0.985842, + 0.934133, 0.927970, 0.986516, + 0.937412, 0.931540, 0.987191, + 0.940689, 0.935113, 0.987862, + 0.943968, 0.938690, 0.988536, + 0.947251, 0.942272, 0.989213, + 0.950534, 0.945851, 0.989888, + 0.953821, 0.949437, 0.990561, + 0.957106, 0.953030, 0.991236, + 0.960399, 0.956621, 0.991911, + 0.963687, 0.960219, 0.992587, + 0.966981, 0.963815, 0.993262, + 0.970273, 0.967421, 0.993933, + 0.973568, 0.971024, 0.994603, + 0.976862, 0.974631, 0.995274, + 0.980158, 0.978242, 0.995945, + 0.983450, 0.981861, 0.996615, + 0.986748, 0.985477, 0.997285, + 0.990040, 0.989098, 0.997954, + 0.993335, 0.992721, 0.998623, + 0.996621, 0.996340, 0.999290, + 0.999916, 0.999970, 0.999952, +}; + +const float ColorMap::m_lajolla[m_size] = +{ + 0.999831, 0.999745, 0.799907, + 0.999529, 0.997249, 0.792918, + 0.999208, 0.994757, 0.785931, + 0.998869, 0.992265, 0.778940, + 0.998513, 0.989768, 0.771954, + 0.998141, 0.987268, 0.764967, + 0.997753, 0.984763, 0.757977, + 0.997350, 0.982252, 0.750989, + 0.996933, 0.979734, 0.743991, + 0.996501, 0.977208, 0.736995, + 0.996056, 0.974673, 0.729998, + 0.995597, 0.972134, 0.722989, + 0.995124, 0.969577, 0.715989, + 0.994638, 0.967011, 0.708976, + 0.994140, 0.964426, 0.701948, + 0.993629, 0.961826, 0.694924, + 0.993104, 0.959212, 0.687894, + 0.992564, 0.956574, 0.680846, + 0.992011, 0.953914, 0.673801, + 0.991445, 0.951226, 0.666736, + 0.990867, 0.948508, 0.659670, + 0.990276, 0.945762, 0.652583, + 0.989672, 0.942984, 0.645487, + 0.989053, 0.940166, 0.638383, + 0.988417, 0.937306, 0.631265, + 0.987770, 0.934399, 0.624131, + 0.987110, 0.931449, 0.616981, + 0.986430, 0.928445, 0.609814, + 0.985734, 0.925390, 0.602638, + 0.985024, 0.922276, 0.595448, + 0.984295, 0.919090, 0.588241, + 0.983544, 0.915842, 0.581031, + 0.982780, 0.912528, 0.573808, + 0.981996, 0.909132, 0.566581, + 0.981188, 0.905665, 0.559340, + 0.980362, 0.902106, 0.552106, + 0.979515, 0.898463, 0.544864, + 0.978640, 0.894738, 0.537636, + 0.977749, 0.890912, 0.530411, + 0.976833, 0.886997, 0.523209, + 0.975892, 0.882982, 0.516016, + 0.974929, 0.878873, 0.508878, + 0.973943, 0.874665, 0.501764, + 0.972936, 0.870355, 0.494698, + 0.971900, 0.865954, 0.487691, + 0.970841, 0.861451, 0.480735, + 0.969761, 0.856853, 0.473869, + 0.968661, 0.852169, 0.467098, + 0.967540, 0.847384, 0.460403, + 0.966394, 0.842526, 0.453838, + 0.965232, 0.837582, 0.447383, + 0.964054, 0.832560, 0.441065, + 0.962864, 0.827471, 0.434901, + 0.961653, 0.822324, 0.428874, + 0.960439, 0.817114, 0.423009, + 0.959208, 0.811859, 0.417337, + 0.957973, 0.806564, 0.411826, + 0.956727, 0.801228, 0.406515, + 0.955483, 0.795868, 0.401394, + 0.954233, 0.790491, 0.396470, + 0.952985, 0.785103, 0.391761, + 0.951734, 0.779703, 0.387244, + 0.950493, 0.774305, 0.382955, + 0.949252, 0.768915, 0.378860, + 0.948022, 0.763539, 0.374977, + 0.946797, 0.758183, 0.371295, + 0.945583, 0.752838, 0.367823, + 0.944379, 0.747533, 0.364549, + 0.943186, 0.742247, 0.361472, + 0.942003, 0.736993, 0.358570, + 0.940832, 0.731775, 0.355871, + 0.939675, 0.726590, 0.353323, + 0.938529, 0.721440, 0.350960, + 0.937397, 0.716337, 0.348738, + 0.936279, 0.711264, 0.346674, + 0.935167, 0.706227, 0.344750, + 0.934070, 0.701224, 0.342973, + 0.932986, 0.696264, 0.341304, + 0.931915, 0.691330, 0.339767, + 0.930857, 0.686438, 0.338316, + 0.929804, 0.681570, 0.336993, + 0.928760, 0.676746, 0.335755, + 0.927727, 0.671938, 0.334613, + 0.926704, 0.667161, 0.333537, + 0.925689, 0.662415, 0.332545, + 0.924676, 0.657688, 0.331596, + 0.923678, 0.652971, 0.330728, + 0.922679, 0.648286, 0.329931, + 0.921685, 0.643617, 0.329173, + 0.920691, 0.638968, 0.328448, + 0.919708, 0.634336, 0.327769, + 0.918721, 0.629704, 0.327130, + 0.917745, 0.625092, 0.326525, + 0.916762, 0.620494, 0.325953, + 0.915774, 0.615891, 0.325408, + 0.914796, 0.611306, 0.324889, + 0.913814, 0.606722, 0.324391, + 0.912824, 0.602139, 0.323912, + 0.911832, 0.597565, 0.323453, + 0.910837, 0.592994, 0.323010, + 0.909834, 0.588408, 0.322574, + 0.908821, 0.583836, 0.322140, + 0.907806, 0.579265, 0.321715, + 0.906775, 0.574674, 0.321308, + 0.905735, 0.570090, 0.320914, + 0.904677, 0.565498, 0.320521, + 0.903607, 0.560904, 0.320125, + 0.902520, 0.556296, 0.319729, + 0.901411, 0.551691, 0.319336, + 0.900282, 0.547063, 0.318947, + 0.899129, 0.542431, 0.318559, + 0.897945, 0.537786, 0.318170, + 0.896736, 0.533119, 0.317781, + 0.895488, 0.528446, 0.317388, + 0.894206, 0.523753, 0.316994, + 0.892880, 0.519049, 0.316599, + 0.891511, 0.514322, 0.316201, + 0.890091, 0.509569, 0.315793, + 0.888622, 0.504806, 0.315370, + 0.887088, 0.500007, 0.314938, + 0.885494, 0.495205, 0.314507, + 0.883832, 0.490372, 0.314076, + 0.882090, 0.485505, 0.313632, + 0.880268, 0.480622, 0.313167, + 0.878363, 0.475721, 0.312685, + 0.876360, 0.470784, 0.312193, + 0.874260, 0.465813, 0.311698, + 0.872057, 0.460829, 0.311198, + 0.869732, 0.455822, 0.310676, + 0.867292, 0.450788, 0.310126, + 0.864725, 0.445721, 0.309559, + 0.862029, 0.440643, 0.308972, + 0.859184, 0.435556, 0.308348, + 0.856198, 0.430436, 0.307704, + 0.853059, 0.425310, 0.307055, + 0.849766, 0.420166, 0.306384, + 0.846302, 0.415023, 0.305675, + 0.842682, 0.409886, 0.304924, + 0.838883, 0.404735, 0.304148, + 0.834916, 0.399603, 0.303343, + 0.830773, 0.394500, 0.302509, + 0.826450, 0.389415, 0.301644, + 0.821954, 0.384356, 0.300746, + 0.817275, 0.379341, 0.299821, + 0.812425, 0.374378, 0.298850, + 0.807406, 0.369452, 0.297832, + 0.802212, 0.364595, 0.296800, + 0.796857, 0.359813, 0.295702, + 0.791346, 0.355117, 0.294575, + 0.785683, 0.350488, 0.293413, + 0.779869, 0.345935, 0.292218, + 0.773918, 0.341509, 0.290978, + 0.767839, 0.337176, 0.289694, + 0.761640, 0.332954, 0.288366, + 0.755332, 0.328832, 0.286993, + 0.748910, 0.324813, 0.285587, + 0.742404, 0.320940, 0.284148, + 0.735801, 0.317163, 0.282680, + 0.729129, 0.313515, 0.281127, + 0.722383, 0.309983, 0.279578, + 0.715588, 0.306561, 0.277960, + 0.708732, 0.303235, 0.276314, + 0.701821, 0.300045, 0.274597, + 0.694882, 0.296965, 0.272862, + 0.687913, 0.293960, 0.271085, + 0.680906, 0.291083, 0.269266, + 0.673889, 0.288276, 0.267363, + 0.666846, 0.285557, 0.265463, + 0.659797, 0.282950, 0.263515, + 0.652729, 0.280375, 0.261494, + 0.645660, 0.277907, 0.259453, + 0.638589, 0.275496, 0.257342, + 0.631514, 0.273125, 0.255206, + 0.624438, 0.270823, 0.253003, + 0.617360, 0.268553, 0.250756, + 0.610287, 0.266332, 0.248485, + 0.603222, 0.264149, 0.246122, + 0.596156, 0.261987, 0.243734, + 0.589093, 0.259866, 0.241320, + 0.582044, 0.257747, 0.238831, + 0.575000, 0.255668, 0.236327, + 0.567965, 0.253604, 0.233732, + 0.560934, 0.251554, 0.231138, + 0.553914, 0.249483, 0.228487, + 0.546904, 0.247454, 0.225780, + 0.539894, 0.245396, 0.223036, + 0.532906, 0.243360, 0.220254, + 0.525921, 0.241340, 0.217418, + 0.518950, 0.239301, 0.214548, + 0.511977, 0.237280, 0.211665, + 0.505025, 0.235233, 0.208715, + 0.498095, 0.233152, 0.205757, + 0.491166, 0.231122, 0.202744, + 0.484232, 0.229060, 0.199703, + 0.477332, 0.226999, 0.196690, + 0.470446, 0.224903, 0.193599, + 0.463562, 0.222859, 0.190481, + 0.456701, 0.220788, 0.187383, + 0.449858, 0.218684, 0.184220, + 0.443028, 0.216604, 0.181049, + 0.436213, 0.214490, 0.177923, + 0.429412, 0.212397, 0.174725, + 0.422622, 0.210282, 0.171513, + 0.415865, 0.208176, 0.168312, + 0.409121, 0.206083, 0.165094, + 0.402387, 0.203968, 0.161932, + 0.395693, 0.201818, 0.158717, + 0.389005, 0.199696, 0.155501, + 0.382326, 0.197597, 0.152278, + 0.375682, 0.195474, 0.149098, + 0.369063, 0.193367, 0.145930, + 0.362455, 0.191238, 0.142740, + 0.355879, 0.189133, 0.139537, + 0.349312, 0.187021, 0.136394, + 0.342780, 0.184917, 0.133292, + 0.336248, 0.182782, 0.130208, + 0.329771, 0.180679, 0.127046, + 0.323293, 0.178608, 0.123972, + 0.316844, 0.176475, 0.120915, + 0.310431, 0.174411, 0.117925, + 0.304013, 0.172305, 0.114880, + 0.297642, 0.170234, 0.111957, + 0.291301, 0.168140, 0.108996, + 0.284955, 0.166067, 0.106101, + 0.278670, 0.164036, 0.103209, + 0.272374, 0.161969, 0.100318, + 0.266143, 0.159860, 0.097520, + 0.259913, 0.157833, 0.094764, + 0.253709, 0.155804, 0.092001, + 0.247534, 0.153732, 0.089260, + 0.241379, 0.151698, 0.086567, + 0.235263, 0.149701, 0.083951, + 0.229142, 0.147663, 0.081329, + 0.223074, 0.145659, 0.078687, + 0.217021, 0.143616, 0.076120, + 0.211005, 0.141605, 0.073581, + 0.205032, 0.139543, 0.070837, + 0.199089, 0.137586, 0.068084, + 0.193222, 0.135533, 0.065213, + 0.187395, 0.133541, 0.062323, + 0.181558, 0.131530, 0.059337, + 0.175797, 0.129510, 0.056198, + 0.170114, 0.127477, 0.052899, + 0.164454, 0.125481, 0.049640, + 0.158832, 0.123421, 0.046062, + 0.153229, 0.121394, 0.042482, + 0.147732, 0.119395, 0.038691, + 0.142251, 0.117348, 0.034740, + 0.136816, 0.115319, 0.030784, + 0.131463, 0.113321, 0.026943, + 0.126157, 0.111255, 0.023110, + 0.120855, 0.109198, 0.019283, + 0.115699, 0.107134, 0.015460, + 0.110584, 0.104992, 0.011622, + 0.105487, 0.102956, 0.007624, + 0.100227, 0.100908, 0.003791, +}; + +const float ColorMap::m_bamako[m_size] = +{ + 0.001175, 0.250044, 0.300000, + 0.003900, 0.251568, 0.298605, + 0.006602, 0.253054, 0.297219, + 0.009291, 0.254561, 0.295808, + 0.012175, 0.256038, 0.294402, + 0.014801, 0.257549, 0.292999, + 0.017450, 0.259071, 0.291607, + 0.020096, 0.260569, 0.290178, + 0.022743, 0.262082, 0.288777, + 0.025398, 0.263613, 0.287345, + 0.028064, 0.265111, 0.285921, + 0.030746, 0.266642, 0.284503, + 0.033437, 0.268163, 0.283091, + 0.036369, 0.269714, 0.281652, + 0.039136, 0.271239, 0.280209, + 0.041941, 0.272776, 0.278785, + 0.044597, 0.274322, 0.277340, + 0.047216, 0.275891, 0.275905, + 0.049816, 0.277433, 0.274432, + 0.052305, 0.278996, 0.272982, + 0.054846, 0.280535, 0.271512, + 0.057331, 0.282129, 0.270069, + 0.059903, 0.283690, 0.268596, + 0.062324, 0.285258, 0.267103, + 0.064737, 0.286842, 0.265642, + 0.067177, 0.288441, 0.264165, + 0.069647, 0.290030, 0.262671, + 0.072007, 0.291645, 0.261177, + 0.074435, 0.293235, 0.259690, + 0.076803, 0.294845, 0.258168, + 0.079232, 0.296472, 0.256672, + 0.081738, 0.298081, 0.255176, + 0.084163, 0.299722, 0.253651, + 0.086556, 0.301341, 0.252132, + 0.089008, 0.302979, 0.250592, + 0.091487, 0.304633, 0.249073, + 0.093906, 0.306298, 0.247540, + 0.096327, 0.307931, 0.245981, + 0.098845, 0.309614, 0.244430, + 0.101320, 0.311270, 0.242884, + 0.103840, 0.312940, 0.241344, + 0.106373, 0.314614, 0.239778, + 0.108845, 0.316315, 0.238209, + 0.111400, 0.317998, 0.236658, + 0.113913, 0.319699, 0.235086, + 0.116485, 0.321393, 0.233469, + 0.119038, 0.323111, 0.231914, + 0.121564, 0.324810, 0.230295, + 0.124158, 0.326529, 0.228728, + 0.126757, 0.328264, 0.227119, + 0.129375, 0.330003, 0.225511, + 0.132004, 0.331730, 0.223888, + 0.134649, 0.333485, 0.222278, + 0.137276, 0.335240, 0.220682, + 0.139884, 0.336985, 0.219039, + 0.142577, 0.338745, 0.217408, + 0.145276, 0.340514, 0.215777, + 0.147932, 0.342290, 0.214132, + 0.150622, 0.344055, 0.212483, + 0.153312, 0.345842, 0.210827, + 0.156069, 0.347650, 0.209167, + 0.158791, 0.349444, 0.207513, + 0.161545, 0.351250, 0.205851, + 0.164291, 0.353061, 0.204188, + 0.167053, 0.354889, 0.202479, + 0.169827, 0.356708, 0.200784, + 0.172592, 0.358526, 0.199098, + 0.175396, 0.360362, 0.197423, + 0.178238, 0.362218, 0.195717, + 0.181022, 0.364063, 0.194034, + 0.183870, 0.365930, 0.192298, + 0.186724, 0.367787, 0.190577, + 0.189589, 0.369663, 0.188866, + 0.192463, 0.371540, 0.187139, + 0.195352, 0.373435, 0.185419, + 0.198249, 0.375327, 0.183644, + 0.201156, 0.377240, 0.181885, + 0.204127, 0.379148, 0.180139, + 0.207065, 0.381078, 0.178409, + 0.210008, 0.383003, 0.176605, + 0.212971, 0.384930, 0.174859, + 0.215989, 0.386875, 0.173056, + 0.218980, 0.388843, 0.171271, + 0.221986, 0.390806, 0.169504, + 0.225012, 0.392773, 0.167676, + 0.228091, 0.394757, 0.165848, + 0.231153, 0.396745, 0.164075, + 0.234233, 0.398752, 0.162241, + 0.237349, 0.400781, 0.160365, + 0.240427, 0.402789, 0.158567, + 0.243566, 0.404820, 0.156711, + 0.246728, 0.406871, 0.154836, + 0.249887, 0.408927, 0.152959, + 0.253098, 0.410998, 0.151093, + 0.256293, 0.413082, 0.149225, + 0.259543, 0.415165, 0.147333, + 0.262784, 0.417278, 0.145458, + 0.266059, 0.419392, 0.143520, + 0.269354, 0.421510, 0.141607, + 0.272646, 0.423655, 0.139630, + 0.276003, 0.425815, 0.137750, + 0.279354, 0.427992, 0.135746, + 0.282735, 0.430174, 0.133822, + 0.286111, 0.432382, 0.131850, + 0.289556, 0.434588, 0.129889, + 0.292997, 0.436821, 0.127876, + 0.296483, 0.439056, 0.125885, + 0.299981, 0.441323, 0.123822, + 0.303503, 0.443605, 0.121787, + 0.307065, 0.445890, 0.119770, + 0.310662, 0.448206, 0.117731, + 0.314258, 0.450539, 0.115679, + 0.317900, 0.452889, 0.113616, + 0.321563, 0.455253, 0.111528, + 0.325257, 0.457630, 0.109445, + 0.329005, 0.460030, 0.107308, + 0.332768, 0.462451, 0.105131, + 0.336543, 0.464897, 0.103037, + 0.340375, 0.467359, 0.100847, + 0.344219, 0.469841, 0.098693, + 0.348122, 0.472335, 0.096453, + 0.352037, 0.474856, 0.094325, + 0.356012, 0.477383, 0.092102, + 0.360001, 0.479944, 0.089849, + 0.364033, 0.482513, 0.087617, + 0.368109, 0.485098, 0.085276, + 0.372215, 0.487718, 0.082993, + 0.376382, 0.490337, 0.080686, + 0.380573, 0.492964, 0.078304, + 0.384799, 0.495619, 0.075955, + 0.389090, 0.498292, 0.073656, + 0.393410, 0.500954, 0.071224, + 0.397790, 0.503623, 0.068802, + 0.402200, 0.506297, 0.066264, + 0.406682, 0.508981, 0.063774, + 0.411203, 0.511641, 0.061172, + 0.415776, 0.514308, 0.058651, + 0.420400, 0.516946, 0.056070, + 0.425087, 0.519560, 0.053359, + 0.429814, 0.522143, 0.050712, + 0.434597, 0.524702, 0.047969, + 0.439428, 0.527215, 0.045272, + 0.444319, 0.529673, 0.042483, + 0.449245, 0.532071, 0.039697, + 0.454215, 0.534399, 0.036906, + 0.459225, 0.536663, 0.034001, + 0.464275, 0.538835, 0.031435, + 0.469349, 0.540915, 0.028957, + 0.474442, 0.542911, 0.026597, + 0.479560, 0.544799, 0.024363, + 0.484680, 0.546591, 0.022265, + 0.489820, 0.548275, 0.020312, + 0.494971, 0.549847, 0.018512, + 0.500103, 0.551321, 0.016876, + 0.505242, 0.552694, 0.015412, + 0.510378, 0.553974, 0.014132, + 0.515503, 0.555166, 0.013033, + 0.520629, 0.556283, 0.012180, + 0.525754, 0.557353, 0.011416, + 0.530877, 0.558348, 0.010839, + 0.536006, 0.559331, 0.010585, + 0.541151, 0.560295, 0.010612, + 0.546332, 0.561256, 0.010945, + 0.551529, 0.562255, 0.011649, + 0.556773, 0.563285, 0.012516, + 0.562073, 0.564377, 0.013650, + 0.567425, 0.565550, 0.015118, + 0.572854, 0.566844, 0.016927, + 0.578351, 0.568243, 0.019098, + 0.583945, 0.569784, 0.021653, + 0.589625, 0.571485, 0.024617, + 0.595411, 0.573356, 0.028014, + 0.601291, 0.575400, 0.031870, + 0.607281, 0.577626, 0.036408, + 0.613352, 0.580062, 0.041287, + 0.619542, 0.582679, 0.046299, + 0.625810, 0.585515, 0.051565, + 0.632175, 0.588528, 0.057023, + 0.638620, 0.591759, 0.062625, + 0.645137, 0.595166, 0.068288, + 0.651731, 0.598763, 0.074092, + 0.658372, 0.602535, 0.079969, + 0.665062, 0.606473, 0.085984, + 0.671795, 0.610568, 0.092200, + 0.678556, 0.614798, 0.098456, + 0.685338, 0.619175, 0.104760, + 0.692129, 0.623659, 0.111287, + 0.698910, 0.628247, 0.117832, + 0.705693, 0.632925, 0.124466, + 0.712448, 0.637686, 0.131249, + 0.719184, 0.642515, 0.138104, + 0.725880, 0.647397, 0.145035, + 0.732527, 0.652321, 0.152009, + 0.739120, 0.657282, 0.159119, + 0.745640, 0.662259, 0.166286, + 0.752096, 0.667240, 0.173524, + 0.758470, 0.672228, 0.180821, + 0.764752, 0.677207, 0.188226, + 0.770940, 0.682154, 0.195617, + 0.777025, 0.687087, 0.203085, + 0.782992, 0.691986, 0.210591, + 0.788855, 0.696837, 0.218132, + 0.794593, 0.701640, 0.225696, + 0.800210, 0.706402, 0.233246, + 0.805697, 0.711100, 0.240836, + 0.811064, 0.715739, 0.248452, + 0.816305, 0.720308, 0.256008, + 0.821416, 0.724823, 0.263607, + 0.826404, 0.729264, 0.271154, + 0.831274, 0.733644, 0.278697, + 0.836021, 0.737953, 0.286192, + 0.840659, 0.742207, 0.293686, + 0.845186, 0.746391, 0.301143, + 0.849615, 0.750519, 0.308586, + 0.853939, 0.754588, 0.315983, + 0.858179, 0.758596, 0.323333, + 0.862341, 0.762550, 0.330648, + 0.866414, 0.766461, 0.337937, + 0.870416, 0.770323, 0.345196, + 0.874359, 0.774139, 0.352412, + 0.878242, 0.777923, 0.359601, + 0.882069, 0.781666, 0.366759, + 0.885849, 0.785374, 0.373882, + 0.889582, 0.789052, 0.380980, + 0.893277, 0.792699, 0.388036, + 0.896942, 0.796319, 0.395081, + 0.900568, 0.799924, 0.402087, + 0.904169, 0.803498, 0.409089, + 0.907751, 0.807061, 0.416060, + 0.911302, 0.810597, 0.423005, + 0.914836, 0.814123, 0.429945, + 0.918353, 0.817635, 0.436863, + 0.921858, 0.821135, 0.443762, + 0.925342, 0.824620, 0.450643, + 0.928815, 0.828096, 0.457507, + 0.932278, 0.831569, 0.464376, + 0.935735, 0.835023, 0.471215, + 0.939177, 0.838471, 0.478042, + 0.942613, 0.841917, 0.484861, + 0.946034, 0.845350, 0.491688, + 0.949452, 0.848781, 0.498492, + 0.952864, 0.852210, 0.505275, + 0.956267, 0.855629, 0.512068, + 0.959662, 0.859041, 0.518860, + 0.963051, 0.862459, 0.525632, + 0.966431, 0.865865, 0.532407, + 0.969807, 0.869272, 0.539168, + 0.973179, 0.872677, 0.545938, + 0.976539, 0.876078, 0.552691, + 0.979899, 0.879477, 0.559448, + 0.983247, 0.882877, 0.566201, + 0.986594, 0.886282, 0.572957, + 0.989930, 0.889679, 0.579707, + 0.993263, 0.893078, 0.586451, + 0.996581, 0.896481, 0.593202, + 0.999903, 0.899882, 0.599947, +}; + +const float ColorMap::m_plasma[m_size] = +{ + 0.0504, 0.0298, 0.5280, + 0.0635, 0.0284, 0.5331, + 0.0754, 0.0272, 0.5380, + 0.0862, 0.0261, 0.5427, + 0.0964, 0.0252, 0.5471, + 0.1060, 0.0243, 0.5514, + 0.1151, 0.0236, 0.5555, + 0.1239, 0.0229, 0.5594, + 0.1324, 0.0223, 0.5633, + 0.1406, 0.0217, 0.5670, + 0.1486, 0.0212, 0.5706, + 0.1564, 0.0207, 0.5741, + 0.1641, 0.0202, 0.5775, + 0.1716, 0.0197, 0.5808, + 0.1789, 0.0193, 0.5841, + 0.1862, 0.0188, 0.5872, + 0.1934, 0.0184, 0.5903, + 0.2004, 0.0179, 0.5934, + 0.2074, 0.0174, 0.5963, + 0.2144, 0.0170, 0.5992, + 0.2212, 0.0165, 0.6021, + 0.2280, 0.0160, 0.6049, + 0.2347, 0.0155, 0.6076, + 0.2414, 0.0150, 0.6103, + 0.2480, 0.0144, 0.6129, + 0.2546, 0.0139, 0.6154, + 0.2612, 0.0133, 0.6179, + 0.2677, 0.0127, 0.6203, + 0.2742, 0.0121, 0.6227, + 0.2806, 0.0115, 0.6250, + 0.2871, 0.0109, 0.6273, + 0.2935, 0.0102, 0.6295, + 0.2999, 0.0096, 0.6316, + 0.3062, 0.0089, 0.6337, + 0.3125, 0.0082, 0.6357, + 0.3189, 0.0076, 0.6376, + 0.3251, 0.0069, 0.6395, + 0.3314, 0.0063, 0.6413, + 0.3377, 0.0056, 0.6430, + 0.3439, 0.0050, 0.6447, + 0.3502, 0.0044, 0.6463, + 0.3564, 0.0038, 0.6478, + 0.3626, 0.0032, 0.6492, + 0.3687, 0.0027, 0.6506, + 0.3749, 0.0022, 0.6519, + 0.3810, 0.0018, 0.6531, + 0.3872, 0.0014, 0.6542, + 0.3933, 0.0011, 0.6552, + 0.3994, 0.0009, 0.6561, + 0.4055, 0.0007, 0.6570, + 0.4116, 0.0006, 0.6577, + 0.4176, 0.0006, 0.6584, + 0.4237, 0.0006, 0.6590, + 0.4297, 0.0008, 0.6594, + 0.4357, 0.0011, 0.6598, + 0.4417, 0.0015, 0.6601, + 0.4477, 0.0021, 0.6602, + 0.4537, 0.0028, 0.6603, + 0.4596, 0.0036, 0.6603, + 0.4656, 0.0045, 0.6601, + 0.4715, 0.0057, 0.6599, + 0.4773, 0.0070, 0.6595, + 0.4832, 0.0085, 0.6591, + 0.4891, 0.0101, 0.6585, + 0.4949, 0.0120, 0.6579, + 0.5007, 0.0141, 0.6571, + 0.5065, 0.0163, 0.6562, + 0.5122, 0.0188, 0.6552, + 0.5179, 0.0216, 0.6541, + 0.5236, 0.0245, 0.6529, + 0.5293, 0.0277, 0.6516, + 0.5350, 0.0312, 0.6502, + 0.5406, 0.0350, 0.6486, + 0.5462, 0.0390, 0.6470, + 0.5517, 0.0431, 0.6453, + 0.5572, 0.0473, 0.6434, + 0.5627, 0.0515, 0.6415, + 0.5682, 0.0558, 0.6395, + 0.5736, 0.0600, 0.6373, + 0.5790, 0.0643, 0.6351, + 0.5844, 0.0686, 0.6328, + 0.5897, 0.0729, 0.6304, + 0.5950, 0.0772, 0.6279, + 0.6003, 0.0815, 0.6253, + 0.6055, 0.0859, 0.6227, + 0.6107, 0.0902, 0.6200, + 0.6158, 0.0946, 0.6171, + 0.6209, 0.0989, 0.6143, + 0.6260, 0.1033, 0.6113, + 0.6310, 0.1077, 0.6083, + 0.6360, 0.1121, 0.6052, + 0.6410, 0.1165, 0.6021, + 0.6459, 0.1209, 0.5989, + 0.6507, 0.1253, 0.5956, + 0.6556, 0.1297, 0.5923, + 0.6604, 0.1341, 0.5890, + 0.6651, 0.1386, 0.5856, + 0.6698, 0.1430, 0.5822, + 0.6745, 0.1474, 0.5787, + 0.6792, 0.1518, 0.5752, + 0.6838, 0.1563, 0.5717, + 0.6883, 0.1607, 0.5681, + 0.6928, 0.1651, 0.5645, + 0.6973, 0.1696, 0.5609, + 0.7018, 0.1740, 0.5573, + 0.7062, 0.1784, 0.5537, + 0.7105, 0.1829, 0.5500, + 0.7149, 0.1873, 0.5463, + 0.7192, 0.1917, 0.5427, + 0.7234, 0.1962, 0.5390, + 0.7277, 0.2006, 0.5353, + 0.7319, 0.2050, 0.5316, + 0.7360, 0.2094, 0.5279, + 0.7401, 0.2139, 0.5242, + 0.7442, 0.2183, 0.5205, + 0.7483, 0.2227, 0.5168, + 0.7523, 0.2271, 0.5131, + 0.7563, 0.2316, 0.5095, + 0.7603, 0.2360, 0.5058, + 0.7642, 0.2404, 0.5021, + 0.7681, 0.2448, 0.4985, + 0.7720, 0.2492, 0.4948, + 0.7758, 0.2537, 0.4912, + 0.7796, 0.2581, 0.4875, + 0.7834, 0.2625, 0.4839, + 0.7871, 0.2669, 0.4803, + 0.7909, 0.2713, 0.4767, + 0.7945, 0.2758, 0.4731, + 0.7982, 0.2802, 0.4695, + 0.8019, 0.2846, 0.4660, + 0.8055, 0.2891, 0.4624, + 0.8091, 0.2935, 0.4589, + 0.8126, 0.2979, 0.4553, + 0.8161, 0.3024, 0.4518, + 0.8197, 0.3068, 0.4483, + 0.8231, 0.3113, 0.4448, + 0.8266, 0.3157, 0.4413, + 0.8300, 0.3202, 0.4378, + 0.8334, 0.3246, 0.4344, + 0.8368, 0.3291, 0.4309, + 0.8402, 0.3336, 0.4275, + 0.8435, 0.3381, 0.4240, + 0.8468, 0.3426, 0.4206, + 0.8501, 0.3470, 0.4172, + 0.8533, 0.3516, 0.4137, + 0.8565, 0.3561, 0.4103, + 0.8598, 0.3606, 0.4069, + 0.8629, 0.3651, 0.4035, + 0.8661, 0.3697, 0.4001, + 0.8692, 0.3742, 0.3967, + 0.8723, 0.3788, 0.3934, + 0.8754, 0.3833, 0.3900, + 0.8784, 0.3879, 0.3866, + 0.8814, 0.3925, 0.3832, + 0.8844, 0.3971, 0.3799, + 0.8874, 0.4018, 0.3765, + 0.8903, 0.4064, 0.3731, + 0.8932, 0.4110, 0.3698, + 0.8961, 0.4157, 0.3664, + 0.8990, 0.4204, 0.3630, + 0.9018, 0.4251, 0.3597, + 0.9046, 0.4298, 0.3563, + 0.9074, 0.4345, 0.3530, + 0.9101, 0.4393, 0.3496, + 0.9128, 0.4440, 0.3463, + 0.9155, 0.4488, 0.3429, + 0.9181, 0.4536, 0.3395, + 0.9207, 0.4584, 0.3362, + 0.9233, 0.4633, 0.3328, + 0.9258, 0.4681, 0.3294, + 0.9283, 0.4730, 0.3261, + 0.9308, 0.4779, 0.3227, + 0.9332, 0.4828, 0.3193, + 0.9356, 0.4877, 0.3160, + 0.9380, 0.4927, 0.3126, + 0.9403, 0.4976, 0.3092, + 0.9426, 0.5026, 0.3058, + 0.9448, 0.5077, 0.3024, + 0.9471, 0.5127, 0.2990, + 0.9492, 0.5178, 0.2957, + 0.9513, 0.5229, 0.2923, + 0.9534, 0.5280, 0.2889, + 0.9555, 0.5331, 0.2855, + 0.9575, 0.5383, 0.2821, + 0.9594, 0.5434, 0.2787, + 0.9613, 0.5486, 0.2753, + 0.9632, 0.5539, 0.2719, + 0.9650, 0.5591, 0.2685, + 0.9668, 0.5644, 0.2651, + 0.9685, 0.5697, 0.2617, + 0.9702, 0.5750, 0.2583, + 0.9718, 0.5804, 0.2549, + 0.9734, 0.5858, 0.2515, + 0.9749, 0.5912, 0.2482, + 0.9764, 0.5966, 0.2448, + 0.9779, 0.6021, 0.2414, + 0.9792, 0.6075, 0.2380, + 0.9806, 0.6130, 0.2346, + 0.9818, 0.6186, 0.2313, + 0.9830, 0.6241, 0.2279, + 0.9842, 0.6297, 0.2246, + 0.9853, 0.6353, 0.2213, + 0.9863, 0.6410, 0.2179, + 0.9873, 0.6466, 0.2146, + 0.9883, 0.6523, 0.2114, + 0.9891, 0.6580, 0.2081, + 0.9899, 0.6638, 0.2049, + 0.9907, 0.6696, 0.2016, + 0.9914, 0.6754, 0.1985, + 0.9920, 0.6812, 0.1953, + 0.9925, 0.6870, 0.1922, + 0.9930, 0.6929, 0.1891, + 0.9935, 0.6988, 0.1860, + 0.9938, 0.7047, 0.1830, + 0.9941, 0.7107, 0.1801, + 0.9943, 0.7167, 0.1772, + 0.9945, 0.7227, 0.1744, + 0.9946, 0.7287, 0.1716, + 0.9946, 0.7348, 0.1689, + 0.9945, 0.7409, 0.1663, + 0.9944, 0.7470, 0.1638, + 0.9941, 0.7531, 0.1614, + 0.9939, 0.7593, 0.1591, + 0.9935, 0.7655, 0.1569, + 0.9930, 0.7717, 0.1548, + 0.9925, 0.7780, 0.1529, + 0.9919, 0.7842, 0.1510, + 0.9912, 0.7905, 0.1494, + 0.9904, 0.7969, 0.1479, + 0.9896, 0.8032, 0.1465, + 0.9886, 0.8096, 0.1454, + 0.9876, 0.8160, 0.1444, + 0.9865, 0.8224, 0.1436, + 0.9853, 0.8288, 0.1429, + 0.9840, 0.8353, 0.1425, + 0.9827, 0.8418, 0.1423, + 0.9812, 0.8483, 0.1423, + 0.9796, 0.8549, 0.1425, + 0.9780, 0.8614, 0.1428, + 0.9763, 0.8680, 0.1434, + 0.9744, 0.8746, 0.1441, + 0.9725, 0.8812, 0.1449, + 0.9705, 0.8879, 0.1459, + 0.9684, 0.8946, 0.1470, + 0.9663, 0.9012, 0.1482, + 0.9640, 0.9080, 0.1494, + 0.9617, 0.9147, 0.1505, + 0.9593, 0.9214, 0.1516, + 0.9568, 0.9282, 0.1524, + 0.9543, 0.9349, 0.1529, + 0.9517, 0.9417, 0.1529, + 0.9492, 0.9484, 0.1522, + 0.9466, 0.9552, 0.1503, + 0.9442, 0.9619, 0.1469, + 0.9419, 0.9686, 0.1410, + 0.9400, 0.9752, 0.1313, +}; + +const float ColorMap::m_rainbow[m_size] = +{ + 0.5000, 0.0000, 1.0000, + 0.4922, 0.0123, 1.0000, + 0.4843, 0.0246, 0.9999, + 0.4765, 0.0370, 0.9998, + 0.4686, 0.0493, 0.9997, + 0.4608, 0.0616, 0.9995, + 0.4529, 0.0739, 0.9993, + 0.4451, 0.0861, 0.9991, + 0.4373, 0.0984, 0.9988, + 0.4294, 0.1107, 0.9985, + 0.4216, 0.1229, 0.9981, + 0.4137, 0.1351, 0.9977, + 0.4059, 0.1473, 0.9973, + 0.3980, 0.1595, 0.9968, + 0.3902, 0.1716, 0.9963, + 0.3824, 0.1837, 0.9957, + 0.3745, 0.1958, 0.9951, + 0.3667, 0.2079, 0.9945, + 0.3588, 0.2199, 0.9939, + 0.3510, 0.2319, 0.9932, + 0.3431, 0.2439, 0.9924, + 0.3353, 0.2558, 0.9916, + 0.3275, 0.2677, 0.9908, + 0.3196, 0.2796, 0.9900, + 0.3118, 0.2914, 0.9891, + 0.3039, 0.3032, 0.9882, + 0.2961, 0.3149, 0.9872, + 0.2882, 0.3265, 0.9862, + 0.2804, 0.3382, 0.9852, + 0.2725, 0.3497, 0.9841, + 0.2647, 0.3612, 0.9830, + 0.2569, 0.3727, 0.9818, + 0.2490, 0.3841, 0.9806, + 0.2412, 0.3955, 0.9794, + 0.2333, 0.4067, 0.9781, + 0.2255, 0.4180, 0.9768, + 0.2176, 0.4291, 0.9755, + 0.2098, 0.4402, 0.9741, + 0.2020, 0.4512, 0.9727, + 0.1941, 0.4622, 0.9713, + 0.1863, 0.4731, 0.9698, + 0.1784, 0.4839, 0.9683, + 0.1706, 0.4947, 0.9667, + 0.1627, 0.5053, 0.9651, + 0.1549, 0.5159, 0.9635, + 0.1471, 0.5264, 0.9618, + 0.1392, 0.5369, 0.9601, + 0.1314, 0.5472, 0.9584, + 0.1235, 0.5575, 0.9566, + 0.1157, 0.5677, 0.9548, + 0.1078, 0.5778, 0.9529, + 0.1000, 0.5878, 0.9511, + 0.0922, 0.5977, 0.9491, + 0.0843, 0.6075, 0.9472, + 0.0765, 0.6173, 0.9452, + 0.0686, 0.6269, 0.9432, + 0.0608, 0.6365, 0.9411, + 0.0529, 0.6459, 0.9390, + 0.0451, 0.6553, 0.9369, + 0.0373, 0.6645, 0.9347, + 0.0294, 0.6737, 0.9325, + 0.0216, 0.6827, 0.9302, + 0.0137, 0.6917, 0.9280, + 0.0059, 0.7005, 0.9256, + 0.0020, 0.7093, 0.9233, + 0.0098, 0.7179, 0.9209, + 0.0176, 0.7264, 0.9185, + 0.0255, 0.7348, 0.9160, + 0.0333, 0.7431, 0.9135, + 0.0412, 0.7513, 0.9110, + 0.0490, 0.7594, 0.9085, + 0.0569, 0.7674, 0.9059, + 0.0647, 0.7752, 0.9032, + 0.0725, 0.7829, 0.9006, + 0.0804, 0.7905, 0.8979, + 0.0882, 0.7980, 0.8952, + 0.0961, 0.8054, 0.8924, + 0.1039, 0.8126, 0.8896, + 0.1118, 0.8197, 0.8868, + 0.1196, 0.8267, 0.8839, + 0.1275, 0.8336, 0.8810, + 0.1353, 0.8403, 0.8781, + 0.1431, 0.8470, 0.8751, + 0.1510, 0.8534, 0.8721, + 0.1588, 0.8598, 0.8691, + 0.1667, 0.8660, 0.8660, + 0.1745, 0.8721, 0.8629, + 0.1824, 0.8781, 0.8598, + 0.1902, 0.8839, 0.8566, + 0.1980, 0.8896, 0.8534, + 0.2059, 0.8952, 0.8502, + 0.2137, 0.9006, 0.8470, + 0.2216, 0.9059, 0.8437, + 0.2294, 0.9110, 0.8403, + 0.2373, 0.9160, 0.8370, + 0.2451, 0.9209, 0.8336, + 0.2529, 0.9256, 0.8302, + 0.2608, 0.9302, 0.8267, + 0.2686, 0.9347, 0.8233, + 0.2765, 0.9390, 0.8197, + 0.2843, 0.9432, 0.8162, + 0.2922, 0.9472, 0.8126, + 0.3000, 0.9511, 0.8090, + 0.3078, 0.9548, 0.8054, + 0.3157, 0.9584, 0.8017, + 0.3235, 0.9618, 0.7980, + 0.3314, 0.9651, 0.7943, + 0.3392, 0.9683, 0.7905, + 0.3471, 0.9713, 0.7867, + 0.3549, 0.9741, 0.7829, + 0.3627, 0.9768, 0.7791, + 0.3706, 0.9794, 0.7752, + 0.3784, 0.9818, 0.7713, + 0.3863, 0.9841, 0.7674, + 0.3941, 0.9862, 0.7634, + 0.4020, 0.9882, 0.7594, + 0.4098, 0.9900, 0.7554, + 0.4176, 0.9916, 0.7513, + 0.4255, 0.9932, 0.7473, + 0.4333, 0.9945, 0.7431, + 0.4412, 0.9957, 0.7390, + 0.4490, 0.9968, 0.7348, + 0.4569, 0.9977, 0.7307, + 0.4647, 0.9985, 0.7264, + 0.4725, 0.9991, 0.7222, + 0.4804, 0.9995, 0.7179, + 0.4882, 0.9998, 0.7136, + 0.4961, 1.0000, 0.7093, + 0.5039, 1.0000, 0.7049, + 0.5118, 0.9998, 0.7005, + 0.5196, 0.9995, 0.6961, + 0.5275, 0.9991, 0.6917, + 0.5353, 0.9985, 0.6872, + 0.5431, 0.9977, 0.6827, + 0.5510, 0.9968, 0.6782, + 0.5588, 0.9957, 0.6737, + 0.5667, 0.9945, 0.6691, + 0.5745, 0.9932, 0.6645, + 0.5824, 0.9916, 0.6599, + 0.5902, 0.9900, 0.6553, + 0.5980, 0.9882, 0.6506, + 0.6059, 0.9862, 0.6459, + 0.6137, 0.9841, 0.6412, + 0.6216, 0.9818, 0.6365, + 0.6294, 0.9794, 0.6317, + 0.6373, 0.9768, 0.6269, + 0.6451, 0.9741, 0.6221, + 0.6529, 0.9713, 0.6173, + 0.6608, 0.9683, 0.6124, + 0.6686, 0.9651, 0.6075, + 0.6765, 0.9618, 0.6026, + 0.6843, 0.9584, 0.5977, + 0.6922, 0.9548, 0.5928, + 0.7000, 0.9511, 0.5878, + 0.7078, 0.9472, 0.5828, + 0.7157, 0.9432, 0.5778, + 0.7235, 0.9390, 0.5727, + 0.7314, 0.9347, 0.5677, + 0.7392, 0.9302, 0.5626, + 0.7471, 0.9256, 0.5575, + 0.7549, 0.9209, 0.5524, + 0.7627, 0.9160, 0.5472, + 0.7706, 0.9110, 0.5421, + 0.7784, 0.9059, 0.5369, + 0.7863, 0.9006, 0.5317, + 0.7941, 0.8952, 0.5264, + 0.8020, 0.8896, 0.5212, + 0.8098, 0.8839, 0.5159, + 0.8176, 0.8781, 0.5106, + 0.8255, 0.8721, 0.5053, + 0.8333, 0.8660, 0.5000, + 0.8412, 0.8598, 0.4947, + 0.8490, 0.8534, 0.4893, + 0.8569, 0.8470, 0.4839, + 0.8647, 0.8403, 0.4785, + 0.8725, 0.8336, 0.4731, + 0.8804, 0.8267, 0.4677, + 0.8882, 0.8197, 0.4622, + 0.8961, 0.8126, 0.4567, + 0.9039, 0.8054, 0.4512, + 0.9118, 0.7980, 0.4457, + 0.9196, 0.7905, 0.4402, + 0.9275, 0.7829, 0.4347, + 0.9353, 0.7752, 0.4291, + 0.9431, 0.7674, 0.4235, + 0.9510, 0.7594, 0.4180, + 0.9588, 0.7513, 0.4124, + 0.9667, 0.7431, 0.4067, + 0.9745, 0.7348, 0.4011, + 0.9824, 0.7264, 0.3955, + 0.9902, 0.7179, 0.3898, + 0.9980, 0.7093, 0.3841, + 1.0000, 0.7005, 0.3784, + 1.0000, 0.6917, 0.3727, + 1.0000, 0.6827, 0.3670, + 1.0000, 0.6737, 0.3612, + 1.0000, 0.6645, 0.3555, + 1.0000, 0.6553, 0.3497, + 1.0000, 0.6459, 0.3439, + 1.0000, 0.6365, 0.3382, + 1.0000, 0.6269, 0.3324, + 1.0000, 0.6173, 0.3265, + 1.0000, 0.6075, 0.3207, + 1.0000, 0.5977, 0.3149, + 1.0000, 0.5878, 0.3090, + 1.0000, 0.5778, 0.3032, + 1.0000, 0.5677, 0.2973, + 1.0000, 0.5575, 0.2914, + 1.0000, 0.5472, 0.2855, + 1.0000, 0.5369, 0.2796, + 1.0000, 0.5264, 0.2737, + 1.0000, 0.5159, 0.2677, + 1.0000, 0.5053, 0.2618, + 1.0000, 0.4947, 0.2558, + 1.0000, 0.4839, 0.2499, + 1.0000, 0.4731, 0.2439, + 1.0000, 0.4622, 0.2379, + 1.0000, 0.4512, 0.2319, + 1.0000, 0.4402, 0.2260, + 1.0000, 0.4291, 0.2199, + 1.0000, 0.4180, 0.2139, + 1.0000, 0.4067, 0.2079, + 1.0000, 0.3955, 0.2019, + 1.0000, 0.3841, 0.1958, + 1.0000, 0.3727, 0.1898, + 1.0000, 0.3612, 0.1837, + 1.0000, 0.3497, 0.1777, + 1.0000, 0.3382, 0.1716, + 1.0000, 0.3265, 0.1656, + 1.0000, 0.3149, 0.1595, + 1.0000, 0.3032, 0.1534, + 1.0000, 0.2914, 0.1473, + 1.0000, 0.2796, 0.1412, + 1.0000, 0.2677, 0.1351, + 1.0000, 0.2558, 0.1290, + 1.0000, 0.2439, 0.1229, + 1.0000, 0.2319, 0.1168, + 1.0000, 0.2199, 0.1107, + 1.0000, 0.2079, 0.1045, + 1.0000, 0.1958, 0.0984, + 1.0000, 0.1837, 0.0923, + 1.0000, 0.1716, 0.0861, + 1.0000, 0.1595, 0.0800, + 1.0000, 0.1473, 0.0739, + 1.0000, 0.1351, 0.0677, + 1.0000, 0.1229, 0.0616, + 1.0000, 0.1107, 0.0554, + 1.0000, 0.0984, 0.0493, + 1.0000, 0.0861, 0.0431, + 1.0000, 0.0739, 0.0370, + 1.0000, 0.0616, 0.0308, + 1.0000, 0.0493, 0.0246, + 1.0000, 0.0370, 0.0185, + 1.0000, 0.0246, 0.0123, + 1.0000, 0.0123, 0.0062, + 1.0000, 0.0000, 0.0000, +}; + +const float ColorMap::m_prism[m_size] = +{ + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.1296, 0.0000, + 1.0000, 0.3203, 0.0000, + 1.0000, 0.5116, 0.0000, + 1.0000, 0.6909, 0.0000, + 1.0000, 0.8464, 0.0000, + 1.0000, 0.9679, 0.0000, + 0.8890, 1.0000, 0.0000, + 0.6991, 1.0000, 0.0000, + 0.5073, 1.0000, 0.0000, + 0.3262, 0.9966, 0.0000, + 0.1678, 0.8870, 0.0000, + 0.0425, 0.7408, 0.2247, + 0.0000, 0.5675, 0.4915, + 0.0000, 0.3785, 0.7259, + 0.0000, 0.1863, 0.9124, + 0.0000, 0.0036, 1.0000, + 0.1001, 0.0000, 1.0000, + 0.2431, 0.0000, 1.0000, + 0.4142, 0.0000, 0.9965, + 0.6022, 0.0000, 0.8450, + 0.7946, 0.0000, 0.6378, + 0.9788, 0.0000, 0.3885, + 1.0000, 0.0000, 0.1136, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0561, 0.0000, + 1.0000, 0.2429, 0.0000, + 1.0000, 0.4354, 0.0000, + 1.0000, 0.6211, 0.0000, + 1.0000, 0.7875, 0.0000, + 1.0000, 0.9237, 0.0000, + 0.9619, 1.0000, 0.0000, + 0.7764, 1.0000, 0.0000, + 0.5838, 1.0000, 0.0000, + 0.3969, 1.0000, 0.0000, + 0.2280, 0.9359, 0.0000, + 0.0883, 0.8034, 0.1123, + 0.0000, 0.6397, 0.3872, + 0.0000, 0.4555, 0.6367, + 0.0000, 0.2631, 0.8441, + 0.0000, 0.0751, 0.9959, + 0.0528, 0.0000, 1.0000, + 0.1817, 0.0000, 1.0000, + 0.3427, 0.0000, 1.0000, + 0.5254, 0.0000, 0.9132, + 0.7176, 0.0000, 0.7269, + 0.9066, 0.0000, 0.4927, + 1.0000, 0.0000, 0.2261, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.1664, 0.0000, + 1.0000, 0.3582, 0.0000, + 1.0000, 0.5481, 0.0000, + 1.0000, 0.7236, 0.0000, + 1.0000, 0.8732, 0.0000, + 1.0000, 0.9870, 0.0000, + 0.8525, 1.0000, 0.0000, + 0.6612, 1.0000, 0.0000, + 0.4705, 1.0000, 0.0000, + 0.2930, 0.9784, 0.0000, + 0.1403, 0.8610, 0.0000, + 0.0226, 0.7086, 0.2788, + 0.0000, 0.5312, 0.5406, + 0.0000, 0.3406, 0.7667, + 0.0000, 0.1493, 0.9423, + 0.0121, 0.0000, 1.0000, + 0.1255, 0.0000, 1.0000, + 0.2748, 0.0000, 1.0000, + 0.4501, 0.0000, 0.9717, + 0.6400, 0.0000, 0.8083, + 0.8318, 0.0000, 0.5917, + 1.0000, 0.0000, 0.3360, + 1.0000, 0.0000, 0.0582, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0917, 0.0000, + 1.0000, 0.2806, 0.0000, + 1.0000, 0.4728, 0.0000, + 1.0000, 0.6556, 0.0000, + 1.0000, 0.8169, 0.0000, + 1.0000, 0.9461, 0.0000, + 0.9267, 1.0000, 0.0000, + 0.7387, 1.0000, 0.0000, + 0.5463, 1.0000, 0.0000, + 0.3620, 1.0000, 0.0000, + 0.1980, 0.9129, 0.0000, + 0.0651, 0.7735, 0.1674, + 0.0000, 0.6048, 0.4387, + 0.0000, 0.4180, 0.6811, + 0.0000, 0.2255, 0.8786, + 0.0000, 0.0398, 1.0000, + 0.0751, 0.0000, 1.0000, + 0.2111, 0.0000, 1.0000, + 0.3772, 0.0000, 1.0000, + 0.5627, 0.0000, 0.8811, + 0.7553, 0.0000, 0.6843, + 0.9422, 0.0000, 0.4425, + 1.0000, 0.0000, 0.1714, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0195, 0.0000, + 1.0000, 0.2036, 0.0000, + 1.0000, 0.3960, 0.0000, + 1.0000, 0.5841, 0.0000, + 1.0000, 0.7554, 0.0000, + 1.0000, 0.8987, 0.0000, + 0.9981, 1.0000, 0.0000, + 0.8155, 1.0000, 0.0000, + 0.6234, 1.0000, 0.0000, + 0.4343, 1.0000, 0.0000, + 0.2607, 0.9585, 0.0000, + 0.1142, 0.8336, 0.0542, + 0.0043, 0.6754, 0.3322, + 0.0000, 0.4945, 0.5883, + 0.0000, 0.3027, 0.8056, + 0.0000, 0.1128, 0.9698, + 0.0311, 0.0000, 1.0000, + 0.1522, 0.0000, 1.0000, + 0.3075, 0.0000, 1.0000, + 0.4866, 0.0000, 0.9444, + 0.6779, 0.0000, 0.7696, + 0.8686, 0.0000, 0.5441, + 1.0000, 0.0000, 0.2827, + 1.0000, 0.0000, 0.0027, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.1279, 0.0000, + 1.0000, 0.3185, 0.0000, + 1.0000, 0.5098, 0.0000, + 1.0000, 0.6893, 0.0000, + 1.0000, 0.8451, 0.0000, + 1.0000, 0.9669, 0.0000, + 0.8908, 1.0000, 0.0000, + 0.7009, 1.0000, 0.0000, + 0.5091, 1.0000, 0.0000, + 0.3279, 0.9974, 0.0000, + 0.1692, 0.8883, 0.0000, + 0.0435, 0.7423, 0.2221, + 0.0000, 0.5692, 0.4891, + 0.0000, 0.3803, 0.7239, + 0.0000, 0.1881, 0.9109, + 0.0000, 0.0052, 1.0000, + 0.0989, 0.0000, 1.0000, + 0.2416, 0.0000, 1.0000, + 0.4125, 0.0000, 0.9976, + 0.6003, 0.0000, 0.8467, + 0.7928, 0.0000, 0.6400, + 0.9772, 0.0000, 0.3910, + 1.0000, 0.0000, 0.1163, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0543, 0.0000, + 1.0000, 0.2410, 0.0000, + 1.0000, 0.4336, 0.0000, + 1.0000, 0.6194, 0.0000, + 1.0000, 0.7860, 0.0000, + 1.0000, 0.9226, 0.0000, + 0.9636, 1.0000, 0.0000, + 0.7782, 1.0000, 0.0000, + 0.5856, 1.0000, 0.0000, + 0.3986, 1.0000, 0.0000, + 0.2295, 0.9370, 0.0000, + 0.0895, 0.8049, 0.1096, + 0.0000, 0.6414, 0.3847, + 0.0000, 0.4574, 0.6344, + 0.0000, 0.2649, 0.8424, + 0.0000, 0.0768, 0.9947, + 0.0518, 0.0000, 1.0000, + 0.1803, 0.0000, 1.0000, + 0.3411, 0.0000, 1.0000, + 0.5236, 0.0000, 0.9147, + 0.7157, 0.0000, 0.7290, + 0.9048, 0.0000, 0.4952, + 1.0000, 0.0000, 0.2287, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.1646, 0.0000, + 1.0000, 0.3563, 0.0000, + 1.0000, 0.5463, 0.0000, + 1.0000, 0.7221, 0.0000, + 1.0000, 0.8720, 0.0000, + 1.0000, 0.9861, 0.0000, + 0.8543, 1.0000, 0.0000, + 0.6631, 1.0000, 0.0000, + 0.4723, 1.0000, 0.0000, + 0.2946, 0.9793, 0.0000, + 0.1416, 0.8623, 0.0000, + 0.0235, 0.7102, 0.2762, + 0.0000, 0.5330, 0.5382, + 0.0000, 0.3425, 0.7648, + 0.0000, 0.1511, 0.9409, + 0.0112, 0.0000, 1.0000, + 0.1242, 0.0000, 1.0000, + 0.2732, 0.0000, 1.0000, + 0.4484, 0.0000, 0.9729, + 0.6381, 0.0000, 0.8102, + 0.8300, 0.0000, 0.5940, + 1.0000, 0.0000, 0.3386, + 1.0000, 0.0000, 0.0610, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0899, 0.0000, + 1.0000, 0.2788, 0.0000, + 1.0000, 0.4710, 0.0000, + 1.0000, 0.6539, 0.0000, + 1.0000, 0.8155, 0.0000, + 1.0000, 0.9451, 0.0000, + 0.9284, 1.0000, 0.0000, + 0.7406, 1.0000, 0.0000, + 0.5481, 1.0000, 0.0000, + 0.3637, 1.0000, 0.0000, + 0.1994, 0.9140, 0.0000, + 0.0662, 0.7749, 0.1647, + 0.0000, 0.6065, 0.4362, + 0.0000, 0.4199, 0.6790, + 0.0000, 0.2273, 0.8770, + 0.0000, 0.0415, 1.0000, + 0.0740, 0.0000, 1.0000, + 0.2096, 0.0000, 1.0000, + 0.3755, 0.0000, 1.0000, + 0.5609, 0.0000, 0.8827, + 0.7534, 0.0000, 0.6864, + 0.9405, 0.0000, 0.4449, + 1.0000, 0.0000, 0.1741, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.0178, 0.0000, + 1.0000, 0.2017, 0.0000, + 1.0000, 0.3941, 0.0000, + 1.0000, 0.5823, 0.0000, + 1.0000, 0.7539, 0.0000, + 1.0000, 0.8974, 0.0000, + 0.9997, 1.0000, 0.0000, + 0.8173, 1.0000, 0.0000, + 0.6252, 1.0000, 0.0000, + 0.4360, 1.0000, 0.0000, + 0.2623, 0.9595, 0.0000, + 0.1154, 0.8349, 0.0515, + 0.0051, 0.6771, 0.3296, + 0.0000, 0.4963, 0.5860, + 0.0000, 0.3046, 0.8037, + 0.0000, 0.1146, 0.9685, + 0.0302, 0.0000, 1.0000, + 0.1509, 0.0000, 1.0000, + 0.3059, 0.0000, 1.0000, + 0.4848, 0.0000, 0.9458, + 0.6760, 0.0000, 0.7716, + 0.8668, 0.0000, 0.5465, + 1.0000, 0.0000, 0.2854, + 1.0000, 0.0000, 0.0054, + 1.0000, 0.0000, 0.0000, + 1.0000, 0.1261, 0.0000, + 1.0000, 0.3166, 0.0000, + 1.0000, 0.5080, 0.0000, + 1.0000, 0.6877, 0.0000, + 1.0000, 0.8437, 0.0000, + 1.0000, 0.9660, 0.0000, + 0.8925, 1.0000, 0.0000, + 0.7028, 1.0000, 0.0000, + 0.5109, 1.0000, 0.0000, + 0.3295, 0.9983, 0.0000, +}; + +const float ColorMap::m_viridis[m_size] = +{ + 0.2670, 0.0049, 0.3294, + 0.2685, 0.0096, 0.3354, + 0.2699, 0.0146, 0.3414, + 0.2713, 0.0199, 0.3473, + 0.2726, 0.0256, 0.3531, + 0.2738, 0.0315, 0.3589, + 0.2750, 0.0378, 0.3645, + 0.2760, 0.0442, 0.3702, + 0.2770, 0.0503, 0.3757, + 0.2779, 0.0563, 0.3812, + 0.2788, 0.0621, 0.3866, + 0.2796, 0.0678, 0.3919, + 0.2803, 0.0734, 0.3972, + 0.2809, 0.0789, 0.4023, + 0.2814, 0.0843, 0.4074, + 0.2819, 0.0897, 0.4124, + 0.2823, 0.0950, 0.4173, + 0.2827, 0.1002, 0.4222, + 0.2829, 0.1054, 0.4269, + 0.2831, 0.1106, 0.4316, + 0.2832, 0.1157, 0.4361, + 0.2832, 0.1208, 0.4406, + 0.2832, 0.1258, 0.4450, + 0.2831, 0.1309, 0.4492, + 0.2829, 0.1359, 0.4534, + 0.2826, 0.1409, 0.4575, + 0.2823, 0.1459, 0.4615, + 0.2819, 0.1509, 0.4654, + 0.2814, 0.1558, 0.4692, + 0.2809, 0.1608, 0.4729, + 0.2803, 0.1657, 0.4765, + 0.2796, 0.1706, 0.4800, + 0.2788, 0.1755, 0.4834, + 0.2780, 0.1804, 0.4867, + 0.2771, 0.1852, 0.4899, + 0.2762, 0.1901, 0.4930, + 0.2752, 0.1949, 0.4960, + 0.2741, 0.1997, 0.4989, + 0.2730, 0.2045, 0.5017, + 0.2718, 0.2093, 0.5044, + 0.2706, 0.2141, 0.5071, + 0.2693, 0.2188, 0.5096, + 0.2680, 0.2235, 0.5120, + 0.2666, 0.2283, 0.5143, + 0.2651, 0.2330, 0.5166, + 0.2637, 0.2376, 0.5188, + 0.2621, 0.2423, 0.5208, + 0.2606, 0.2469, 0.5228, + 0.2590, 0.2515, 0.5247, + 0.2573, 0.2561, 0.5266, + 0.2556, 0.2607, 0.5283, + 0.2539, 0.2653, 0.5300, + 0.2522, 0.2698, 0.5316, + 0.2504, 0.2743, 0.5331, + 0.2486, 0.2788, 0.5346, + 0.2468, 0.2832, 0.5359, + 0.2450, 0.2877, 0.5373, + 0.2431, 0.2921, 0.5385, + 0.2412, 0.2965, 0.5397, + 0.2393, 0.3009, 0.5408, + 0.2374, 0.3052, 0.5419, + 0.2355, 0.3095, 0.5429, + 0.2336, 0.3138, 0.5439, + 0.2317, 0.3181, 0.5448, + 0.2297, 0.3224, 0.5457, + 0.2278, 0.3266, 0.5465, + 0.2259, 0.3308, 0.5473, + 0.2239, 0.3350, 0.5481, + 0.2220, 0.3392, 0.5488, + 0.2201, 0.3433, 0.5494, + 0.2181, 0.3474, 0.5500, + 0.2162, 0.3515, 0.5506, + 0.2143, 0.3556, 0.5512, + 0.2124, 0.3597, 0.5517, + 0.2105, 0.3637, 0.5522, + 0.2086, 0.3678, 0.5527, + 0.2068, 0.3718, 0.5531, + 0.2049, 0.3757, 0.5535, + 0.2031, 0.3797, 0.5539, + 0.2012, 0.3837, 0.5543, + 0.1994, 0.3876, 0.5546, + 0.1976, 0.3915, 0.5550, + 0.1959, 0.3954, 0.5553, + 0.1941, 0.3993, 0.5556, + 0.1924, 0.4032, 0.5558, + 0.1906, 0.4071, 0.5561, + 0.1889, 0.4109, 0.5563, + 0.1872, 0.4147, 0.5565, + 0.1856, 0.4186, 0.5568, + 0.1839, 0.4224, 0.5569, + 0.1823, 0.4262, 0.5571, + 0.1806, 0.4300, 0.5573, + 0.1790, 0.4338, 0.5574, + 0.1774, 0.4375, 0.5576, + 0.1758, 0.4413, 0.5577, + 0.1743, 0.4450, 0.5578, + 0.1727, 0.4488, 0.5579, + 0.1712, 0.4525, 0.5580, + 0.1696, 0.4563, 0.5580, + 0.1681, 0.4600, 0.5581, + 0.1666, 0.4637, 0.5581, + 0.1651, 0.4674, 0.5581, + 0.1636, 0.4711, 0.5581, + 0.1621, 0.4748, 0.5581, + 0.1607, 0.4785, 0.5581, + 0.1592, 0.4822, 0.5581, + 0.1577, 0.4859, 0.5580, + 0.1563, 0.4896, 0.5579, + 0.1548, 0.4933, 0.5578, + 0.1534, 0.4970, 0.5577, + 0.1519, 0.5007, 0.5576, + 0.1505, 0.5044, 0.5574, + 0.1490, 0.5081, 0.5573, + 0.1476, 0.5117, 0.5570, + 0.1462, 0.5154, 0.5568, + 0.1448, 0.5191, 0.5566, + 0.1433, 0.5228, 0.5563, + 0.1419, 0.5265, 0.5560, + 0.1405, 0.5301, 0.5557, + 0.1391, 0.5338, 0.5553, + 0.1378, 0.5375, 0.5549, + 0.1364, 0.5412, 0.5545, + 0.1351, 0.5449, 0.5540, + 0.1337, 0.5485, 0.5535, + 0.1324, 0.5522, 0.5530, + 0.1312, 0.5559, 0.5525, + 0.1299, 0.5596, 0.5519, + 0.1287, 0.5633, 0.5512, + 0.1276, 0.5669, 0.5506, + 0.1265, 0.5706, 0.5498, + 0.1254, 0.5743, 0.5491, + 0.1244, 0.5780, 0.5483, + 0.1235, 0.5817, 0.5474, + 0.1226, 0.5854, 0.5466, + 0.1218, 0.5891, 0.5456, + 0.1211, 0.5927, 0.5446, + 0.1206, 0.5964, 0.5436, + 0.1201, 0.6001, 0.5425, + 0.1197, 0.6038, 0.5414, + 0.1195, 0.6075, 0.5402, + 0.1194, 0.6111, 0.5390, + 0.1195, 0.6148, 0.5377, + 0.1197, 0.6185, 0.5363, + 0.1201, 0.6222, 0.5349, + 0.1206, 0.6258, 0.5335, + 0.1214, 0.6295, 0.5320, + 0.1223, 0.6332, 0.5304, + 0.1234, 0.6368, 0.5288, + 0.1248, 0.6405, 0.5271, + 0.1263, 0.6441, 0.5253, + 0.1281, 0.6477, 0.5235, + 0.1301, 0.6514, 0.5216, + 0.1323, 0.6550, 0.5197, + 0.1347, 0.6586, 0.5176, + 0.1373, 0.6623, 0.5156, + 0.1402, 0.6659, 0.5134, + 0.1433, 0.6695, 0.5112, + 0.1466, 0.6731, 0.5089, + 0.1501, 0.6766, 0.5066, + 0.1539, 0.6802, 0.5042, + 0.1579, 0.6838, 0.5017, + 0.1620, 0.6873, 0.4991, + 0.1664, 0.6909, 0.4965, + 0.1709, 0.6944, 0.4938, + 0.1757, 0.6979, 0.4910, + 0.1807, 0.7014, 0.4882, + 0.1858, 0.7049, 0.4853, + 0.1911, 0.7084, 0.4823, + 0.1966, 0.7118, 0.4792, + 0.2022, 0.7153, 0.4761, + 0.2080, 0.7187, 0.4729, + 0.2140, 0.7221, 0.4696, + 0.2201, 0.7255, 0.4662, + 0.2264, 0.7289, 0.4628, + 0.2328, 0.7322, 0.4593, + 0.2394, 0.7356, 0.4557, + 0.2461, 0.7389, 0.4520, + 0.2529, 0.7422, 0.4483, + 0.2599, 0.7455, 0.4445, + 0.2669, 0.7488, 0.4406, + 0.2741, 0.7520, 0.4366, + 0.2815, 0.7552, 0.4326, + 0.2889, 0.7584, 0.4284, + 0.2965, 0.7616, 0.4242, + 0.3041, 0.7647, 0.4199, + 0.3119, 0.7678, 0.4156, + 0.3198, 0.7709, 0.4112, + 0.3278, 0.7740, 0.4066, + 0.3359, 0.7770, 0.4020, + 0.3441, 0.7800, 0.3974, + 0.3524, 0.7830, 0.3926, + 0.3607, 0.7860, 0.3878, + 0.3692, 0.7889, 0.3829, + 0.3778, 0.7918, 0.3779, + 0.3864, 0.7946, 0.3729, + 0.3952, 0.7975, 0.3678, + 0.4040, 0.8003, 0.3626, + 0.4129, 0.8030, 0.3573, + 0.4219, 0.8058, 0.3519, + 0.4310, 0.8085, 0.3465, + 0.4401, 0.8111, 0.3410, + 0.4494, 0.8138, 0.3354, + 0.4587, 0.8164, 0.3297, + 0.4681, 0.8189, 0.3240, + 0.4775, 0.8214, 0.3182, + 0.4870, 0.8239, 0.3123, + 0.4966, 0.8264, 0.3064, + 0.5063, 0.8288, 0.3004, + 0.5160, 0.8312, 0.2943, + 0.5258, 0.8335, 0.2881, + 0.5356, 0.8358, 0.2819, + 0.5455, 0.8380, 0.2756, + 0.5555, 0.8403, 0.2693, + 0.5655, 0.8424, 0.2629, + 0.5756, 0.8446, 0.2564, + 0.5857, 0.8467, 0.2499, + 0.5958, 0.8487, 0.2433, + 0.6060, 0.8507, 0.2367, + 0.6163, 0.8527, 0.2301, + 0.6266, 0.8546, 0.2234, + 0.6369, 0.8565, 0.2166, + 0.6473, 0.8584, 0.2099, + 0.6576, 0.8602, 0.2031, + 0.6681, 0.8620, 0.1963, + 0.6785, 0.8637, 0.1895, + 0.6889, 0.8654, 0.1827, + 0.6994, 0.8671, 0.1760, + 0.7099, 0.8688, 0.1693, + 0.7204, 0.8703, 0.1626, + 0.7309, 0.8719, 0.1560, + 0.7414, 0.8734, 0.1496, + 0.7519, 0.8750, 0.1432, + 0.7624, 0.8764, 0.1371, + 0.7729, 0.8779, 0.1311, + 0.7833, 0.8793, 0.1254, + 0.7938, 0.8807, 0.1200, + 0.8042, 0.8820, 0.1150, + 0.8146, 0.8834, 0.1103, + 0.8249, 0.8847, 0.1062, + 0.8353, 0.8860, 0.1026, + 0.8456, 0.8873, 0.0997, + 0.8558, 0.8886, 0.0975, + 0.8660, 0.8899, 0.0960, + 0.8762, 0.8911, 0.0953, + 0.8863, 0.8924, 0.0954, + 0.8963, 0.8936, 0.0963, + 0.9063, 0.8949, 0.0981, + 0.9162, 0.8961, 0.1007, + 0.9261, 0.8973, 0.1041, + 0.9359, 0.8986, 0.1081, + 0.9456, 0.8998, 0.1128, + 0.9553, 0.9011, 0.1181, + 0.9649, 0.9023, 0.1239, + 0.9744, 0.9036, 0.1302, + 0.9839, 0.9049, 0.1369, + 0.9932, 0.9062, 0.1439, +}; + +const float ColorMap::m_loggray[m_size] = +{ + 0.0000, 0.0000, 0.0000, + 0.0151, 0.0151, 0.0151, + 0.0296, 0.0296, 0.0296, + 0.0437, 0.0437, 0.0437, + 0.0574, 0.0574, 0.0574, + 0.0706, 0.0706, 0.0706, + 0.0834, 0.0834, 0.0834, + 0.0959, 0.0959, 0.0959, + 0.1080, 0.1080, 0.1080, + 0.1198, 0.1198, 0.1198, + 0.1313, 0.1313, 0.1313, + 0.1425, 0.1425, 0.1425, + 0.1534, 0.1534, 0.1534, + 0.1640, 0.1640, 0.1640, + 0.1744, 0.1744, 0.1744, + 0.1845, 0.1845, 0.1845, + 0.1944, 0.1944, 0.1944, + 0.2041, 0.2041, 0.2041, + 0.2136, 0.2136, 0.2136, + 0.2229, 0.2229, 0.2229, + 0.2319, 0.2319, 0.2319, + 0.2408, 0.2408, 0.2408, + 0.2496, 0.2496, 0.2496, + 0.2581, 0.2581, 0.2581, + 0.2665, 0.2665, 0.2665, + 0.2747, 0.2747, 0.2747, + 0.2828, 0.2828, 0.2828, + 0.2907, 0.2907, 0.2907, + 0.2985, 0.2985, 0.2985, + 0.3061, 0.3061, 0.3061, + 0.3136, 0.3136, 0.3136, + 0.3210, 0.3210, 0.3210, + 0.3283, 0.3283, 0.3283, + 0.3354, 0.3354, 0.3354, + 0.3424, 0.3424, 0.3424, + 0.3493, 0.3493, 0.3493, + 0.3561, 0.3561, 0.3561, + 0.3628, 0.3628, 0.3628, + 0.3694, 0.3694, 0.3694, + 0.3759, 0.3759, 0.3759, + 0.3823, 0.3823, 0.3823, + 0.3886, 0.3886, 0.3886, + 0.3949, 0.3949, 0.3949, + 0.4010, 0.4010, 0.4010, + 0.4070, 0.4070, 0.4070, + 0.4130, 0.4130, 0.4130, + 0.4189, 0.4189, 0.4189, + 0.4247, 0.4247, 0.4247, + 0.4304, 0.4304, 0.4304, + 0.4361, 0.4361, 0.4361, + 0.4416, 0.4416, 0.4416, + 0.4472, 0.4472, 0.4472, + 0.4526, 0.4526, 0.4526, + 0.4580, 0.4580, 0.4580, + 0.4633, 0.4633, 0.4633, + 0.4685, 0.4685, 0.4685, + 0.4737, 0.4737, 0.4737, + 0.4788, 0.4788, 0.4788, + 0.4839, 0.4839, 0.4839, + 0.4889, 0.4889, 0.4889, + 0.4938, 0.4938, 0.4938, + 0.4987, 0.4987, 0.4987, + 0.5036, 0.5036, 0.5036, + 0.5083, 0.5083, 0.5083, + 0.5131, 0.5131, 0.5131, + 0.5177, 0.5177, 0.5177, + 0.5224, 0.5224, 0.5224, + 0.5269, 0.5269, 0.5269, + 0.5315, 0.5315, 0.5315, + 0.5360, 0.5360, 0.5360, + 0.5404, 0.5404, 0.5404, + 0.5448, 0.5448, 0.5448, + 0.5491, 0.5491, 0.5491, + 0.5535, 0.5535, 0.5535, + 0.5577, 0.5577, 0.5577, + 0.5619, 0.5619, 0.5619, + 0.5661, 0.5661, 0.5661, + 0.5703, 0.5703, 0.5703, + 0.5744, 0.5744, 0.5744, + 0.5784, 0.5784, 0.5784, + 0.5825, 0.5825, 0.5825, + 0.5865, 0.5865, 0.5865, + 0.5904, 0.5904, 0.5904, + 0.5943, 0.5943, 0.5943, + 0.5982, 0.5982, 0.5982, + 0.6021, 0.6021, 0.6021, + 0.6059, 0.6059, 0.6059, + 0.6097, 0.6097, 0.6097, + 0.6134, 0.6134, 0.6134, + 0.6171, 0.6171, 0.6171, + 0.6208, 0.6208, 0.6208, + 0.6245, 0.6245, 0.6245, + 0.6281, 0.6281, 0.6281, + 0.6317, 0.6317, 0.6317, + 0.6352, 0.6352, 0.6352, + 0.6388, 0.6388, 0.6388, + 0.6423, 0.6423, 0.6423, + 0.6458, 0.6458, 0.6458, + 0.6492, 0.6492, 0.6492, + 0.6526, 0.6526, 0.6526, + 0.6560, 0.6560, 0.6560, + 0.6594, 0.6594, 0.6594, + 0.6628, 0.6628, 0.6628, + 0.6661, 0.6661, 0.6661, + 0.6694, 0.6694, 0.6694, + 0.6726, 0.6726, 0.6726, + 0.6759, 0.6759, 0.6759, + 0.6791, 0.6791, 0.6791, + 0.6823, 0.6823, 0.6823, + 0.6855, 0.6855, 0.6855, + 0.6886, 0.6886, 0.6886, + 0.6918, 0.6918, 0.6918, + 0.6949, 0.6949, 0.6949, + 0.6979, 0.6979, 0.6979, + 0.7010, 0.7010, 0.7010, + 0.7040, 0.7040, 0.7040, + 0.7071, 0.7071, 0.7071, + 0.7101, 0.7101, 0.7101, + 0.7130, 0.7130, 0.7130, + 0.7160, 0.7160, 0.7160, + 0.7189, 0.7189, 0.7189, + 0.7219, 0.7219, 0.7219, + 0.7248, 0.7248, 0.7248, + 0.7276, 0.7276, 0.7276, + 0.7305, 0.7305, 0.7305, + 0.7333, 0.7333, 0.7333, + 0.7362, 0.7362, 0.7362, + 0.7390, 0.7390, 0.7390, + 0.7418, 0.7418, 0.7418, + 0.7445, 0.7445, 0.7445, + 0.7473, 0.7473, 0.7473, + 0.7500, 0.7500, 0.7500, + 0.7527, 0.7527, 0.7527, + 0.7554, 0.7554, 0.7554, + 0.7581, 0.7581, 0.7581, + 0.7608, 0.7608, 0.7608, + 0.7634, 0.7634, 0.7634, + 0.7661, 0.7661, 0.7661, + 0.7687, 0.7687, 0.7687, + 0.7713, 0.7713, 0.7713, + 0.7739, 0.7739, 0.7739, + 0.7764, 0.7764, 0.7764, + 0.7790, 0.7790, 0.7790, + 0.7815, 0.7815, 0.7815, + 0.7841, 0.7841, 0.7841, + 0.7866, 0.7866, 0.7866, + 0.7891, 0.7891, 0.7891, + 0.7916, 0.7916, 0.7916, + 0.7940, 0.7940, 0.7940, + 0.7965, 0.7965, 0.7965, + 0.7989, 0.7989, 0.7989, + 0.8014, 0.8014, 0.8014, + 0.8038, 0.8038, 0.8038, + 0.8062, 0.8062, 0.8062, + 0.8086, 0.8086, 0.8086, + 0.8109, 0.8109, 0.8109, + 0.8133, 0.8133, 0.8133, + 0.8157, 0.8157, 0.8157, + 0.8180, 0.8180, 0.8180, + 0.8203, 0.8203, 0.8203, + 0.8226, 0.8226, 0.8226, + 0.8249, 0.8249, 0.8249, + 0.8272, 0.8272, 0.8272, + 0.8295, 0.8295, 0.8295, + 0.8318, 0.8318, 0.8318, + 0.8340, 0.8340, 0.8340, + 0.8362, 0.8362, 0.8362, + 0.8385, 0.8385, 0.8385, + 0.8407, 0.8407, 0.8407, + 0.8429, 0.8429, 0.8429, + 0.8451, 0.8451, 0.8451, + 0.8473, 0.8473, 0.8473, + 0.8495, 0.8495, 0.8495, + 0.8516, 0.8516, 0.8516, + 0.8538, 0.8538, 0.8538, + 0.8559, 0.8559, 0.8559, + 0.8580, 0.8580, 0.8580, + 0.8602, 0.8602, 0.8602, + 0.8623, 0.8623, 0.8623, + 0.8644, 0.8644, 0.8644, + 0.8665, 0.8665, 0.8665, + 0.8685, 0.8685, 0.8685, + 0.8706, 0.8706, 0.8706, + 0.8727, 0.8727, 0.8727, + 0.8747, 0.8747, 0.8747, + 0.8768, 0.8768, 0.8768, + 0.8788, 0.8788, 0.8788, + 0.8808, 0.8808, 0.8808, + 0.8828, 0.8828, 0.8828, + 0.8848, 0.8848, 0.8848, + 0.8868, 0.8868, 0.8868, + 0.8888, 0.8888, 0.8888, + 0.8908, 0.8908, 0.8908, + 0.8927, 0.8927, 0.8927, + 0.8947, 0.8947, 0.8947, + 0.8967, 0.8967, 0.8967, + 0.8986, 0.8986, 0.8986, + 0.9005, 0.9005, 0.9005, + 0.9025, 0.9025, 0.9025, + 0.9044, 0.9044, 0.9044, + 0.9063, 0.9063, 0.9063, + 0.9082, 0.9082, 0.9082, + 0.9101, 0.9101, 0.9101, + 0.9119, 0.9119, 0.9119, + 0.9138, 0.9138, 0.9138, + 0.9157, 0.9157, 0.9157, + 0.9175, 0.9175, 0.9175, + 0.9194, 0.9194, 0.9194, + 0.9212, 0.9212, 0.9212, + 0.9231, 0.9231, 0.9231, + 0.9249, 0.9249, 0.9249, + 0.9267, 0.9267, 0.9267, + 0.9285, 0.9285, 0.9285, + 0.9303, 0.9303, 0.9303, + 0.9321, 0.9321, 0.9321, + 0.9339, 0.9339, 0.9339, + 0.9357, 0.9357, 0.9357, + 0.9375, 0.9375, 0.9375, + 0.9392, 0.9392, 0.9392, + 0.9410, 0.9410, 0.9410, + 0.9427, 0.9427, 0.9427, + 0.9445, 0.9445, 0.9445, + 0.9462, 0.9462, 0.9462, + 0.9480, 0.9480, 0.9480, + 0.9497, 0.9497, 0.9497, + 0.9514, 0.9514, 0.9514, + 0.9531, 0.9531, 0.9531, + 0.9548, 0.9548, 0.9548, + 0.9565, 0.9565, 0.9565, + 0.9582, 0.9582, 0.9582, + 0.9599, 0.9599, 0.9599, + 0.9616, 0.9616, 0.9616, + 0.9632, 0.9632, 0.9632, + 0.9649, 0.9649, 0.9649, + 0.9666, 0.9666, 0.9666, + 0.9682, 0.9682, 0.9682, + 0.9699, 0.9699, 0.9699, + 0.9715, 0.9715, 0.9715, + 0.9731, 0.9731, 0.9731, + 0.9748, 0.9748, 0.9748, + 0.9764, 0.9764, 0.9764, + 0.9780, 0.9780, 0.9780, + 0.9796, 0.9796, 0.9796, + 0.9812, 0.9812, 0.9812, + 0.9828, 0.9828, 0.9828, + 0.9844, 0.9844, 0.9844, + 0.9860, 0.9860, 0.9860, + 0.9876, 0.9876, 0.9876, + 0.9891, 0.9891, 0.9891, + 0.9907, 0.9907, 0.9907, + 0.9923, 0.9923, 0.9923, + 0.9938, 0.9938, 0.9938, + 0.9954, 0.9954, 0.9954, + 0.9969, 0.9969, 0.9969, + 0.9985, 0.9985, 0.9985, + 1.0000, 1.0000, 1.0000 +}; + +const float ColorMap::m_shrimp[m_size] = +{ + 0.0000, 0.0000, 0.0000, + 0.0151, 0.0151, 0.0151, + 0.0296, 0.0296, 0.0296, + 0.0437, 0.0437, 0.0437, + 0.0574, 0.0574, 0.0574, + 0.0706, 0.0706, 0.0706, + 0.0834, 0.0834, 0.0834, + 0.0959, 0.0959, 0.0959, + 0.1080, 0.1080, 0.1080, + 0.1198, 0.1198, 0.1198, + 0.1313, 0.1313, 0.1313, + 0.1425, 0.1425, 0.1425, + 0.1534, 0.1534, 0.1534, + 0.1640, 0.1640, 0.1640, + 0.1744, 0.1744, 0.1744, + 0.1845, 0.1845, 0.1845, + 0.1944, 0.1944, 0.1944, + 0.2041, 0.2041, 0.2041, + 0.2136, 0.2136, 0.2136, + 0.2229, 0.2229, 0.2229, + 0.2319, 0.2319, 0.2319, + 0.2408, 0.2408, 0.2408, + 0.2496, 0.2496, 0.2496, + 0.2581, 0.2581, 0.2581, + 0.2665, 0.2665, 0.2665, + 0.2747, 0.2747, 0.2747, + 0.2828, 0.2828, 0.2828, + 0.2907, 0.2907, 0.2907, + 0.2985, 0.2985, 0.2985, + 0.3061, 0.3061, 0.3061, + 0.3136, 0.3136, 0.3136, + 0.3210, 0.3210, 0.3210, + 0.3283, 0.3283, 0.3283, + 0.3354, 0.3354, 0.3354, + 0.3424, 0.3424, 0.3424, + 0.3493, 0.3493, 0.3493, + 0.3561, 0.3561, 0.3561, + 0.3628, 0.3628, 0.3628, + 0.3694, 0.3694, 0.3694, + 0.3759, 0.3759, 0.3759, + 0.3823, 0.3823, 0.3823, + 0.3886, 0.3886, 0.3886, + 0.3949, 0.3949, 0.3949, + 0.4010, 0.4010, 0.4010, + 0.4070, 0.4070, 0.4070, + 0.4130, 0.4130, 0.4130, + 0.4189, 0.4189, 0.4189, + 0.4247, 0.4247, 0.4247, + 0.4304, 0.4304, 0.4304, + 0.4361, 0.4361, 0.4361, + 0.4416, 0.4416, 0.4416, + 0.4472, 0.4472, 0.4472, + 0.4526, 0.4526, 0.4526, + 0.4580, 0.4580, 0.4580, + 0.4633, 0.4633, 0.4633, + 0.4685, 0.4685, 0.4685, + 0.4737, 0.4737, 0.4737, + 0.4788, 0.4788, 0.4788, + 0.4839, 0.4839, 0.4839, + 0.4889, 0.4889, 0.4889, + 0.4938, 0.4938, 0.4938, + 0.4987, 0.4987, 0.4987, + 0.5036, 0.5036, 0.5036, + 0.5083, 0.5083, 0.5083, + 0.5131, 0.5131, 0.5131, + 0.5177, 0.5177, 0.5177, + 0.5224, 0.5224, 0.5224, + 0.5269, 0.5269, 0.5269, + 0.5315, 0.5315, 0.5315, + 0.5360, 0.5360, 0.5360, + 0.5404, 0.5404, 0.5404, + 0.5448, 0.5448, 0.5448, + 0.5491, 0.5491, 0.5491, + 0.5535, 0.5535, 0.5535, + 0.5577, 0.5577, 0.5577, + 0.5619, 0.5619, 0.5619, + 0.5661, 0.5661, 0.5661, + 0.5703, 0.5703, 0.5703, + 0.5744, 0.5744, 0.5744, + 0.5784, 0.5784, 0.5784, + 0.5825, 0.5825, 0.5825, + 0.5865, 0.5865, 0.5865, + 0.5904, 0.5904, 0.5904, + 0.5943, 0.5943, 0.5943, + 0.5982, 0.5982, 0.5982, + 0.6021, 0.6021, 0.6021, + 0.6059, 0.6059, 0.6059, + 0.6097, 0.6097, 0.6097, + 0.6134, 0.6134, 0.6134, + 0.6171, 0.6171, 0.6171, + 0.6208, 0.6208, 0.6208, + 0.6245, 0.6245, 0.6245, + 0.6281, 0.6281, 0.6281, + 0.6317, 0.6317, 0.6317, + 0.6352, 0.6352, 0.6352, + 0.6388, 0.6388, 0.6388, + 0.6423, 0.6423, 0.6423, + 0.6458, 0.6458, 0.6458, + 0.6492, 0.6492, 0.6492, + 0.6526, 0.6526, 0.6526, + 0.6560, 0.6560, 0.6560, + 0.6594, 0.6594, 0.6594, + 0.6628, 0.6628, 0.6628, + 0.6661, 0.6661, 0.6661, + 0.6694, 0.6694, 0.6694, + 0.6726, 0.6726, 0.6726, + 0.6759, 0.6759, 0.6759, + 0.6791, 0.6791, 0.6791, + 0.6823, 0.6823, 0.6823, + 0.6855, 0.6855, 0.6855, + 0.6886, 0.6886, 0.6886, + 0.6918, 0.6918, 0.6918, + 0.6949, 0.6949, 0.6949, + 0.6979, 0.6979, 0.6979, + 0.7010, 0.7010, 0.7010, + 0.7040, 0.7040, 0.7040, + 0.7071, 0.7071, 0.7071, + 0.7101, 0.7101, 0.7101, + 0.7130, 0.7130, 0.7130, + 0.7160, 0.7160, 0.7160, + 0.7189, 0.7189, 0.7189, + 0.7219, 0.7219, 0.7219, + 0.7248, 0.7248, 0.7248, + 0.7276, 0.7276, 0.7276, + 0.7305, 0.7305, 0.7305, + 0.7333, 0.7333, 0.7333, + 0.7362, 0.7362, 0.7362, + 0.7390, 0.7390, 0.7390, + 0.7418, 0.7418, 0.7418, + 0.7445, 0.7445, 0.7445, + 0.7473, 0.7473, 0.7473, + 0.7500, 0.7500, 0.7500, + 0.7527, 0.7527, 0.7527, + 0.7554, 0.7554, 0.7554, + 0.7581, 0.7581, 0.7581, + 0.7608, 0.7608, 0.7608, + 0.7634, 0.7634, 0.7634, + 0.7661, 0.7661, 0.7661, + 0.7687, 0.7687, 0.7687, + 0.7713, 0.7713, 0.7713, + 0.7739, 0.7739, 0.7739, + 0.7764, 0.7764, 0.7764, + 0.7790, 0.7790, 0.7790, + 0.7815, 0.7815, 0.7815, + 0.7841, 0.7841, 0.7841, + 0.7866, 0.7866, 0.7866, + 0.7891, 0.7891, 0.7891, + 0.7916, 0.7916, 0.7916, + 0.7940, 0.7940, 0.7940, + 0.7965, 0.7965, 0.7965, + 0.7989, 0.7989, 0.7989, + 0.8014, 0.8014, 0.8014, + 0.8038, 0.8038, 0.8038, + 0.8062, 0.8062, 0.8062, + 0.8086, 0.8086, 0.8086, + 0.8109, 0.8109, 0.8109, + 0.8133, 0.8133, 0.8133, + 0.8157, 0.8157, 0.8157, + 0.8180, 0.8180, 0.8180, + 0.8203, 0.8203, 0.8203, + 0.8226, 0.8226, 0.8226, + 0.8249, 0.8249, 0.8249, + 0.8272, 0.8272, 0.8272, + 0.8295, 0.8295, 0.8295, + 0.8318, 0.8318, 0.8318, + 0.8340, 0.8340, 0.8340, + 0.8362, 0.8362, 0.8362, + 0.8385, 0.8385, 0.8385, + 0.8407, 0.8407, 0.8407, + 0.8429, 0.8429, 0.8429, + 0.8451, 0.8451, 0.8451, + 0.8473, 0.8473, 0.8473, + 0.8495, 0.8495, 0.8495, + 0.8516, 0.8516, 0.8516, + 0.8538, 0.8538, 0.8538, + 0.8559, 0.8559, 0.8559, + 0.8580, 0.8580, 0.8580, + 0.8602, 0.8602, 0.8602, + 0.8623, 0.8623, 0.8623, + 0.8644, 0.8644, 0.8644, + 0.8665, 0.8665, 0.8665, + 0.8685, 0.8685, 0.8685, + 0.8706, 0.8706, 0.8706, + 0.8727, 0.8727, 0.8727, + 0.8747, 0.8747, 0.8747, + 0.8768, 0.8768, 0.8768, + 0.8788, 0.8788, 0.8788, + 0.8808, 0.8808, 0.8808, + 0.8828, 0.8828, 0.8828, + 0.8848, 0.8848, 0.8848, + 0.8868, 0.8868, 0.8868, + 0.8888, 0.8888, 0.8888, + 0.8908, 0.8908, 0.8908, + 0.8927, 0.8927, 0.8927, + 0.8947, 0.8947, 0.8947, + 0.8967, 0.8967, 0.8967, + 0.8986, 0.8986, 0.8986, + 0.9005, 0.9005, 0.9005, + 0.9025, 0.9025, 0.9025, + 0.9044, 0.9044, 0.9044, + 0.9063, 0.8944, 0.8884, + 0.9082, 0.8844, 0.8725, + 0.9101, 0.8743, 0.8565, + 0.9119, 0.8643, 0.8405, + 0.9138, 0.8543, 0.8245, + 0.9157, 0.8443, 0.8085, + 0.9175, 0.8342, 0.7925, + 0.9194, 0.8241, 0.7765, + 0.9212, 0.8141, 0.7605, + 0.9231, 0.8040, 0.7445, + 0.9249, 0.7939, 0.7285, + 0.9267, 0.7838, 0.7124, + 0.9285, 0.7738, 0.6964, + 0.9303, 0.7637, 0.6803, + 0.9321, 0.7535, 0.6643, + 0.9339, 0.7434, 0.6482, + 0.9357, 0.7333, 0.6321, + 0.9375, 0.7232, 0.6160, + 0.9392, 0.7130, 0.5999, + 0.9410, 0.7029, 0.5838, + 0.9427, 0.6927, 0.5677, + 0.9445, 0.6826, 0.5516, + 0.9462, 0.6724, 0.5355, + 0.9480, 0.6622, 0.5194, + 0.9497, 0.6521, 0.5032, + 0.9514, 0.6419, 0.4871, + 0.9531, 0.6317, 0.4710, + 0.9548, 0.6215, 0.4548, + 0.9565, 0.6113, 0.4387, + 0.9582, 0.6011, 0.4225, + 0.9599, 0.5908, 0.4063, + 0.9616, 0.5806, 0.3901, + 0.9632, 0.5704, 0.3739, + 0.9649, 0.5601, 0.3578, + 0.9666, 0.5499, 0.3416, + 0.9682, 0.5396, 0.3254, + 0.9699, 0.5294, 0.3091, + 0.9715, 0.5191, 0.2929, + 0.9731, 0.5088, 0.2767, + 0.9748, 0.4986, 0.2605, + 0.9764, 0.4883, 0.2442, + 0.9780, 0.4780, 0.2280, + 0.9796, 0.4677, 0.2117, + 0.9812, 0.4574, 0.1955, + 0.9828, 0.4471, 0.1792, + 0.9844, 0.4368, 0.1630, + 0.9860, 0.4265, 0.1467, + 0.9876, 0.4161, 0.1304, + 0.9891, 0.4058, 0.1141, + 0.9907, 0.3955, 0.0978, + 0.9923, 0.3851, 0.0816, + 0.9938, 0.3748, 0.0653, + 0.9954, 0.3644, 0.0489, + 0.9969, 0.3541, 0.0326, + 0.9985, 0.3437, 0.0163, + 1.0000, 0.3333, 0.0000 +}; + +// See: https://people.phy.cam.ac.uk/dag9/CUBEHELIX/ +const float ColorMap::m_cubehelix[m_size] = +{ + 0.0000, 0.0273, 0.0117, + 0.0000, 0.0352, 0.0195, + 0.0000, 0.0391, 0.0195, + 0.0000, 0.0430, 0.0234, + 0.0000, 0.0508, 0.0273, + 0.0000, 0.0586, 0.0273, + 0.0000, 0.0625, 0.0312, + 0.0000, 0.0703, 0.0352, + 0.0000, 0.0781, 0.0391, + 0.0000, 0.0859, 0.0430, + 0.0000, 0.0938, 0.0430, + 0.0000, 0.0977, 0.0469, + 0.0000, 0.1016, 0.0508, + 0.0000, 0.1055, 0.0547, + 0.0000, 0.1133, 0.0625, + 0.0000, 0.1211, 0.0703, + 0.0000, 0.1250, 0.0781, + 0.0000, 0.1289, 0.0859, + 0.0000, 0.1367, 0.0938, + 0.0000, 0.1406, 0.0977, + 0.0000, 0.1445, 0.1055, + 0.0000, 0.1523, 0.1133, + 0.0000, 0.1523, 0.1172, + 0.0000, 0.1602, 0.1289, + 0.0000, 0.1641, 0.1328, + 0.0000, 0.1719, 0.1445, + 0.0000, 0.1758, 0.1523, + 0.0000, 0.1836, 0.1602, + 0.0000, 0.1875, 0.1680, + 0.0000, 0.1914, 0.1719, + 0.0000, 0.1953, 0.1797, + 0.0000, 0.1992, 0.1875, + 0.0039, 0.2070, 0.2031, + 0.0039, 0.2070, 0.2070, + 0.0078, 0.2109, 0.2188, + 0.0078, 0.2148, 0.2266, + 0.0078, 0.2188, 0.2422, + 0.0117, 0.2227, 0.2500, + 0.0117, 0.2227, 0.2578, + 0.0156, 0.2266, 0.2695, + 0.0156, 0.2266, 0.2734, + 0.0156, 0.2344, 0.2891, + 0.0156, 0.2344, 0.3008, + 0.0195, 0.2383, 0.3086, + 0.0234, 0.2422, 0.3164, + 0.0234, 0.2461, 0.3281, + 0.0273, 0.2500, 0.3398, + 0.0273, 0.2500, 0.3477, + 0.0273, 0.2539, 0.3555, + 0.0312, 0.2539, 0.3672, + 0.0430, 0.2578, 0.3789, + 0.0469, 0.2578, 0.3867, + 0.0547, 0.2578, 0.3984, + 0.0625, 0.2617, 0.4062, + 0.0703, 0.2617, 0.4219, + 0.0742, 0.2656, 0.4297, + 0.0781, 0.2656, 0.4375, + 0.0859, 0.2656, 0.4492, + 0.0898, 0.2656, 0.4531, + 0.0977, 0.2695, 0.4688, + 0.1016, 0.2695, 0.4766, + 0.1094, 0.2734, 0.4844, + 0.1172, 0.2734, 0.4961, + 0.1250, 0.2734, 0.5078, + 0.1328, 0.2773, 0.5195, + 0.1367, 0.2773, 0.5234, + 0.1445, 0.2773, 0.5312, + 0.1523, 0.2773, 0.5352, + 0.1680, 0.2773, 0.5508, + 0.1719, 0.2773, 0.5547, + 0.1836, 0.2773, 0.5625, + 0.1914, 0.2773, 0.5703, + 0.2031, 0.2773, 0.5781, + 0.2148, 0.2773, 0.5898, + 0.2227, 0.2812, 0.5938, + 0.2344, 0.2812, 0.6016, + 0.2383, 0.2812, 0.6055, + 0.2539, 0.2812, 0.6172, + 0.2617, 0.2812, 0.6250, + 0.2695, 0.2812, 0.6328, + 0.2812, 0.2812, 0.6367, + 0.2930, 0.2812, 0.6445, + 0.3008, 0.2812, 0.6523, + 0.3125, 0.2812, 0.6562, + 0.3203, 0.2812, 0.6641, + 0.3320, 0.2812, 0.6641, + 0.3477, 0.2812, 0.6680, + 0.3555, 0.2812, 0.6719, + 0.3672, 0.2812, 0.6758, + 0.3789, 0.2812, 0.6797, + 0.3945, 0.2812, 0.6836, + 0.4023, 0.2812, 0.6875, + 0.4141, 0.2812, 0.6914, + 0.4258, 0.2812, 0.6953, + 0.4336, 0.2812, 0.6953, + 0.4531, 0.2812, 0.7031, + 0.4609, 0.2812, 0.7031, + 0.4727, 0.2812, 0.7109, + 0.4805, 0.2812, 0.7148, + 0.4961, 0.2812, 0.7188, + 0.5078, 0.2812, 0.7188, + 0.5195, 0.2812, 0.7188, + 0.5273, 0.2852, 0.7188, + 0.5352, 0.2852, 0.7148, + 0.5508, 0.2852, 0.7148, + 0.5586, 0.2852, 0.7148, + 0.5703, 0.2891, 0.7148, + 0.5820, 0.2891, 0.7148, + 0.5938, 0.2930, 0.7109, + 0.6016, 0.2930, 0.7109, + 0.6133, 0.2930, 0.7109, + 0.6250, 0.2930, 0.7109, + 0.6328, 0.2930, 0.7109, + 0.6484, 0.2969, 0.7109, + 0.6562, 0.2969, 0.7070, + 0.6680, 0.2969, 0.7070, + 0.6758, 0.3008, 0.7070, + 0.6914, 0.3008, 0.7070, + 0.6992, 0.3047, 0.7070, + 0.7031, 0.3047, 0.7070, + 0.7148, 0.3086, 0.7070, + 0.7227, 0.3125, 0.7070, + 0.7344, 0.3125, 0.7070, + 0.7383, 0.3164, 0.7070, + 0.7461, 0.3203, 0.7070, + 0.7539, 0.3242, 0.7070, + 0.7695, 0.3281, 0.7070, + 0.7773, 0.3281, 0.7070, + 0.7812, 0.3320, 0.7070, + 0.7891, 0.3359, 0.7070, + 0.7930, 0.3398, 0.7070, + 0.8086, 0.3398, 0.7070, + 0.8164, 0.3438, 0.7070, + 0.8242, 0.3477, 0.7070, + 0.8320, 0.3516, 0.7070, + 0.8398, 0.3555, 0.7031, + 0.8438, 0.3594, 0.6953, + 0.8477, 0.3633, 0.6875, + 0.8516, 0.3711, 0.6758, + 0.8516, 0.3711, 0.6680, + 0.8633, 0.3789, 0.6602, + 0.8672, 0.3828, 0.6523, + 0.8711, 0.3906, 0.6445, + 0.8750, 0.3945, 0.6328, + 0.8828, 0.4023, 0.6250, + 0.8867, 0.4062, 0.6172, + 0.8906, 0.4102, 0.6094, + 0.8945, 0.4141, 0.6016, + 0.8984, 0.4180, 0.5898, + 0.9062, 0.4258, 0.5820, + 0.9102, 0.4297, 0.5742, + 0.9141, 0.4375, 0.5664, + 0.9180, 0.4414, 0.5586, + 0.9180, 0.4492, 0.5547, + 0.9180, 0.4570, 0.5508, + 0.9180, 0.4609, 0.5508, + 0.9219, 0.4688, 0.5430, + 0.9219, 0.4688, 0.5391, + 0.9219, 0.4844, 0.5352, + 0.9219, 0.4883, 0.5312, + 0.9219, 0.4922, 0.5312, + 0.9258, 0.5000, 0.5273, + 0.9258, 0.5078, 0.5234, + 0.9258, 0.5156, 0.5195, + 0.9258, 0.5195, 0.5195, + 0.9258, 0.5273, 0.5156, + 0.9258, 0.5312, 0.5078, + 0.9297, 0.5391, 0.5039, + 0.9297, 0.5430, 0.5039, + 0.9297, 0.5547, 0.5039, + 0.9297, 0.5625, 0.5000, + 0.9258, 0.5703, 0.5000, + 0.9219, 0.5781, 0.5000, + 0.9219, 0.5781, 0.5000, + 0.9219, 0.5898, 0.5000, + 0.9180, 0.5938, 0.5000, + 0.9180, 0.6055, 0.5000, + 0.9141, 0.6094, 0.5000, + 0.9141, 0.6172, 0.5000, + 0.9141, 0.6250, 0.4961, + 0.9102, 0.6367, 0.4961, + 0.9102, 0.6445, 0.4961, + 0.9062, 0.6484, 0.4961, + 0.9062, 0.6562, 0.4961, + 0.9023, 0.6602, 0.4961, + 0.9023, 0.6758, 0.4961, + 0.8984, 0.6797, 0.4961, + 0.8984, 0.6836, 0.4961, + 0.8945, 0.6914, 0.5000, + 0.8945, 0.6992, 0.5039, + 0.8906, 0.7109, 0.5078, + 0.8867, 0.7148, 0.5117, + 0.8867, 0.7227, 0.5156, + 0.8828, 0.7266, 0.5195, + 0.8789, 0.7344, 0.5234, + 0.8789, 0.7422, 0.5273, + 0.8750, 0.7500, 0.5312, + 0.8711, 0.7578, 0.5352, + 0.8711, 0.7617, 0.5391, + 0.8672, 0.7695, 0.5430, + 0.8633, 0.7773, 0.5469, + 0.8633, 0.7852, 0.5508, + 0.8594, 0.7891, 0.5508, + 0.8594, 0.8008, 0.5586, + 0.8555, 0.8047, 0.5625, + 0.8555, 0.8086, 0.5664, + 0.8555, 0.8164, 0.5742, + 0.8516, 0.8242, 0.5859, + 0.8516, 0.8281, 0.5938, + 0.8516, 0.8320, 0.5977, + 0.8516, 0.8398, 0.6055, + 0.8516, 0.8438, 0.6133, + 0.8516, 0.8516, 0.6250, + 0.8477, 0.8516, 0.6289, + 0.8477, 0.8594, 0.6367, + 0.8477, 0.8672, 0.6445, + 0.8477, 0.8711, 0.6523, + 0.8477, 0.8789, 0.6641, + 0.8477, 0.8828, 0.6680, + 0.8438, 0.8906, 0.6758, + 0.8438, 0.8906, 0.6797, + 0.8438, 0.9023, 0.6914, + 0.8438, 0.9023, 0.6992, + 0.8477, 0.9062, 0.7109, + 0.8477, 0.9102, 0.7188, + 0.8516, 0.9180, 0.7305, + 0.8555, 0.9219, 0.7383, + 0.8555, 0.9219, 0.7461, + 0.8594, 0.9258, 0.7578, + 0.8594, 0.9297, 0.7617, + 0.8633, 0.9375, 0.7773, + 0.8633, 0.9375, 0.7812, + 0.8672, 0.9414, 0.7930, + 0.8711, 0.9414, 0.8008, + 0.8711, 0.9453, 0.8125, + 0.8750, 0.9531, 0.8242, + 0.8750, 0.9531, 0.8281, + 0.8789, 0.9570, 0.8398, + 0.8789, 0.9609, 0.8477, + 0.8867, 0.9648, 0.8594, + 0.8906, 0.9688, 0.8633, + 0.8984, 0.9688, 0.8711, + 0.9023, 0.9688, 0.8789, + 0.9141, 0.9727, 0.8945, + 0.9180, 0.9727, 0.9023, + 0.9219, 0.9766, 0.9062, + 0.9297, 0.9766, 0.9141, + 0.9336, 0.9805, 0.9180, + 0.9453, 0.9805, 0.9336, + 0.9492, 0.9844, 0.9375, + 0.9570, 0.9844, 0.9453, + 0.9648, 0.9883, 0.9531, + 0.9688, 0.9883, 0.9648, + 0.9766, 0.9922, 0.9727, + 0.9844, 0.9922, 0.9805, + 0.9883, 0.9961, 0.9883, +}; + +// Generated with https://people.phy.cam.ac.uk/dag9/CUBEHELIX/cubetry.html +// Using: Start color: 2.0, Number of rotations: 1.5, Rotation direction: positive, Hue: 1.0, Gamma: 0.4 +const float ColorMap::m_cubegamma[m_size] = +{ + 0.000, 0.000, 0.000, + 0.036, 0.155, 0.064, + 0.049, 0.202, 0.091, + 0.059, 0.236, 0.114, + 0.066, 0.263, 0.134, + 0.073, 0.286, 0.154, + 0.079, 0.306, 0.172, + 0.085, 0.323, 0.191, + 0.091, 0.339, 0.209, + 0.096, 0.354, 0.226, + 0.101, 0.367, 0.244, + 0.106, 0.379, 0.261, + 0.111, 0.391, 0.278, + 0.116, 0.401, 0.295, + 0.122, 0.411, 0.312, + 0.127, 0.420, 0.329, + 0.133, 0.428, 0.345, + 0.138, 0.436, 0.362, + 0.144, 0.443, 0.378, + 0.150, 0.450, 0.394, + 0.157, 0.456, 0.410, + 0.163, 0.462, 0.426, + 0.170, 0.467, 0.441, + 0.177, 0.472, 0.457, + 0.184, 0.477, 0.472, + 0.192, 0.481, 0.487, + 0.200, 0.485, 0.502, + 0.208, 0.489, 0.516, + 0.216, 0.492, 0.530, + 0.224, 0.495, 0.544, + 0.233, 0.497, 0.558, + 0.242, 0.500, 0.571, + 0.251, 0.502, 0.585, + 0.261, 0.504, 0.597, + 0.271, 0.506, 0.610, + 0.281, 0.507, 0.622, + 0.291, 0.509, 0.634, + 0.301, 0.510, 0.645, + 0.311, 0.511, 0.656, + 0.322, 0.512, 0.667, + 0.333, 0.512, 0.678, + 0.344, 0.513, 0.688, + 0.355, 0.513, 0.697, + 0.367, 0.514, 0.706, + 0.378, 0.514, 0.715, + 0.390, 0.514, 0.724, + 0.401, 0.514, 0.732, + 0.413, 0.514, 0.739, + 0.425, 0.514, 0.747, + 0.437, 0.514, 0.753, + 0.449, 0.513, 0.760, + 0.461, 0.513, 0.766, + 0.473, 0.513, 0.772, + 0.485, 0.513, 0.777, + 0.497, 0.512, 0.781, + 0.509, 0.512, 0.786, + 0.521, 0.512, 0.790, + 0.534, 0.512, 0.793, + 0.546, 0.511, 0.797, + 0.558, 0.511, 0.799, + 0.569, 0.511, 0.802, + 0.581, 0.511, 0.804, + 0.593, 0.511, 0.805, + 0.605, 0.511, 0.807, + 0.616, 0.511, 0.807, + 0.627, 0.511, 0.808, + 0.639, 0.512, 0.808, + 0.650, 0.512, 0.808, + 0.661, 0.512, 0.807, + 0.671, 0.513, 0.807, + 0.682, 0.514, 0.805, + 0.692, 0.514, 0.804, + 0.703, 0.515, 0.802, + 0.713, 0.516, 0.800, + 0.722, 0.517, 0.798, + 0.732, 0.519, 0.795, + 0.741, 0.520, 0.792, + 0.750, 0.521, 0.789, + 0.759, 0.523, 0.786, + 0.767, 0.525, 0.782, + 0.776, 0.526, 0.779, + 0.784, 0.528, 0.775, + 0.791, 0.531, 0.771, + 0.799, 0.533, 0.766, + 0.806, 0.535, 0.762, + 0.813, 0.538, 0.757, + 0.819, 0.540, 0.753, + 0.826, 0.543, 0.748, + 0.832, 0.546, 0.743, + 0.837, 0.549, 0.738, + 0.843, 0.552, 0.733, + 0.848, 0.556, 0.728, + 0.852, 0.559, 0.723, + 0.857, 0.563, 0.717, + 0.861, 0.566, 0.712, + 0.865, 0.570, 0.707, + 0.868, 0.574, 0.702, + 0.872, 0.578, 0.697, + 0.875, 0.583, 0.691, + 0.877, 0.587, 0.686, + 0.880, 0.591, 0.681, + 0.882, 0.596, 0.676, + 0.883, 0.601, 0.671, + 0.885, 0.605, 0.666, + 0.886, 0.610, 0.662, + 0.887, 0.615, 0.657, + 0.888, 0.620, 0.653, + 0.888, 0.625, 0.648, + 0.888, 0.630, 0.644, + 0.888, 0.636, 0.640, + 0.888, 0.641, 0.636, + 0.887, 0.646, 0.632, + 0.886, 0.652, 0.629, + 0.885, 0.657, 0.625, + 0.884, 0.663, 0.622, + 0.883, 0.668, 0.619, + 0.881, 0.674, 0.616, + 0.879, 0.680, 0.614, + 0.877, 0.685, 0.611, + 0.875, 0.691, 0.609, + 0.873, 0.697, 0.607, + 0.870, 0.702, 0.606, + 0.868, 0.708, 0.604, + 0.865, 0.714, 0.603, + 0.862, 0.720, 0.602, + 0.859, 0.725, 0.602, + 0.856, 0.731, 0.601, + 0.853, 0.737, 0.601, + 0.850, 0.742, 0.601, + 0.847, 0.748, 0.601, + 0.843, 0.754, 0.602, + 0.840, 0.759, 0.603, + 0.836, 0.765, 0.604, + 0.833, 0.770, 0.605, + 0.829, 0.775, 0.607, + 0.826, 0.781, 0.609, + 0.822, 0.786, 0.611, + 0.819, 0.791, 0.613, + 0.815, 0.796, 0.616, + 0.812, 0.802, 0.619, + 0.808, 0.807, 0.622, + 0.805, 0.811, 0.625, + 0.802, 0.816, 0.628, + 0.798, 0.821, 0.632, + 0.795, 0.826, 0.636, + 0.792, 0.830, 0.640, + 0.789, 0.835, 0.644, + 0.786, 0.839, 0.649, + 0.783, 0.843, 0.653, + 0.781, 0.847, 0.658, + 0.778, 0.852, 0.663, + 0.775, 0.856, 0.669, + 0.773, 0.859, 0.674, + 0.771, 0.863, 0.679, + 0.769, 0.867, 0.685, + 0.767, 0.870, 0.691, + 0.765, 0.874, 0.697, + 0.763, 0.877, 0.703, + 0.762, 0.880, 0.709, + 0.760, 0.883, 0.715, + 0.759, 0.886, 0.721, + 0.758, 0.889, 0.728, + 0.757, 0.892, 0.734, + 0.757, 0.894, 0.741, + 0.756, 0.897, 0.747, + 0.756, 0.899, 0.754, + 0.756, 0.901, 0.761, + 0.756, 0.904, 0.767, + 0.756, 0.906, 0.774, + 0.756, 0.908, 0.781, + 0.757, 0.910, 0.787, + 0.757, 0.911, 0.794, + 0.758, 0.913, 0.801, + 0.759, 0.915, 0.808, + 0.761, 0.916, 0.814, + 0.762, 0.917, 0.821, + 0.764, 0.919, 0.827, + 0.765, 0.920, 0.834, + 0.767, 0.921, 0.840, + 0.769, 0.922, 0.847, + 0.772, 0.923, 0.853, + 0.774, 0.924, 0.859, + 0.776, 0.925, 0.865, + 0.779, 0.926, 0.871, + 0.782, 0.926, 0.877, + 0.785, 0.927, 0.883, + 0.788, 0.928, 0.888, + 0.791, 0.928, 0.894, + 0.794, 0.929, 0.899, + 0.798, 0.929, 0.905, + 0.801, 0.930, 0.910, + 0.805, 0.930, 0.915, + 0.809, 0.930, 0.920, + 0.813, 0.931, 0.924, + 0.817, 0.931, 0.929, + 0.821, 0.931, 0.933, + 0.825, 0.931, 0.938, + 0.829, 0.932, 0.942, + 0.833, 0.932, 0.946, + 0.837, 0.932, 0.950, + 0.842, 0.932, 0.953, + 0.846, 0.933, 0.957, + 0.850, 0.933, 0.960, + 0.855, 0.933, 0.963, + 0.859, 0.933, 0.966, + 0.863, 0.934, 0.969, + 0.868, 0.934, 0.972, + 0.872, 0.934, 0.974, + 0.877, 0.934, 0.977, + 0.881, 0.935, 0.979, + 0.885, 0.935, 0.981, + 0.890, 0.936, 0.983, + 0.894, 0.936, 0.985, + 0.898, 0.936, 0.986, + 0.903, 0.937, 0.988, + 0.907, 0.938, 0.989, + 0.911, 0.938, 0.991, + 0.915, 0.939, 0.992, + 0.919, 0.939, 0.993, + 0.923, 0.940, 0.994, + 0.927, 0.941, 0.994, + 0.931, 0.942, 0.995, + 0.935, 0.943, 0.996, + 0.938, 0.944, 0.996, + 0.942, 0.945, 0.997, + 0.945, 0.946, 0.997, + 0.949, 0.947, 0.997, + 0.952, 0.948, 0.997, + 0.955, 0.949, 0.997, + 0.958, 0.950, 0.998, + 0.961, 0.952, 0.998, + 0.964, 0.953, 0.998, + 0.967, 0.955, 0.997, + 0.969, 0.956, 0.997, + 0.972, 0.958, 0.997, + 0.974, 0.959, 0.997, + 0.976, 0.961, 0.997, + 0.979, 0.963, 0.997, + 0.981, 0.964, 0.997, + 0.983, 0.966, 0.996, + 0.984, 0.968, 0.996, + 0.986, 0.970, 0.996, + 0.988, 0.972, 0.996, + 0.989, 0.974, 0.996, + 0.991, 0.976, 0.996, + 0.992, 0.978, 0.996, + 0.993, 0.980, 0.996, + 0.994, 0.982, 0.996, + 0.995, 0.984, 0.997, + 0.996, 0.986, 0.997, + 0.997, 0.989, 0.997, + 0.998, 0.991, 0.998, + 0.998, 0.993, 0.998, + 0.999, 0.995, 0.999, + 1.000, 0.998, 0.999, + 1.000, 1.000, 1.000, +}; + +// Generated with https://people.phy.cam.ac.uk/dag9/CUBEHELIX/cubetry.html +// Using: Start color: 1.5, Number of rotations: -1.0, Rotation direction: negative, Hue: 1.0, Gamma: 0.7 +const float ColorMap::m_cubehlx2[m_size] = +{ + 0.000, 0.000, 0.000, + 0.023, 0.023, 0.001, + 0.037, 0.038, 0.002, + 0.051, 0.049, 0.003, + 0.063, 0.060, 0.004, + 0.075, 0.069, 0.005, + 0.086, 0.078, 0.007, + 0.098, 0.086, 0.009, + 0.109, 0.093, 0.010, + 0.120, 0.100, 0.013, + 0.131, 0.107, 0.015, + 0.141, 0.113, 0.017, + 0.152, 0.119, 0.020, + 0.163, 0.124, 0.022, + 0.173, 0.129, 0.025, + 0.184, 0.134, 0.028, + 0.195, 0.139, 0.032, + 0.205, 0.144, 0.035, + 0.216, 0.148, 0.039, + 0.226, 0.152, 0.043, + 0.237, 0.156, 0.047, + 0.247, 0.160, 0.051, + 0.257, 0.164, 0.055, + 0.267, 0.167, 0.060, + 0.278, 0.171, 0.065, + 0.288, 0.174, 0.070, + 0.298, 0.177, 0.075, + 0.308, 0.181, 0.080, + 0.318, 0.184, 0.085, + 0.328, 0.186, 0.091, + 0.337, 0.189, 0.097, + 0.347, 0.192, 0.103, + 0.357, 0.195, 0.109, + 0.366, 0.197, 0.116, + 0.375, 0.200, 0.122, + 0.385, 0.202, 0.129, + 0.394, 0.205, 0.136, + 0.403, 0.207, 0.143, + 0.412, 0.210, 0.150, + 0.421, 0.212, 0.158, + 0.429, 0.214, 0.165, + 0.438, 0.217, 0.173, + 0.446, 0.219, 0.181, + 0.454, 0.221, 0.189, + 0.462, 0.224, 0.197, + 0.470, 0.226, 0.205, + 0.478, 0.228, 0.214, + 0.486, 0.230, 0.222, + 0.493, 0.233, 0.231, + 0.501, 0.235, 0.239, + 0.508, 0.237, 0.248, + 0.515, 0.240, 0.257, + 0.522, 0.242, 0.266, + 0.528, 0.244, 0.276, + 0.535, 0.247, 0.285, + 0.541, 0.249, 0.294, + 0.547, 0.252, 0.304, + 0.553, 0.254, 0.313, + 0.559, 0.257, 0.323, + 0.565, 0.259, 0.332, + 0.570, 0.262, 0.342, + 0.575, 0.265, 0.352, + 0.580, 0.267, 0.362, + 0.585, 0.270, 0.372, + 0.590, 0.273, 0.381, + 0.594, 0.276, 0.391, + 0.599, 0.279, 0.401, + 0.603, 0.282, 0.411, + 0.607, 0.285, 0.421, + 0.610, 0.288, 0.431, + 0.614, 0.291, 0.441, + 0.617, 0.295, 0.451, + 0.621, 0.298, 0.461, + 0.624, 0.301, 0.471, + 0.626, 0.305, 0.481, + 0.629, 0.308, 0.491, + 0.632, 0.312, 0.500, + 0.634, 0.316, 0.510, + 0.636, 0.319, 0.520, + 0.638, 0.323, 0.530, + 0.640, 0.327, 0.539, + 0.641, 0.331, 0.549, + 0.643, 0.335, 0.558, + 0.644, 0.339, 0.568, + 0.645, 0.343, 0.577, + 0.646, 0.348, 0.586, + 0.647, 0.352, 0.595, + 0.648, 0.356, 0.604, + 0.648, 0.361, 0.613, + 0.649, 0.365, 0.622, + 0.649, 0.370, 0.631, + 0.649, 0.375, 0.639, + 0.649, 0.380, 0.648, + 0.649, 0.384, 0.656, + 0.648, 0.389, 0.665, + 0.648, 0.394, 0.673, + 0.648, 0.399, 0.681, + 0.647, 0.404, 0.688, + 0.646, 0.409, 0.696, + 0.645, 0.415, 0.704, + 0.644, 0.420, 0.711, + 0.643, 0.425, 0.718, + 0.642, 0.431, 0.726, + 0.641, 0.436, 0.732, + 0.640, 0.442, 0.739, + 0.638, 0.447, 0.746, + 0.637, 0.453, 0.752, + 0.635, 0.458, 0.759, + 0.634, 0.464, 0.765, + 0.632, 0.470, 0.771, + 0.630, 0.476, 0.776, + 0.629, 0.481, 0.782, + 0.627, 0.487, 0.787, + 0.625, 0.493, 0.793, + 0.623, 0.499, 0.798, + 0.621, 0.505, 0.803, + 0.620, 0.511, 0.807, + 0.618, 0.517, 0.812, + 0.616, 0.523, 0.816, + 0.614, 0.529, 0.821, + 0.612, 0.535, 0.825, + 0.610, 0.541, 0.828, + 0.608, 0.547, 0.832, + 0.606, 0.553, 0.836, + 0.604, 0.560, 0.839, + 0.602, 0.566, 0.842, + 0.601, 0.572, 0.845, + 0.599, 0.578, 0.848, + 0.597, 0.584, 0.850, + 0.595, 0.590, 0.853, + 0.594, 0.596, 0.855, + 0.592, 0.602, 0.857, + 0.591, 0.609, 0.859, + 0.589, 0.615, 0.861, + 0.588, 0.621, 0.862, + 0.586, 0.627, 0.864, + 0.585, 0.633, 0.865, + 0.584, 0.639, 0.866, + 0.582, 0.645, 0.867, + 0.581, 0.651, 0.868, + 0.580, 0.657, 0.869, + 0.579, 0.663, 0.870, + 0.579, 0.669, 0.870, + 0.578, 0.674, 0.870, + 0.577, 0.680, 0.871, + 0.577, 0.686, 0.871, + 0.576, 0.692, 0.871, + 0.576, 0.697, 0.870, + 0.576, 0.703, 0.870, + 0.576, 0.709, 0.870, + 0.576, 0.714, 0.869, + 0.576, 0.720, 0.869, + 0.576, 0.725, 0.868, + 0.577, 0.731, 0.867, + 0.577, 0.736, 0.866, + 0.578, 0.741, 0.865, + 0.578, 0.746, 0.864, + 0.579, 0.751, 0.863, + 0.580, 0.757, 0.862, + 0.581, 0.762, 0.861, + 0.583, 0.767, 0.860, + 0.584, 0.771, 0.858, + 0.586, 0.776, 0.857, + 0.587, 0.781, 0.855, + 0.589, 0.786, 0.854, + 0.591, 0.790, 0.852, + 0.593, 0.795, 0.851, + 0.595, 0.799, 0.849, + 0.597, 0.804, 0.848, + 0.600, 0.808, 0.846, + 0.602, 0.812, 0.845, + 0.605, 0.817, 0.843, + 0.608, 0.821, 0.841, + 0.611, 0.825, 0.840, + 0.614, 0.829, 0.838, + 0.617, 0.832, 0.837, + 0.620, 0.836, 0.835, + 0.624, 0.840, 0.834, + 0.627, 0.844, 0.832, + 0.631, 0.847, 0.831, + 0.635, 0.851, 0.829, + 0.639, 0.854, 0.828, + 0.643, 0.858, 0.827, + 0.647, 0.861, 0.825, + 0.651, 0.864, 0.824, + 0.655, 0.867, 0.823, + 0.660, 0.870, 0.822, + 0.664, 0.873, 0.821, + 0.669, 0.876, 0.820, + 0.674, 0.879, 0.819, + 0.679, 0.882, 0.818, + 0.683, 0.885, 0.818, + 0.688, 0.887, 0.817, + 0.693, 0.890, 0.817, + 0.699, 0.892, 0.816, + 0.704, 0.895, 0.816, + 0.709, 0.897, 0.816, + 0.714, 0.899, 0.816, + 0.720, 0.902, 0.815, + 0.725, 0.904, 0.816, + 0.731, 0.906, 0.816, + 0.736, 0.908, 0.816, + 0.742, 0.910, 0.816, + 0.748, 0.912, 0.817, + 0.753, 0.914, 0.818, + 0.759, 0.916, 0.818, + 0.765, 0.918, 0.819, + 0.771, 0.920, 0.820, + 0.776, 0.922, 0.821, + 0.782, 0.923, 0.823, + 0.788, 0.925, 0.824, + 0.794, 0.927, 0.826, + 0.800, 0.928, 0.827, + 0.806, 0.930, 0.829, + 0.811, 0.932, 0.831, + 0.817, 0.933, 0.833, + 0.823, 0.935, 0.835, + 0.829, 0.936, 0.837, + 0.835, 0.938, 0.840, + 0.840, 0.939, 0.842, + 0.846, 0.941, 0.845, + 0.852, 0.942, 0.848, + 0.857, 0.944, 0.851, + 0.863, 0.945, 0.854, + 0.869, 0.946, 0.857, + 0.874, 0.948, 0.860, + 0.880, 0.949, 0.863, + 0.885, 0.951, 0.867, + 0.890, 0.952, 0.871, + 0.896, 0.954, 0.874, + 0.901, 0.955, 0.878, + 0.906, 0.956, 0.882, + 0.911, 0.958, 0.886, + 0.916, 0.959, 0.890, + 0.921, 0.961, 0.894, + 0.926, 0.962, 0.899, + 0.930, 0.964, 0.903, + 0.935, 0.966, 0.908, + 0.940, 0.967, 0.912, + 0.944, 0.969, 0.917, + 0.948, 0.970, 0.922, + 0.953, 0.972, 0.927, + 0.957, 0.974, 0.932, + 0.961, 0.976, 0.937, + 0.965, 0.977, 0.942, + 0.968, 0.979, 0.947, + 0.972, 0.981, 0.952, + 0.976, 0.983, 0.957, + 0.979, 0.985, 0.962, + 0.983, 0.987, 0.968, + 0.986, 0.989, 0.973, + 0.989, 0.991, 0.978, + 0.992, 0.993, 0.984, + 0.995, 0.995, 0.989, + 0.997, 0.998, 0.995, + 1.000, 1.000, 1.000, +}; + +// Generated with https://people.phy.cam.ac.uk/dag9/CUBEHELIX/cubetry.html +// Using: Start color: 3.0, Number of rotations: -0.5, Rotation direction: negative, Hue: 1.0, Gamma: 0.7 +const float ColorMap::m_icy[m_size] = +{ + 0.000, 0.000, 0.000, + 0.019, 0.018, 0.041, + 0.030, 0.029, 0.066, + 0.040, 0.039, 0.087, + 0.048, 0.048, 0.105, + 0.056, 0.057, 0.123, + 0.063, 0.065, 0.139, + 0.070, 0.073, 0.154, + 0.076, 0.080, 0.168, + 0.081, 0.088, 0.182, + 0.087, 0.095, 0.195, + 0.092, 0.103, 0.207, + 0.096, 0.110, 0.219, + 0.101, 0.117, 0.231, + 0.105, 0.124, 0.242, + 0.110, 0.130, 0.253, + 0.113, 0.137, 0.263, + 0.117, 0.144, 0.273, + 0.121, 0.151, 0.283, + 0.124, 0.157, 0.293, + 0.128, 0.164, 0.302, + 0.131, 0.171, 0.311, + 0.134, 0.177, 0.320, + 0.137, 0.184, 0.329, + 0.140, 0.190, 0.337, + 0.143, 0.196, 0.345, + 0.146, 0.203, 0.353, + 0.148, 0.209, 0.361, + 0.151, 0.216, 0.369, + 0.153, 0.222, 0.376, + 0.156, 0.228, 0.383, + 0.158, 0.235, 0.390, + 0.160, 0.241, 0.397, + 0.162, 0.247, 0.404, + 0.165, 0.253, 0.410, + 0.167, 0.260, 0.417, + 0.169, 0.266, 0.423, + 0.171, 0.272, 0.429, + 0.173, 0.278, 0.435, + 0.175, 0.284, 0.441, + 0.177, 0.290, 0.446, + 0.179, 0.297, 0.452, + 0.180, 0.303, 0.457, + 0.182, 0.309, 0.462, + 0.184, 0.315, 0.467, + 0.186, 0.321, 0.472, + 0.188, 0.327, 0.477, + 0.189, 0.333, 0.481, + 0.191, 0.339, 0.486, + 0.193, 0.345, 0.490, + 0.194, 0.351, 0.495, + 0.196, 0.357, 0.499, + 0.198, 0.362, 0.503, + 0.200, 0.368, 0.507, + 0.201, 0.374, 0.511, + 0.203, 0.380, 0.515, + 0.205, 0.386, 0.518, + 0.206, 0.392, 0.522, + 0.208, 0.397, 0.525, + 0.210, 0.403, 0.529, + 0.212, 0.409, 0.532, + 0.213, 0.415, 0.535, + 0.215, 0.420, 0.538, + 0.217, 0.426, 0.541, + 0.219, 0.431, 0.544, + 0.220, 0.437, 0.547, + 0.222, 0.443, 0.549, + 0.224, 0.448, 0.552, + 0.226, 0.454, 0.554, + 0.228, 0.459, 0.557, + 0.230, 0.465, 0.559, + 0.232, 0.470, 0.561, + 0.234, 0.475, 0.564, + 0.236, 0.481, 0.566, + 0.238, 0.486, 0.568, + 0.240, 0.491, 0.570, + 0.242, 0.497, 0.572, + 0.244, 0.502, 0.574, + 0.246, 0.507, 0.575, + 0.248, 0.512, 0.577, + 0.251, 0.518, 0.579, + 0.253, 0.523, 0.580, + 0.255, 0.528, 0.582, + 0.257, 0.533, 0.583, + 0.260, 0.538, 0.585, + 0.262, 0.543, 0.586, + 0.265, 0.548, 0.587, + 0.267, 0.553, 0.589, + 0.270, 0.558, 0.590, + 0.272, 0.563, 0.591, + 0.275, 0.567, 0.592, + 0.278, 0.572, 0.593, + 0.280, 0.577, 0.594, + 0.283, 0.582, 0.595, + 0.286, 0.586, 0.596, + 0.289, 0.591, 0.597, + 0.291, 0.596, 0.598, + 0.294, 0.600, 0.599, + 0.297, 0.605, 0.600, + 0.300, 0.609, 0.600, + 0.303, 0.614, 0.601, + 0.306, 0.618, 0.602, + 0.309, 0.623, 0.603, + 0.313, 0.627, 0.603, + 0.316, 0.631, 0.604, + 0.319, 0.636, 0.604, + 0.322, 0.640, 0.605, + 0.326, 0.644, 0.606, + 0.329, 0.648, 0.606, + 0.333, 0.653, 0.607, + 0.336, 0.657, 0.607, + 0.340, 0.661, 0.608, + 0.343, 0.665, 0.608, + 0.347, 0.669, 0.609, + 0.351, 0.673, 0.609, + 0.354, 0.677, 0.610, + 0.358, 0.681, 0.610, + 0.362, 0.685, 0.611, + 0.366, 0.688, 0.611, + 0.370, 0.692, 0.612, + 0.374, 0.696, 0.612, + 0.378, 0.700, 0.612, + 0.382, 0.703, 0.613, + 0.386, 0.707, 0.613, + 0.390, 0.711, 0.614, + 0.394, 0.714, 0.614, + 0.398, 0.718, 0.615, + 0.402, 0.721, 0.615, + 0.407, 0.725, 0.616, + 0.411, 0.728, 0.616, + 0.415, 0.731, 0.617, + 0.420, 0.735, 0.617, + 0.424, 0.738, 0.618, + 0.429, 0.741, 0.619, + 0.433, 0.744, 0.619, + 0.438, 0.748, 0.620, + 0.443, 0.751, 0.620, + 0.447, 0.754, 0.621, + 0.452, 0.757, 0.622, + 0.457, 0.760, 0.622, + 0.461, 0.763, 0.623, + 0.466, 0.766, 0.624, + 0.471, 0.769, 0.625, + 0.476, 0.772, 0.625, + 0.481, 0.775, 0.626, + 0.486, 0.778, 0.627, + 0.491, 0.781, 0.628, + 0.496, 0.783, 0.629, + 0.501, 0.786, 0.630, + 0.506, 0.789, 0.631, + 0.511, 0.792, 0.632, + 0.516, 0.794, 0.633, + 0.521, 0.797, 0.634, + 0.526, 0.799, 0.635, + 0.531, 0.802, 0.636, + 0.536, 0.805, 0.638, + 0.542, 0.807, 0.639, + 0.547, 0.810, 0.640, + 0.552, 0.812, 0.642, + 0.557, 0.815, 0.643, + 0.563, 0.817, 0.644, + 0.568, 0.819, 0.646, + 0.573, 0.822, 0.647, + 0.578, 0.824, 0.649, + 0.584, 0.826, 0.651, + 0.589, 0.829, 0.652, + 0.595, 0.831, 0.654, + 0.600, 0.833, 0.656, + 0.605, 0.835, 0.657, + 0.611, 0.837, 0.659, + 0.616, 0.840, 0.661, + 0.622, 0.842, 0.663, + 0.627, 0.844, 0.665, + 0.632, 0.846, 0.667, + 0.638, 0.848, 0.669, + 0.643, 0.850, 0.671, + 0.649, 0.852, 0.674, + 0.654, 0.854, 0.676, + 0.660, 0.856, 0.678, + 0.665, 0.858, 0.680, + 0.671, 0.860, 0.683, + 0.676, 0.862, 0.685, + 0.681, 0.864, 0.688, + 0.687, 0.866, 0.690, + 0.692, 0.868, 0.693, + 0.698, 0.869, 0.696, + 0.703, 0.871, 0.698, + 0.709, 0.873, 0.701, + 0.714, 0.875, 0.704, + 0.719, 0.877, 0.707, + 0.725, 0.879, 0.710, + 0.730, 0.880, 0.713, + 0.735, 0.882, 0.716, + 0.741, 0.884, 0.719, + 0.746, 0.886, 0.722, + 0.751, 0.887, 0.725, + 0.757, 0.889, 0.729, + 0.762, 0.891, 0.732, + 0.767, 0.893, 0.735, + 0.772, 0.894, 0.739, + 0.778, 0.896, 0.742, + 0.783, 0.898, 0.746, + 0.788, 0.900, 0.749, + 0.793, 0.901, 0.753, + 0.798, 0.903, 0.757, + 0.803, 0.905, 0.760, + 0.808, 0.906, 0.764, + 0.813, 0.908, 0.768, + 0.818, 0.910, 0.772, + 0.823, 0.911, 0.776, + 0.828, 0.913, 0.780, + 0.833, 0.915, 0.784, + 0.838, 0.917, 0.788, + 0.842, 0.918, 0.792, + 0.847, 0.920, 0.796, + 0.852, 0.922, 0.801, + 0.857, 0.923, 0.805, + 0.861, 0.925, 0.809, + 0.866, 0.927, 0.814, + 0.870, 0.929, 0.818, + 0.875, 0.930, 0.822, + 0.879, 0.932, 0.827, + 0.884, 0.934, 0.832, + 0.888, 0.936, 0.836, + 0.892, 0.937, 0.841, + 0.897, 0.939, 0.845, + 0.901, 0.941, 0.850, + 0.905, 0.943, 0.855, + 0.909, 0.945, 0.860, + 0.913, 0.946, 0.865, + 0.917, 0.948, 0.869, + 0.921, 0.950, 0.874, + 0.925, 0.952, 0.879, + 0.929, 0.954, 0.884, + 0.933, 0.956, 0.889, + 0.937, 0.958, 0.894, + 0.941, 0.960, 0.899, + 0.944, 0.962, 0.904, + 0.948, 0.964, 0.910, + 0.951, 0.965, 0.915, + 0.955, 0.967, 0.920, + 0.958, 0.970, 0.925, + 0.962, 0.972, 0.930, + 0.965, 0.974, 0.936, + 0.968, 0.976, 0.941, + 0.971, 0.978, 0.946, + 0.975, 0.980, 0.951, + 0.978, 0.982, 0.957, + 0.981, 0.984, 0.962, + 0.984, 0.986, 0.967, + 0.986, 0.989, 0.973, + 0.989, 0.991, 0.978, + 0.992, 0.993, 0.984, + 0.995, 0.995, 0.989, + 0.997, 0.998, 0.995, + 1.000, 1.000, 1.000, +}; + +// Generated with https://people.phy.cam.ac.uk/dag9/CUBEHELIX/cubetry.html +// Using: Start color: 2.75, Number of rotations: -0.5, Rotation direction: negative, Hue: 1.0, Gamma: 0.7 +const float ColorMap::m_mint[m_size] = +{ + 0.000, 0.000, 0.000, + 0.010, 0.023, 0.038, + 0.016, 0.037, 0.061, + 0.022, 0.050, 0.080, + 0.026, 0.061, 0.097, + 0.031, 0.071, 0.113, + 0.035, 0.081, 0.127, + 0.038, 0.091, 0.141, + 0.042, 0.100, 0.153, + 0.045, 0.109, 0.165, + 0.048, 0.118, 0.177, + 0.051, 0.127, 0.188, + 0.054, 0.135, 0.198, + 0.057, 0.143, 0.208, + 0.060, 0.151, 0.217, + 0.062, 0.159, 0.227, + 0.065, 0.167, 0.235, + 0.067, 0.175, 0.244, + 0.069, 0.183, 0.252, + 0.072, 0.190, 0.260, + 0.074, 0.198, 0.267, + 0.076, 0.205, 0.275, + 0.078, 0.213, 0.282, + 0.081, 0.220, 0.289, + 0.083, 0.227, 0.295, + 0.085, 0.234, 0.302, + 0.087, 0.241, 0.308, + 0.089, 0.248, 0.314, + 0.091, 0.255, 0.320, + 0.093, 0.262, 0.325, + 0.095, 0.269, 0.331, + 0.097, 0.276, 0.336, + 0.099, 0.282, 0.341, + 0.101, 0.289, 0.346, + 0.103, 0.296, 0.351, + 0.105, 0.302, 0.356, + 0.107, 0.309, 0.360, + 0.109, 0.315, 0.364, + 0.111, 0.322, 0.369, + 0.113, 0.328, 0.373, + 0.115, 0.335, 0.377, + 0.117, 0.341, 0.380, + 0.119, 0.347, 0.384, + 0.121, 0.354, 0.388, + 0.124, 0.360, 0.391, + 0.126, 0.366, 0.394, + 0.128, 0.372, 0.398, + 0.130, 0.378, 0.401, + 0.132, 0.384, 0.404, + 0.135, 0.390, 0.407, + 0.137, 0.396, 0.409, + 0.139, 0.402, 0.412, + 0.141, 0.408, 0.415, + 0.144, 0.413, 0.417, + 0.146, 0.419, 0.420, + 0.149, 0.425, 0.422, + 0.151, 0.431, 0.424, + 0.154, 0.436, 0.426, + 0.156, 0.442, 0.428, + 0.159, 0.447, 0.430, + 0.162, 0.453, 0.432, + 0.164, 0.458, 0.434, + 0.167, 0.464, 0.436, + 0.170, 0.469, 0.438, + 0.172, 0.474, 0.439, + 0.175, 0.480, 0.441, + 0.178, 0.485, 0.443, + 0.181, 0.490, 0.444, + 0.184, 0.495, 0.446, + 0.187, 0.500, 0.447, + 0.190, 0.505, 0.448, + 0.193, 0.510, 0.450, + 0.196, 0.515, 0.451, + 0.200, 0.520, 0.452, + 0.203, 0.525, 0.453, + 0.206, 0.530, 0.454, + 0.210, 0.535, 0.455, + 0.213, 0.540, 0.456, + 0.216, 0.544, 0.457, + 0.220, 0.549, 0.458, + 0.223, 0.554, 0.459, + 0.227, 0.558, 0.460, + 0.231, 0.563, 0.461, + 0.234, 0.567, 0.462, + 0.238, 0.572, 0.463, + 0.242, 0.576, 0.463, + 0.246, 0.581, 0.464, + 0.249, 0.585, 0.465, + 0.253, 0.589, 0.466, + 0.257, 0.593, 0.466, + 0.261, 0.598, 0.467, + 0.265, 0.602, 0.468, + 0.270, 0.606, 0.469, + 0.274, 0.610, 0.469, + 0.278, 0.614, 0.470, + 0.282, 0.618, 0.471, + 0.286, 0.622, 0.471, + 0.291, 0.626, 0.472, + 0.295, 0.630, 0.473, + 0.300, 0.633, 0.473, + 0.304, 0.637, 0.474, + 0.308, 0.641, 0.475, + 0.313, 0.645, 0.475, + 0.318, 0.648, 0.476, + 0.322, 0.652, 0.477, + 0.327, 0.655, 0.478, + 0.332, 0.659, 0.478, + 0.336, 0.663, 0.479, + 0.341, 0.666, 0.480, + 0.346, 0.669, 0.480, + 0.351, 0.673, 0.481, + 0.356, 0.676, 0.482, + 0.361, 0.679, 0.483, + 0.366, 0.683, 0.484, + 0.371, 0.686, 0.485, + 0.376, 0.689, 0.485, + 0.381, 0.692, 0.486, + 0.386, 0.695, 0.487, + 0.391, 0.698, 0.488, + 0.396, 0.701, 0.489, + 0.402, 0.704, 0.490, + 0.407, 0.707, 0.491, + 0.412, 0.710, 0.492, + 0.417, 0.713, 0.493, + 0.423, 0.716, 0.495, + 0.428, 0.719, 0.496, + 0.434, 0.722, 0.497, + 0.439, 0.724, 0.498, + 0.444, 0.727, 0.499, + 0.450, 0.730, 0.501, + 0.455, 0.732, 0.502, + 0.461, 0.735, 0.504, + 0.466, 0.738, 0.505, + 0.472, 0.740, 0.506, + 0.478, 0.743, 0.508, + 0.483, 0.745, 0.509, + 0.489, 0.748, 0.511, + 0.494, 0.750, 0.513, + 0.500, 0.753, 0.514, + 0.506, 0.755, 0.516, + 0.511, 0.757, 0.518, + 0.517, 0.760, 0.520, + 0.523, 0.762, 0.522, + 0.528, 0.764, 0.523, + 0.534, 0.767, 0.525, + 0.540, 0.769, 0.527, + 0.545, 0.771, 0.529, + 0.551, 0.773, 0.532, + 0.557, 0.775, 0.534, + 0.563, 0.778, 0.536, + 0.568, 0.780, 0.538, + 0.574, 0.782, 0.540, + 0.580, 0.784, 0.543, + 0.586, 0.786, 0.545, + 0.591, 0.788, 0.548, + 0.597, 0.790, 0.550, + 0.603, 0.792, 0.553, + 0.608, 0.794, 0.555, + 0.614, 0.796, 0.558, + 0.620, 0.798, 0.561, + 0.626, 0.800, 0.563, + 0.631, 0.802, 0.566, + 0.637, 0.804, 0.569, + 0.643, 0.806, 0.572, + 0.648, 0.808, 0.575, + 0.654, 0.809, 0.578, + 0.660, 0.811, 0.581, + 0.665, 0.813, 0.584, + 0.671, 0.815, 0.587, + 0.676, 0.817, 0.591, + 0.682, 0.819, 0.594, + 0.687, 0.820, 0.597, + 0.693, 0.822, 0.601, + 0.699, 0.824, 0.604, + 0.704, 0.826, 0.608, + 0.709, 0.828, 0.611, + 0.715, 0.829, 0.615, + 0.720, 0.831, 0.618, + 0.726, 0.833, 0.622, + 0.731, 0.835, 0.626, + 0.736, 0.836, 0.630, + 0.742, 0.838, 0.633, + 0.747, 0.840, 0.637, + 0.752, 0.842, 0.641, + 0.757, 0.843, 0.645, + 0.762, 0.845, 0.649, + 0.768, 0.847, 0.653, + 0.773, 0.849, 0.657, + 0.778, 0.850, 0.662, + 0.783, 0.852, 0.666, + 0.788, 0.854, 0.670, + 0.793, 0.856, 0.674, + 0.798, 0.857, 0.679, + 0.802, 0.859, 0.683, + 0.807, 0.861, 0.688, + 0.812, 0.863, 0.692, + 0.817, 0.865, 0.697, + 0.821, 0.866, 0.701, + 0.826, 0.868, 0.706, + 0.831, 0.870, 0.710, + 0.835, 0.872, 0.715, + 0.840, 0.874, 0.720, + 0.844, 0.876, 0.724, + 0.849, 0.877, 0.729, + 0.853, 0.879, 0.734, + 0.857, 0.881, 0.739, + 0.862, 0.883, 0.744, + 0.866, 0.885, 0.749, + 0.870, 0.887, 0.754, + 0.874, 0.889, 0.759, + 0.878, 0.891, 0.764, + 0.882, 0.893, 0.769, + 0.886, 0.895, 0.774, + 0.890, 0.897, 0.779, + 0.894, 0.899, 0.784, + 0.898, 0.901, 0.789, + 0.901, 0.903, 0.794, + 0.905, 0.905, 0.799, + 0.909, 0.907, 0.804, + 0.912, 0.909, 0.810, + 0.916, 0.911, 0.815, + 0.919, 0.913, 0.820, + 0.922, 0.915, 0.825, + 0.926, 0.917, 0.831, + 0.929, 0.920, 0.836, + 0.932, 0.922, 0.841, + 0.935, 0.924, 0.847, + 0.938, 0.926, 0.852, + 0.942, 0.929, 0.857, + 0.944, 0.931, 0.863, + 0.947, 0.933, 0.868, + 0.950, 0.936, 0.873, + 0.953, 0.938, 0.879, + 0.956, 0.940, 0.884, + 0.958, 0.943, 0.889, + 0.961, 0.945, 0.895, + 0.964, 0.948, 0.900, + 0.966, 0.950, 0.905, + 0.968, 0.953, 0.911, + 0.971, 0.955, 0.916, + 0.973, 0.958, 0.921, + 0.975, 0.961, 0.927, + 0.977, 0.963, 0.932, + 0.980, 0.966, 0.937, + 0.982, 0.969, 0.943, + 0.984, 0.971, 0.948, + 0.985, 0.974, 0.953, + 0.987, 0.977, 0.959, + 0.989, 0.980, 0.964, + 0.991, 0.982, 0.969, + 0.993, 0.985, 0.974, + 0.994, 0.988, 0.979, + 0.996, 0.991, 0.985, + 0.997, 0.994, 0.990, + 0.999, 0.997, 0.995, + 1.000, 1.000, 1.000, +}; + +const float ColorMap::m_acab[m_size] = +{ + 0.00000, 0.00000, 0.00000, + 0.01042, 0.01042, 0.01042, + 0.02083, 0.02083, 0.02083, + 0.03125, 0.03125, 0.03125, + 0.04167, 0.04167, 0.04167, + 0.05208, 0.05208, 0.05208, + 0.06250, 0.06250, 0.06250, + 0.07292, 0.07292, 0.07292, + 0.08333, 0.08333, 0.08333, + 0.09375, 0.09375, 0.09375, + 0.10417, 0.10417, 0.10417, + 0.11458, 0.11458, 0.11458, + 0.12500, 0.12500, 0.12500, + 0.13542, 0.13542, 0.13542, + 0.14583, 0.14583, 0.14583, + 0.15625, 0.15625, 0.15625, + 0.16667, 0.16667, 0.16667, + 0.17708, 0.17708, 0.17708, + 0.18750, 0.18750, 0.18750, + 0.19792, 0.19792, 0.19792, + 0.20833, 0.20833, 0.20833, + 0.21875, 0.21875, 0.21875, + 0.22917, 0.22917, 0.22917, + 0.23958, 0.23958, 0.23958, + 0.25000, 0.25000, 0.25000, + 0.26042, 0.26042, 0.26042, + 0.27083, 0.27083, 0.27083, + 0.28125, 0.28125, 0.28125, + 0.29167, 0.29167, 0.29167, + 0.30208, 0.30208, 0.30208, + 0.31250, 0.31250, 0.31250, + 0.32292, 0.32292, 0.32292, + 0.33333, 0.33333, 0.33333, + 0.32292, 0.32292, 0.35417, + 0.31250, 0.31250, 0.37500, + 0.30208, 0.30208, 0.39583, + 0.29167, 0.29167, 0.41667, + 0.28125, 0.28125, 0.43750, + 0.27083, 0.27083, 0.45833, + 0.26042, 0.26042, 0.47917, + 0.25000, 0.25000, 0.50000, + 0.23958, 0.23958, 0.52083, + 0.22917, 0.22917, 0.54167, + 0.21875, 0.21875, 0.56250, + 0.20833, 0.20833, 0.58333, + 0.19792, 0.19792, 0.60417, + 0.18750, 0.18750, 0.62500, + 0.17708, 0.17708, 0.64583, + 0.16667, 0.16667, 0.66667, + 0.15625, 0.15625, 0.68750, + 0.14583, 0.14583, 0.70833, + 0.13542, 0.13542, 0.72917, + 0.12500, 0.12500, 0.75000, + 0.11458, 0.11458, 0.77083, + 0.10417, 0.10417, 0.79167, + 0.09375, 0.09375, 0.81250, + 0.08333, 0.08333, 0.83333, + 0.07292, 0.07292, 0.85417, + 0.06250, 0.06250, 0.87500, + 0.05208, 0.05208, 0.89583, + 0.04167, 0.04167, 0.91667, + 0.03125, 0.03125, 0.93750, + 0.02083, 0.02083, 0.95833, + 0.01042, 0.01042, 0.97917, + 0.00000, 0.00000, 1.00000, + 0.00000, 0.03125, 1.00000, + 0.00000, 0.06250, 1.00000, + 0.00000, 0.09375, 1.00000, + 0.00000, 0.12500, 1.00000, + 0.00000, 0.15625, 1.00000, + 0.00000, 0.18750, 1.00000, + 0.00000, 0.21875, 1.00000, + 0.00000, 0.25000, 1.00000, + 0.00000, 0.28125, 1.00000, + 0.00000, 0.31250, 1.00000, + 0.00000, 0.34375, 1.00000, + 0.00000, 0.37500, 1.00000, + 0.00000, 0.40625, 1.00000, + 0.00000, 0.43750, 1.00000, + 0.00000, 0.46875, 1.00000, + 0.00000, 0.50000, 1.00000, + 0.00000, 0.53125, 1.00000, + 0.00000, 0.56250, 1.00000, + 0.00000, 0.59375, 1.00000, + 0.00000, 0.62500, 1.00000, + 0.00000, 0.65625, 1.00000, + 0.00000, 0.68750, 1.00000, + 0.00000, 0.71875, 1.00000, + 0.00000, 0.75000, 1.00000, + 0.00000, 0.78125, 1.00000, + 0.00000, 0.81250, 1.00000, + 0.00000, 0.84375, 1.00000, + 0.00000, 0.87500, 1.00000, + 0.00000, 0.90625, 1.00000, + 0.00000, 0.93750, 1.00000, + 0.00000, 0.96875, 1.00000, + 0.00000, 1.00000, 1.00000, + 0.00000, 1.00000, 0.96875, + 0.00000, 1.00000, 0.93750, + 0.00000, 1.00000, 0.90625, + 0.00000, 1.00000, 0.87500, + 0.00000, 1.00000, 0.84375, + 0.00000, 1.00000, 0.81250, + 0.00000, 1.00000, 0.78125, + 0.00000, 1.00000, 0.75000, + 0.00000, 1.00000, 0.71875, + 0.00000, 1.00000, 0.68750, + 0.00000, 1.00000, 0.65625, + 0.00000, 1.00000, 0.62500, + 0.00000, 1.00000, 0.59375, + 0.00000, 1.00000, 0.56250, + 0.00000, 1.00000, 0.53125, + 0.00000, 1.00000, 0.50000, + 0.00000, 1.00000, 0.46875, + 0.00000, 1.00000, 0.43750, + 0.00000, 1.00000, 0.40625, + 0.00000, 1.00000, 0.37500, + 0.00000, 1.00000, 0.34375, + 0.00000, 1.00000, 0.31250, + 0.00000, 1.00000, 0.28125, + 0.00000, 1.00000, 0.25000, + 0.00000, 1.00000, 0.21875, + 0.00000, 1.00000, 0.18750, + 0.00000, 1.00000, 0.15625, + 0.00000, 1.00000, 0.12500, + 0.00000, 1.00000, 0.09375, + 0.00000, 1.00000, 0.06250, + 0.00000, 1.00000, 0.03125, + 0.00000, 1.00000, 0.00000, + 0.03125, 1.00000, 0.00000, + 0.06250, 1.00000, 0.00000, + 0.09375, 1.00000, 0.00000, + 0.12500, 1.00000, 0.00000, + 0.15625, 1.00000, 0.00000, + 0.18750, 1.00000, 0.00000, + 0.21875, 1.00000, 0.00000, + 0.25000, 1.00000, 0.00000, + 0.28125, 1.00000, 0.00000, + 0.31250, 1.00000, 0.00000, + 0.34375, 1.00000, 0.00000, + 0.37500, 1.00000, 0.00000, + 0.40625, 1.00000, 0.00000, + 0.43750, 1.00000, 0.00000, + 0.46875, 1.00000, 0.00000, + 0.50000, 1.00000, 0.00000, + 0.53125, 1.00000, 0.00000, + 0.56250, 1.00000, 0.00000, + 0.59375, 1.00000, 0.00000, + 0.62500, 1.00000, 0.00000, + 0.65625, 1.00000, 0.00000, + 0.68750, 1.00000, 0.00000, + 0.71875, 1.00000, 0.00000, + 0.75000, 1.00000, 0.00000, + 0.78125, 1.00000, 0.00000, + 0.81250, 1.00000, 0.00000, + 0.84375, 1.00000, 0.00000, + 0.87500, 1.00000, 0.00000, + 0.90625, 1.00000, 0.00000, + 0.93750, 1.00000, 0.00000, + 0.96875, 1.00000, 0.00000, + 1.00000, 1.00000, 0.00000, + 1.00000, 0.96875, 0.00000, + 1.00000, 0.93750, 0.00000, + 1.00000, 0.90625, 0.00000, + 1.00000, 0.87500, 0.00000, + 1.00000, 0.84375, 0.00000, + 1.00000, 0.81250, 0.00000, + 1.00000, 0.78125, 0.00000, + 1.00000, 0.75000, 0.00000, + 1.00000, 0.71875, 0.00000, + 1.00000, 0.68750, 0.00000, + 1.00000, 0.65625, 0.00000, + 1.00000, 0.62500, 0.00000, + 1.00000, 0.59375, 0.00000, + 1.00000, 0.56250, 0.00000, + 1.00000, 0.53125, 0.00000, + 1.00000, 0.50000, 0.00000, + 1.00000, 0.46875, 0.00000, + 1.00000, 0.43750, 0.00000, + 1.00000, 0.40625, 0.00000, + 1.00000, 0.37500, 0.00000, + 1.00000, 0.34375, 0.00000, + 1.00000, 0.31250, 0.00000, + 1.00000, 0.28125, 0.00000, + 1.00000, 0.25000, 0.00000, + 1.00000, 0.21875, 0.00000, + 1.00000, 0.18750, 0.00000, + 1.00000, 0.15625, 0.00000, + 1.00000, 0.12500, 0.00000, + 1.00000, 0.09375, 0.00000, + 1.00000, 0.06250, 0.00000, + 1.00000, 0.03125, 0.00000, + 1.00000, 0.00000, 0.00000, + 1.00000, 0.00000, 0.03125, + 1.00000, 0.00000, 0.06250, + 1.00000, 0.00000, 0.09375, + 1.00000, 0.00000, 0.12500, + 1.00000, 0.00000, 0.15625, + 1.00000, 0.00000, 0.18750, + 1.00000, 0.00000, 0.21875, + 1.00000, 0.00000, 0.25000, + 1.00000, 0.00000, 0.28125, + 1.00000, 0.00000, 0.31250, + 1.00000, 0.00000, 0.34375, + 1.00000, 0.00000, 0.37500, + 1.00000, 0.00000, 0.40625, + 1.00000, 0.00000, 0.43750, + 1.00000, 0.00000, 0.46875, + 1.00000, 0.00000, 0.50000, + 1.00000, 0.00000, 0.53125, + 1.00000, 0.00000, 0.56250, + 1.00000, 0.00000, 0.59375, + 1.00000, 0.00000, 0.62500, + 1.00000, 0.00000, 0.65625, + 1.00000, 0.00000, 0.68750, + 1.00000, 0.00000, 0.71875, + 1.00000, 0.00000, 0.75000, + 1.00000, 0.00000, 0.78125, + 1.00000, 0.00000, 0.81250, + 1.00000, 0.00000, 0.84375, + 1.00000, 0.00000, 0.87500, + 1.00000, 0.00000, 0.90625, + 1.00000, 0.00000, 0.93750, + 1.00000, 0.00000, 0.96875, + 1.00000, 0.00000, 1.00000, + 1.00000, 0.03125, 1.00000, + 1.00000, 0.06250, 1.00000, + 1.00000, 0.09375, 1.00000, + 1.00000, 0.12500, 1.00000, + 1.00000, 0.15625, 1.00000, + 1.00000, 0.18750, 1.00000, + 1.00000, 0.21875, 1.00000, + 1.00000, 0.25000, 1.00000, + 1.00000, 0.28125, 1.00000, + 1.00000, 0.31250, 1.00000, + 1.00000, 0.34375, 1.00000, + 1.00000, 0.37500, 1.00000, + 1.00000, 0.40625, 1.00000, + 1.00000, 0.43750, 1.00000, + 1.00000, 0.46875, 1.00000, + 1.00000, 0.50000, 1.00000, + 1.00000, 0.53125, 1.00000, + 1.00000, 0.56250, 1.00000, + 1.00000, 0.59375, 1.00000, + 1.00000, 0.62500, 1.00000, + 1.00000, 0.65625, 1.00000, + 1.00000, 0.68750, 1.00000, + 1.00000, 0.71875, 1.00000, + 1.00000, 0.75000, 1.00000, + 1.00000, 0.78125, 1.00000, + 1.00000, 0.81250, 1.00000, + 1.00000, 0.84375, 1.00000, + 1.00000, 0.87500, 1.00000, + 1.00000, 0.90625, 1.00000, + 1.00000, 0.93750, 1.00000, + 1.00000, 0.96875, 1.00000, +}; diff --git a/android/app/src/main/cpp/util/colormap.h b/android/app/src/main/cpp/util/colormap.h new file mode 100644 index 0000000..ae7399b --- /dev/null +++ b/android/app/src/main/cpp/util/colormap.h @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2017, 2019-2020, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_COLORMAP_H +#define INCLUDE_COLORMAP_H + +#include "export.h" + +#include + +// 256-entry floating point RGB color maps. +// "Angel" is SDRangel's waterfall colormap +// Some common maps from matplotlib +// Scientific colour maps from: +// https://www.nature.com/articles/s41467-020-19160-7 +// https://zenodo.org/record/5501399#.YqhaAu7MLAQ +class SDRBASE_API ColorMap +{ +public: + static QStringList getColorMapNames(); + static const float *getColorMap(const QString &name); + static constexpr int m_size = 256*3; + +private: + static QHash m_colorMaps; + static const float m_angel[m_size]; + static const float m_jet[m_size]; + static const float m_turbo[m_size]; + static const float m_parula[m_size]; + static const float m_hot[m_size]; + static const float m_cool[m_size]; + static const float m_batlow[m_size]; + static const float m_hawaii[m_size]; + static const float m_acton[m_size]; + static const float m_imola[m_size]; + static const float m_tokyo[m_size]; + static const float m_lapaz[m_size]; + static const float m_buda[m_size]; + static const float m_devon[m_size]; + static const float m_lajolla[m_size]; + static const float m_bamako[m_size]; + static const float m_plasma[m_size]; + static const float m_rainbow[m_size]; + static const float m_prism[m_size]; + static const float m_viridis[m_size]; + static const float m_loggray[m_size]; + static const float m_shrimp[m_size]; + static const float m_cubehelix[m_size]; + static const float m_cubegamma[m_size]; + static const float m_cubehlx2[m_size]; + static const float m_icy[m_size]; + static const float m_mint[m_size]; + static const float m_acab[m_size]; +}; + +#endif diff --git a/android/app/src/main/cpp/util/coordinates.cpp b/android/app/src/main/cpp/util/coordinates.cpp new file mode 100644 index 0000000..a175b27 --- /dev/null +++ b/android/app/src/main/cpp/util/coordinates.cpp @@ -0,0 +1,318 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// Copyright (C) 2011-2020 Cesium Contributors // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "coordinates.h" +#include "units.h" + +// Scale cartesian position on to surface of ellipsoid +QVector3D Coordinates::scaleToGeodeticSurface(QVector3D cartesian, QVector3D oneOverRadii, QVector3D oneOverRadiiSquared) +{ + float centerToleranceSquared = 0.1; + + double x2 = cartesian.x() * cartesian.x() * oneOverRadii.x() * oneOverRadii.x(); + double y2 = cartesian.y() * cartesian.y() * oneOverRadii.y() * oneOverRadii.y(); + double z2 = cartesian.z() * cartesian.z() * oneOverRadii.z() * oneOverRadii.z(); + + double squaredNorm = x2 + y2 + z2; + double ratio = sqrt(1.0 / squaredNorm); + + QVector3D intersection = cartesian * ratio; + + if (squaredNorm < centerToleranceSquared) { + return intersection; + } + + QVector3D gradient( + intersection.x() * oneOverRadiiSquared.x() * 2.0, + intersection.y() * oneOverRadiiSquared.y() * 2.0, + intersection.z() * oneOverRadiiSquared.z() * 2.0 + ); + + double lambda = ((1.0 - ratio) * cartesian.length()) / (0.5 * gradient.length()); + + double correction = 0.0; + double func; + double denominator; + double xMultiplier; + double yMultiplier; + double zMultiplier; + double xMultiplier2; + double yMultiplier2; + double zMultiplier2; + double xMultiplier3; + double yMultiplier3; + double zMultiplier3; + + do + { + lambda -= correction; + + xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquared.x()); + yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquared.y()); + zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquared.z()); + + xMultiplier2 = xMultiplier * xMultiplier; + yMultiplier2 = yMultiplier * yMultiplier; + zMultiplier2 = zMultiplier * zMultiplier; + + xMultiplier3 = xMultiplier2 * xMultiplier; + yMultiplier3 = yMultiplier2 * yMultiplier; + zMultiplier3 = zMultiplier2 * zMultiplier; + + func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0; + + denominator = + x2 * xMultiplier3 * oneOverRadiiSquared.x() + + y2 * yMultiplier3 * oneOverRadiiSquared.y() + + z2 * zMultiplier3 * oneOverRadiiSquared.z(); + + double derivative = -2.0 * denominator; + + correction = func / derivative; + } + while (abs(func) > 0.000000000001); + + QVector3D result( + cartesian.x() * xMultiplier, + cartesian.y() * yMultiplier, + cartesian.z() * zMultiplier + ); + return result; +} + +// QVector3D.normalized doesn't work with small numbers +QVector3D Coordinates::normalized(QVector3D vec) +{ + QVector3D result; + float magnitude = vec.length(); + result.setX(vec.x() / magnitude); + result.setY(vec.y() / magnitude); + result.setZ(vec.z() / magnitude); + return result; +} + +// Convert ECEF position to geodetic coordinates +void Coordinates::ecefToGeodetic(double x, double y, double z, double &latitude, double &longitude, double &height) +{ + QVector3D wgs84OneOverRadix(1.0 / 6378137.0, + 1.0 / 6378137.0, + 1.0 / 6356752.3142451793); + QVector3D wgs84OneOverRadiiSquared(1.0 / (6378137.0 * 6378137.0), + 1.0 / (6378137.0 * 6378137.0), + 1.0 / (6356752.3142451793 * 6356752.3142451793)); + + QVector3D cartesian(x, y, z); + + QVector3D p = scaleToGeodeticSurface(cartesian, wgs84OneOverRadix, wgs84OneOverRadiiSquared); + + QVector3D n = p * wgs84OneOverRadiiSquared; + n = normalized(n); + + QVector3D h = cartesian - p; + + longitude = atan2(n.y(), n.x()); + latitude = asin(n.z()); + + longitude = Units::radiansToDegrees(longitude); + latitude = Units::radiansToDegrees(latitude); + + double t = QVector3D::dotProduct(h, cartesian); + double sign = t >= 0.0 ? 1.0 : 0.0; + height = sign * h.length(); +} + +// Convert ECEF velocity to speed and heading +void Coordinates::ecefVelToSpeedHeading(double latitude, double longitude, + double velX, double velY, double velZ, + double &speed, double &verticalRate, double &heading) +{ + if ((velX == 0.0) && (velY == 0.0) && (velZ == 0.0)) + { + speed = 0.0; + heading = 0.0; + verticalRate = 0.0; + return; + } + + double latRad = Units::degreesToRadians(latitude); + double lonRad = Units::degreesToRadians(longitude); + + double sinLat = sin(latRad); + double cosLat = cos(latRad); + double sinLon = sin(lonRad); + double cosLon = cos(lonRad); + + double velEast = -velX * sinLon + velY * cosLon; + double velNorth = -velX * sinLat * cosLon - velY * sinLat * sinLon + velZ * cosLat; + double velUp = velX * cosLat * cosLon + velY * cosLat * sinLon + velZ * sinLat; + + speed = sqrt(velNorth * velNorth + velEast * velEast); + verticalRate = velUp; + + double headingRad = atan2(velEast, velNorth); + heading = Units::radiansToDegrees(headingRad); + if (heading < 0.0) { + heading += 360.0; + } else if (heading >= 360.0) { + heading -= 360.0; + } +} + +// Convert a position specified in longitude, latitude in degrees and height in metres above WGS84 ellipsoid in to +// Earth Centered Earth Fixed frame cartesian coordinates +// See Cesium.Cartesian3.fromDegrees +QVector3D Coordinates::geodeticToECEF(double longitude, double latitude, double height) +{ + return geodeticRadiansToECEF(Units::degreesToRadians(longitude), Units::degreesToRadians(latitude), height); +} + +// FIXME: QVector3D is only float! +// See Cesium.Cartesian3.fromRadians +QVector3D Coordinates::geodeticRadiansToECEF(double longitude, double latitude, double height) +{ + QVector3D wgs84RadiiSquared(6378137.0 * 6378137.0, 6378137.0 * 6378137.0, 6356752.3142451793 * 6356752.3142451793); + + double cosLatitude = cos(latitude); + QVector3D n; + n.setX(cosLatitude * cos(longitude)); + n.setY(cosLatitude * sin(longitude)); + n.setZ(sin(latitude)); + n.normalize(); + QVector3D k; + k = wgs84RadiiSquared * n; + double gamma = sqrt(QVector3D::dotProduct(n, k)); + k = k / gamma; + n = n * height; + return k + n; +} + +// Convert heading, pitch and roll in degrees to a quaternoin +// See: Cesium.Quaternion.fromHeadingPitchRoll +QQuaternion Coordinates::fromHeadingPitchRoll(double heading, double pitch, double roll) +{ + QVector3D xAxis(1, 0, 0); + QVector3D yAxis(0, 1, 0); + QVector3D zAxis(0, 0, 1); + + QQuaternion rollQ = QQuaternion::fromAxisAndAngle(xAxis, roll); + + QQuaternion pitchQ = QQuaternion::fromAxisAndAngle(yAxis, -pitch); + + QQuaternion headingQ = QQuaternion::fromAxisAndAngle(zAxis, -heading); + + QQuaternion temp = rollQ * pitchQ; + + return headingQ * temp; +} + +// Calculate a transformation matrix from a East, North, Up frame at the given position to Earth Centered Earth Fixed frame +// See: Cesium.Transforms.eastNorthUpToFixedFrame +QMatrix4x4 Coordinates::eastNorthUpToECEF(QVector3D origin) +{ + // TODO: Handle special case at centre of earth and poles + QVector3D up = origin.normalized(); + QVector3D east(-origin.y(), origin.x(), 0.0); + east.normalize(); + QVector3D north = QVector3D::crossProduct(up, east); + QMatrix4x4 result( + east.x(), north.x(), up.x(), origin.x(), + east.y(), north.y(), up.y(), origin.y(), + east.z(), north.z(), up.z(), origin.z(), + 0.0, 0.0, 0.0, 1.0 + ); + return result; +} + +// Convert 3x3 rotation matrix to a quaternoin +// Although there is a method for this in Qt: QQuaternion::fromRotationMatrix, it seems to +// result in different signs, so the following is based on Cesium code +QQuaternion Coordinates::fromRotation(QMatrix3x3 mat) +{ + QQuaternion q; + + double trace = mat(0, 0) + mat(1, 1) + mat(2, 2); + + if (trace > 0.0) + { + double root = sqrt(trace + 1.0); + q.setScalar(0.5 * root); + root = 0.5 / root; + + q.setX((mat(2,1) - mat(1,2)) * root); + q.setY((mat(0,2) - mat(2,0)) * root); + q.setZ((mat(1,0) - mat(0,1)) * root); + } + else + { + double next[] = {1, 2, 0}; + int i = 0; + if (mat(1,1) > mat(0,0)) { + i = 1; + } + if (mat(2,2) > mat(0,0) && mat(2,2) > mat(1,1)) { + i = 2; + } + int j = next[i]; + int k = next[j]; + + double root = sqrt(mat(i,i) - mat(j,j) - mat(k,k) + 1); + double quat[] = {0.0, 0.0, 0.0}; + quat[i] = 0.5 * root; + root = 0.5 / root; + + q.setScalar((mat(j,k) - mat(k,j)) * root); + quat[j] = (mat(i,j) + mat(j,i)) * root; + quat[k] = (mat(i,k) + mat(k,i)) * root; + q.setX(-quat[0]); + q.setY(-quat[1]); + q.setZ(-quat[2]); + } + return q; +} + +// Calculate orientation quaternion for a model (such as an aircraft) based on position and (HPR) heading, pitch and roll (in degrees) +// While Cesium supports specifying orientation as HPR, CZML doesn't currently. See https://github.com/CesiumGS/cesium/issues/5184 +// CZML requires the orientation to be in the Earth Centered Earth Fixed (geocentric) reference frame (https://en.wikipedia.org/wiki/Local_tangent_plane_coordinates) +// The orientation therefore depends not only on HPR but also on position +// +// glTF uses a right-handed axis convention; that is, the cross product of right and forward yields up. glTF defines +Y as up, +Z as forward, and -X as right. +// Cesium.Quaternion.fromHeadingPitchRoll Heading is the rotation about the negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about the positive x axis. +QQuaternion Coordinates::orientation(double longitude, double latitude, double altitude, double heading, double pitch, double roll) +{ + // Forward direction for gltf models in Cesium seems to be Eastward, rather than Northward, so we adjust heading by -90 degrees + heading = -90 + heading; + + // Convert position to Earth Centered Earth Fixed (ECEF) frame + QVector3D positionECEF = geodeticToECEF(longitude, latitude, altitude); + + // Calculate matrix to transform from East, North, Up (ENU) frame to ECEF frame + QMatrix4x4 enuToECEFTransform = eastNorthUpToECEF(positionECEF); + + // Calculate rotation based on HPR in ENU frame + QQuaternion hprENU = fromHeadingPitchRoll(heading, pitch, roll); + + // Transform rotation from ENU to ECEF + QMatrix3x3 hprENU3 = hprENU.toRotationMatrix(); + QMatrix4x4 hprENU4(hprENU3); + QMatrix4x4 transform = enuToECEFTransform * hprENU4; + + // Convert from 4x4 matrix to 3x3 matrix then to a quaternion + QQuaternion oq = fromRotation(transform.toGenericMatrix<3,3>()); + + return oq; +} diff --git a/android/app/src/main/cpp/util/coordinates.h b/android/app/src/main/cpp/util/coordinates.h new file mode 100644 index 0000000..2a045af --- /dev/null +++ b/android/app/src/main/cpp/util/coordinates.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2017, 2019-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_COORDINATES_H +#define INCLUDE_COORDINATES_H + +#include "export.h" + +#include +#include +#include +#include +#include + +// Functions for transformations between geodetic and ECEF coordinates +class SDRBASE_API Coordinates { + +public: + + static QVector3D geodeticToECEF(double longitude, double latitude, double height=0.0); + static QVector3D geodeticRadiansToECEF(double longitude, double latitude, double height=0.0); + static QMatrix4x4 eastNorthUpToECEF(QVector3D origin); + static void ecefToGeodetic(double x, double y, double z, double &latitude, double &longitude, double &height); + static void ecefVelToSpeedHeading(double latitude, double longitude, + double velX, double velY, double velZ, + double &speed, double &verticalRate, double &heading); + static QQuaternion orientation(double longitude, double latitude, double altitude, double heading, double pitch, double roll); + +protected: + + static QVector3D scaleToGeodeticSurface(QVector3D cartesian, QVector3D oneOverRadii, QVector3D oneOverRadiiSquared); + static QVector3D normalized(QVector3D vec); + static QQuaternion fromHeadingPitchRoll(double heading, double pitch, double roll); + static QQuaternion fromRotation(QMatrix3x3 mat); + +}; + +#endif // INCLUDE_COORDINATES_H diff --git a/android/app/src/main/cpp/util/corsproxy.cpp b/android/app/src/main/cpp/util/corsproxy.cpp new file mode 100644 index 0000000..ca9961d --- /dev/null +++ b/android/app/src/main/cpp/util/corsproxy.cpp @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "corsproxy.h" + +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +QString CORSProxy::adjustHost(const QString& url) +{ +#ifdef __EMSCRIPTEN__ + return CORSProxy::adjustHost(QUrl(url)).toString(); +#else + return url; +#endif +} + +// Some servers don't support CORS, so we assume proxy running on same server SDRangel is on +// Look at using https://corsproxy.io/ as alternative - although text only +// E.g: https://corsproxy.io/?https://db.satnogs.org/api/satellites/ +QUrl CORSProxy::adjustHost(const QUrl& url) +{ +#ifdef __EMSCRIPTEN__ + QUrl requestURL(url); + + emscripten::val location = emscripten::val::global("location"); + QString proxyHost = QString::fromStdString(location["hostname"].as()); + + // sdrangel.org doesn't currently support proxying + if (proxyHost == "www.sdrangel.org") { + proxyHost = "sdrangel.beniston.com"; + } + + QString host = requestURL.host(); + static const QStringList hosts = { + "db.satnogs.org", + "sdo.gsfc.nasa.gov", + "www.amsat.org", + "datacenter.stix.i4ds.net", + "user-web.icecube.wisc.edu", + "www.sws.bom.gov.au", + "www.spaceweather.gc.ca", + "airspy.com", + "kiwisdr.com", + "opensky-network.org", + "storage.googleapis.com" + }; + if (hosts.contains(host)) + { + requestURL.setHost(proxyHost); + QString newPath = "/" + host + requestURL.path(); + requestURL.setPath(newPath); + } + + return requestURL; + +#else + return url; +#endif +} diff --git a/android/app/src/main/cpp/util/corsproxy.h b/android/app/src/main/cpp/util/corsproxy.h new file mode 100644 index 0000000..510571b --- /dev/null +++ b/android/app/src/main/cpp/util/corsproxy.h @@ -0,0 +1,35 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_CORSPROXY_H +#define INCLUDE_UTIL_CORSPROXY_H + +#include +#include + +#include "export.h" + +class SDRBASE_API CORSProxy +{ +public: + + static QString adjustHost(const QString& url); + static QUrl adjustHost(const QUrl& url); + +}; + +#endif // INCLUDE_UTIL_CORSPROXY_H diff --git a/android/app/src/main/cpp/util/countrydat.cpp b/android/app/src/main/cpp/util/countrydat.cpp new file mode 100644 index 0000000..1974369 --- /dev/null +++ b/android/app/src/main/cpp/util/countrydat.cpp @@ -0,0 +1,622 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// This source code file was last time modified by Arvo ES1JA on 20190224 +// All changes are shown in the patch file coming together with the full JTDX source code. + +/* +#Sov Mil Order of Malta: 15: 28: EU: 41.90: -12.43: -1.0: 1A: + #1A; +#Spratly Islands: 26: 50: AS: 9.88: -114.23: -8.0: 1S: + #1S,9M0,BV9S; +#Monaco: 14: 27: EU: 43.73: -7.40: -1.0: 3A: + #3A; +#Heard Island: 39: 68: AF: -53.08: -73.50: -5.0: VK0H: + #=VK0IR; +#Macquarie Island: 30: 60: OC: -54.60: -158.88: -10.0: VK0M: + #=VK0KEV; +#Cocos-Keeling: 29: 54: OC: -12.15: -96.82: -6.5: VK9C: + #AX9C,AX9Y,VH9C,VH9Y,VI9C,VI9Y,VJ9C,VJ9Y,VK9C,VK9Y,VL9C,VL9Y,VM9C,VM9Y, + #VN9C,VN9Y,VZ9C,VZ9Y,=VK9AA; +*/ + + + +#include "countrydat.h" +#include +#include + +const CountryDat::CountryInfo CountryDat::nullCountry = CountryDat::CountryInfo{"", "?", "where?", "", ""}; + +void CountryDat::init() +{ + _countries.clear(); + _name.clear(); + _name.insert("where?","where?"); + _name.insert("Sov Mil Order of Malta","Sov Mil Order of Malta"); + _name.insert("Spratly Islands","Spratly Is."); + _name.insert("Monaco","Monaco"); + _name.insert("Agalega & St. Brandon","Agalega & St. Brandon"); + _name.insert("Mauritius","Mauritius"); + _name.insert("Rodriguez Island","Rodriguez Is."); + _name.insert("Equatorial Guinea","Equatorial Guinea"); + _name.insert("Annobon Island","Annobon Is."); + _name.insert("Fiji","Fiji"); + _name.insert("Conway Reef","Conway Reef"); + _name.insert("Rotuma Island","Rotuma Is."); + _name.insert("Kingdom of Eswatini","Kingdom of Eswatini"); + _name.insert("Tunisia","Tunisia"); + _name.insert("Vietnam","Vietnam"); + _name.insert("Guinea","Guinea"); + _name.insert("Bouvet","Bouvet"); + _name.insert("Peter 1 Island","Peter 1 Is."); + _name.insert("Azerbaijan","Azerbaijan"); + _name.insert("Georgia","Georgia"); + _name.insert("Montenegro","Montenegro"); + _name.insert("Sri Lanka","Sri Lanka"); + _name.insert("ITU HQ","ITU HQ"); + _name.insert("United Nations HQ","United Nations HQ"); + _name.insert("Vienna Intl Ctr","Vienna Intl Ctr"); + _name.insert("Timor - Leste","Timor - Leste"); + _name.insert("Israel","Israel"); + _name.insert("Libya","Libya"); + _name.insert("Cyprus","Cyprus"); + _name.insert("Tanzania","Tanzania"); + _name.insert("Nigeria","Nigeria"); + _name.insert("Madagascar","Madagascar"); + _name.insert("Mauritania","Mauritania"); + _name.insert("Niger","Niger"); + _name.insert("Togo","Togo"); + _name.insert("Samoa","Samoa"); + _name.insert("Uganda","Uganda"); + _name.insert("Kenya","Kenya"); + _name.insert("Senegal","Senegal"); + _name.insert("Jamaica","Jamaica"); + _name.insert("Yemen","Yemen"); + _name.insert("Lesotho","Lesotho"); + _name.insert("Malawi","Malawi"); + _name.insert("Algeria","Algeria"); + _name.insert("Barbados","Barbados"); + _name.insert("Maldives","Maldives"); + _name.insert("Guyana","Guyana"); + _name.insert("Croatia","Croatia"); + _name.insert("Ghana","Ghana"); + _name.insert("Malta","Malta"); + _name.insert("Zambia","Zambia"); + _name.insert("Kuwait","Kuwait"); + _name.insert("Sierra Leone","Sierra Leone"); + _name.insert("West Malaysia","W. Malaysia"); + _name.insert("East Malaysia","E. Malaysia"); + _name.insert("Nepal","Nepal"); + _name.insert("Dem. Rep. of the Congo","Dem. Rep. of the Congo"); + _name.insert("Burundi","Burundi"); + _name.insert("Singapore","Singapore"); + _name.insert("Rwanda","Rwanda"); + _name.insert("Trinidad & Tobago","Trinidad & Tobago"); + _name.insert("Botswana","Botswana"); + _name.insert("Tonga","Tonga"); + _name.insert("Oman","Oman"); + _name.insert("Bhutan","Bhutan"); + _name.insert("United Arab Emirates","United Arab Emirates"); + _name.insert("Qatar","Qatar"); + _name.insert("Bahrain","Bahrain"); + _name.insert("Pakistan","Pakistan"); + _name.insert("Scarborough Reef","Scarborough Reef"); + _name.insert("Taiwan","Taiwan"); + _name.insert("Pratas Island","Pratas Is."); + _name.insert("China","China"); + _name.insert("Nauru","Nauru"); + _name.insert("Andorra","Andorra"); + _name.insert("The Gambia","The Gambia"); + _name.insert("Bahamas","Bahamas"); + _name.insert("Mozambique","Mozambique"); + _name.insert("Chile","Chile"); + _name.insert("San Felix & San Ambrosio","San Felix & San Ambrosio"); + _name.insert("Easter Island","Easter Is."); + _name.insert("Juan Fernandez Islands","Juan Fernandez Is."); + _name.insert("Antarctica","Antarctica"); + _name.insert("Cuba","Cuba"); + _name.insert("Morocco","Morocco"); + _name.insert("Bolivia","Bolivia"); + _name.insert("Portugal","Portugal"); + _name.insert("Madeira Islands","Madeira Is."); + _name.insert("Azores","Azores"); + _name.insert("Uruguay","Uruguay"); + _name.insert("Sable Island","Sable Is."); + _name.insert("St. Paul Island","St. Paul Is."); + _name.insert("Angola","Angola"); + _name.insert("Cape Verde","Cape Verde"); + _name.insert("Comoros","Comoros"); + _name.insert("Fed. Rep. of Germany","Germany"); + _name.insert("Philippines","Philippines"); + _name.insert("Eritrea","Eritrea"); + _name.insert("Palestine","Palestine"); + _name.insert("North Cook Islands","N. Cook Is."); + _name.insert("South Cook Islands","S. Cook Is."); + _name.insert("Niue","Niue"); + _name.insert("Bosnia-Herzegovina","Bosnia-Herzegovina"); + _name.insert("Spain","Spain"); + _name.insert("Balearic Islands","Balearic Is."); + _name.insert("Canary Islands","Canary Is."); + _name.insert("Ceuta & Melilla","Ceuta & Melilla"); + _name.insert("Ireland","Ireland"); + _name.insert("Armenia","Armenia"); + _name.insert("Liberia","Liberia"); + _name.insert("Iran","Iran"); + _name.insert("Moldova","Moldova"); + _name.insert("Estonia","Estonia"); + _name.insert("Ethiopia","Ethiopia"); + _name.insert("Belarus","Belarus"); + _name.insert("Kyrgyzstan","Kyrgyzstan"); + _name.insert("Tajikistan","Tajikistan"); + _name.insert("Turkmenistan","Turkmenistan"); + _name.insert("France","France"); + _name.insert("Guadeloupe","Guadeloupe"); + _name.insert("Mayotte","Mayotte"); + _name.insert("St. Barthelemy","St. Barthelemy"); + _name.insert("New Caledonia","New Caledonia"); + _name.insert("Chesterfield Islands","Chesterfield Is."); + _name.insert("Martinique","Martinique"); + _name.insert("French Polynesia","Fr. Polynesia"); + _name.insert("Austral Islands","Austral Is."); + _name.insert("Clipperton Island","Clipperton Is."); + _name.insert("Marquesas Islands","Marquesas Is."); + _name.insert("St. Pierre & Miquelo","St. Pierre & Miquelo"); + _name.insert("Reunion Island","Reunion Is."); + _name.insert("St. Martin","St. Martin"); + _name.insert("Glorioso Islands","Glorioso Is."); + _name.insert("Juan de Nova, Europa","Juan de Nova, Europa"); + _name.insert("Tromelin Island","Tromelin Is."); + _name.insert("Crozet Island","Crozet Is."); + _name.insert("Kerguelen Islands","Kerguelen Is."); + _name.insert("Amsterdam & St. Paul Is.","Amsterdam & St. Paul Is."); + _name.insert("Wallis & Futuna Islands","Wallis & Futuna Is."); + _name.insert("French Guiana","Fr. Guiana"); + _name.insert("England","England"); + _name.insert("Isle of Man","Isle of Man"); + _name.insert("Northern Ireland","N. Ireland"); + _name.insert("Jersey","Jersey"); + _name.insert("Shetland Islands","Shetland Is."); + _name.insert("Scotland","Scotland"); + _name.insert("Guernsey","Guernsey"); + _name.insert("Wales","Wales"); + _name.insert("Solomon Islands","Solomon Is."); + _name.insert("Temotu Province","Temotu Province"); + _name.insert("Hungary","Hungary"); + _name.insert("Switzerland","Switzerland"); + _name.insert("Liechtenstein","Liechtenstein"); + _name.insert("Ecuador","Ecuador"); + _name.insert("Galapagos Islands","Galapagos Is."); + _name.insert("Haiti","Haiti"); + _name.insert("Dominican Republic","Dominican Rep."); + _name.insert("Colombia","Colombia"); + _name.insert("San Andres & Providencia","San Andres & Providencia"); + _name.insert("Malpelo Island","Malpelo Is."); + _name.insert("Republic of Korea","Rep. of Korea"); + _name.insert("Panama","Panama"); + _name.insert("Honduras","Honduras"); + _name.insert("Thailand","Thailand"); + _name.insert("Vatican City","Vatican City"); + _name.insert("Saudi Arabia","Saudi Arabia"); + _name.insert("Italy","Italy"); + _name.insert("African Italy","AF Italy"); + _name.insert("Sardinia","Sardinia"); + _name.insert("Sicily","Sicily"); + _name.insert("Djibouti","Djibouti"); + _name.insert("Grenada","Grenada"); + _name.insert("Guinea-Bissau","Guinea-Bissau"); + _name.insert("St. Lucia","St. Lucia"); + _name.insert("Dominica","Dominica"); + _name.insert("St. Vincent","St. Vincent"); + _name.insert("Japan","Japan"); + _name.insert("Minami Torishima","Minami Torishima"); + _name.insert("Ogasawara","Ogasawara"); + _name.insert("Mongolia","Mongolia"); + _name.insert("Svalbard","Svalbard"); + _name.insert("Bear Island","Bear Is."); + _name.insert("Jan Mayen","Jan Mayen"); + _name.insert("Jordan","Jordan"); + _name.insert("United States","U.S.A."); + _name.insert("Guantanamo Bay","Guantanamo Bay"); + _name.insert("Mariana Islands","Mariana Is."); + _name.insert("Baker & Howland Islands","Baker & Howland Is."); + _name.insert("Guam","Guam"); + _name.insert("Johnston Island","Johnston Is."); + _name.insert("Midway Island","Midway Is."); + _name.insert("Palmyra & Jarvis Islands","Palmyra & Jarvis Is."); + _name.insert("Hawaii","Hawaii"); + _name.insert("Kure Island","Kure Is."); + _name.insert("American Samoa","American Samoa"); + _name.insert("Swains Island","Swains Is."); + _name.insert("Wake Island","Wake Is."); + _name.insert("Alaska","Alaska"); + _name.insert("Navassa Island","Navassa Is."); + _name.insert("US Virgin Islands","US Virgin Is."); + _name.insert("Puerto Rico","Puerto Rico"); + _name.insert("Desecheo Island","Desecheo Is."); + _name.insert("Norway","Norway"); + _name.insert("Argentina","Argentina"); + _name.insert("Luxembourg","Luxembourg"); + _name.insert("Lithuania","Lithuania"); + _name.insert("Bulgaria","Bulgaria"); + _name.insert("Peru","Peru"); + _name.insert("Lebanon","Lebanon"); + _name.insert("Austria","Austria"); + _name.insert("Finland","Finland"); + _name.insert("Aland Islands","Aland Is."); + _name.insert("Market Reef","Market Reef"); + _name.insert("Czech Republic","Czech Rep."); + _name.insert("Slovak Republic","Slovak Rep."); + _name.insert("Belgium","Belgium"); + _name.insert("Greenland","Greenland"); + _name.insert("Faroe Islands","Faroe Is."); + _name.insert("Denmark","Denmark"); + _name.insert("Papua New Guinea","Papua New Guinea"); + _name.insert("Aruba","Aruba"); + _name.insert("DPR of Korea","DPR of Korea"); + _name.insert("Netherlands","Netherlands"); + _name.insert("Curacao","Curacao"); + _name.insert("Bonaire","Bonaire"); + _name.insert("Saba & St. Eustatius","Saba & St. Eustatius"); + _name.insert("Sint Maarten","Sint Maarten"); + _name.insert("Brazil","Brazil"); + _name.insert("Fernando de Noronha","Fernando de Noronha"); + _name.insert("St. Peter & St. Paul","St. Peter & St. Paul"); + _name.insert("Trindade & Martim Vaz","Trindade & Martim Vaz"); + _name.insert("Suriname","Suriname"); + _name.insert("Franz Josef Land","Franz Josef Land"); + _name.insert("Western Sahara","Western Sahara"); + _name.insert("Bangladesh","Bangladesh"); + _name.insert("Slovenia","Slovenia"); + _name.insert("Seychelles","Seychelles"); + _name.insert("Sao Tome & Principe","Sao Tome & Principe"); + _name.insert("Sweden","Sweden"); + _name.insert("Poland","Poland"); + _name.insert("Sudan","Sudan"); + _name.insert("Egypt","Egypt"); + _name.insert("Greece","Greece"); + _name.insert("Mount Athos","Mount Athos"); + _name.insert("Dodecanese","Dodecanese"); + _name.insert("Crete","Crete"); + _name.insert("Tuvalu","Tuvalu"); + _name.insert("Western Kiribati","W. Kiribati"); + _name.insert("Central Kiribati","C. Kiribati"); + _name.insert("Eastern Kiribati","E. Kiribati"); + _name.insert("Banaba Island","Banaba Is."); + _name.insert("Somalia","Somalia"); + _name.insert("San Marino","San Marino"); + _name.insert("Palau","Palau"); + _name.insert("Asiatic Turkey","AS Turkey"); + _name.insert("European Turkey","EU Turkey"); + _name.insert("Iceland","Iceland"); + _name.insert("Guatemala","Guatemala"); + _name.insert("Costa Rica","Costa Rica"); + _name.insert("Cocos Island","Cocos Is."); + _name.insert("Cameroon","Cameroon"); + _name.insert("Corsica","Corsica"); + _name.insert("Central African Republic","C. African Rep."); + _name.insert("Republic of the Congo","Rep. of the Congo"); + _name.insert("Gabon","Gabon"); + _name.insert("Chad","Chad"); + _name.insert("Cote d'Ivoire","Cote d'Ivoire"); + _name.insert("Benin","Benin"); + _name.insert("Mali","Mali"); + _name.insert("European Russia","EU Russia"); + _name.insert("Kaliningrad","Kaliningrad"); + _name.insert("Asiatic Russia","AS Russia"); + _name.insert("Uzbekistan","Uzbekistan"); + _name.insert("Kazakhstan","Kazakhstan"); + _name.insert("Ukraine","Ukraine"); + _name.insert("Antigua & Barbuda","Antigua & Barbuda"); + _name.insert("Belize","Belize"); + _name.insert("St. Kitts & Nevis","St. Kitts & Nevis"); + _name.insert("Namibia","Namibia"); + _name.insert("Micronesia","Micronesia"); + _name.insert("Marshall Islands","Marshall Is."); + _name.insert("Brunei Darussalam","Brunei Darussalam"); + _name.insert("Canada","Canada"); + _name.insert("Australia","Australia"); + _name.insert("Heard Island","Heard Is."); + _name.insert("Macquarie Island","Macquarie Is."); + _name.insert("Cocos (Keeling) Islands","Cocos (Keeling) Is."); + _name.insert("Lord Howe Island","Lord Howe Is."); + _name.insert("Mellish Reef","Mellish Reef"); + _name.insert("Norfolk Island","Norfolk Is."); + _name.insert("Willis Island","Willis Is."); + _name.insert("Christmas Island","Christmas Is."); + _name.insert("Anguilla","Anguilla"); + _name.insert("Montserrat","Montserrat"); + _name.insert("British Virgin Islands","British Virgin Is."); + _name.insert("Turks & Caicos Islands","Turks & Caicos Is."); + _name.insert("Pitcairn Island","Pitcairn Is."); + _name.insert("Ducie Island","Ducie Is."); + _name.insert("Falkland Islands","Falkland Is."); + _name.insert("South Georgia Island","S. Georgia Is."); + _name.insert("South Shetland Islands","S. Shetland Is."); + _name.insert("South Orkney Islands","S. Orkney Is."); + _name.insert("South Sandwich Islands","S. Sandwich Is."); + _name.insert("Bermuda","Bermuda"); + _name.insert("Chagos Islands","Chagos Is."); + _name.insert("Hong Kong","Hong Kong"); + _name.insert("India","India"); + _name.insert("Andaman & Nicobar Is.","Andaman & Nicobar Is."); + _name.insert("Lakshadweep Islands","Lakshadweep Is."); + _name.insert("Mexico","Mexico"); + _name.insert("Revillagigedo","Revillagigedo"); + _name.insert("Burkina Faso","Burkina Faso"); + _name.insert("Cambodia","Cambodia"); + _name.insert("Laos","Laos"); + _name.insert("Macao","Macao"); + _name.insert("Myanmar","Myanmar"); + _name.insert("Afghanistan","Afghanistan"); + _name.insert("Indonesia","Indonesia"); + _name.insert("Iraq","Iraq"); + _name.insert("Vanuatu","Vanuatu"); + _name.insert("Syria","Syria"); + _name.insert("Latvia","Latvia"); + _name.insert("Nicaragua","Nicaragua"); + _name.insert("Romania","Romania"); + _name.insert("El Salvador","El Salvador"); + _name.insert("Serbia","Serbia"); + _name.insert("Venezuela","Venezuela"); + _name.insert("Aves Island","Aves Is."); + _name.insert("Zimbabwe","Zimbabwe"); + _name.insert("North Macedonia","N. Macedonia"); + _name.insert("Republic of Kosovo","Rep. of Kosovo"); + _name.insert("Republic of South Sudan","Rep. of S. Sudan"); + _name.insert("Albania","Albania"); + _name.insert("Gibraltar","Gibraltar"); + _name.insert("UK Base Areas on Cyprus","UK Base Areas on Cyprus"); + _name.insert("St. Helena","St. Helena"); + _name.insert("Ascension Island","Ascension Is."); + _name.insert("Tristan da Cunha & Gough","Tristan da Cunha & Gough"); + _name.insert("Cayman Islands","Cayman Is."); + _name.insert("Tokelau Islands","Tokelau Is."); + _name.insert("New Zealand","New Zealand"); + _name.insert("Chatham Islands","Chatham Is."); + _name.insert("Kermadec Islands","Kermadec Is."); + _name.insert("N.Z. Subantarctic Is.","N.Z. Subantarctic Is."); + _name.insert("Paraguay","Paraguay"); + _name.insert("South Africa","S. Africa"); + _name.insert("Pr. Edward & Marion Is.","Pr. Edward & Marion Is."); +} + +QString CountryDat::_extractName(const QString line) +{ + int s1 = line.indexOf(':'); + + if (s1>=0) + { + QString name = line.left(s1); + return _name.value(name,name); + } + + return ""; +} + +QString CountryDat::_extractMasterPrefix(const QString line) +{ + int s1 = line.lastIndexOf(' '); + int s2 = line.lastIndexOf(':'); + + if (s1 >= 0 && s1 < s2) + { + QString pfx = line.mid(s1, s2-s1); + return pfx.toUpper(); + } + + return ""; +} + +QString CountryDat::_extractContinent(const QString line) +{ + int s1; + s1 = line.indexOf(':'); + + if (s1>=0) + { + s1 = line.indexOf(':',s1+1); + + if (s1>=0) + { + s1 = line.indexOf(':',s1+1); + + if (s1>=0) + { + s1 = line.indexOf(':',s1+1); + + if (s1>=0) + { + QString cont = line.mid(s1-2, 2); + return cont; + } + } + } + } + + return ""; +} + +QString CountryDat::_extractITUZ(const QString line) +{ + int s1; + s1 = line.indexOf(':'); + + if (s1>=0) + { + s1 = line.indexOf(':',s1+1); + + if (s1>=0) + { + s1 = line.indexOf(':',s1+1); + + if (s1>=0) + { + QString cont = line.mid(s1-2, 2); + + if (cont.size() == 1) { + cont = " " + cont; + } + + return cont; + } + } + } + + return ""; +} + +QString CountryDat::_extractCQZ(const QString line) +{ + int s1; + s1 = line.indexOf(':'); + + if (s1>=0) + { + s1 = line.indexOf(':',s1+1); + + if (s1>=0) + { + QString cont = line.mid(s1-2, 2); + + if (cont.size() == 1) { + cont = " " + cont; + } + + return cont; + } + } + + return ""; +} + +QString CountryDat::_removeBrackets(QString &line, const QString a, const QString b) +{ + QString res = ""; + int s1 = line.indexOf(a); + + while (s1 >= 0) + { + int s2 = line.indexOf(b); + res += line.mid(s1+1,s2-s1-1); + line = line.left(s1) + line.mid(s2+1,-1); + s1 = line.indexOf(a); + } + + return res; +} + +QStringList CountryDat::_extractPrefix(QString &line, bool &more) +{ + QString a; + line = line.remove(" \n"); + line = line.replace(" ",""); + a = _removeBrackets(line,"<",">"); + a = _removeBrackets(line,"~","~"); + int s1 = line.indexOf(';'); + more = true; + + if (s1 >= 0) + { + line = line.left(s1); + more = false; + } + + QStringList r = line.split(','); + + return r; +} + + +void CountryDat::load() +{ + _countries.clear(); + QFile inputFile(":/data/cty.dat"); + + if (inputFile.open(QIODevice::ReadOnly)) + { + QTextStream in(&inputFile); + + while (!in.atEnd()) + { + QString line1 = in.readLine(); + + if (!in.atEnd()) + { + QString line2 = in.readLine(); + QString masterPrefix, country, cqz, ituz, continent; + cqz = _extractCQZ(line1); + ituz = _extractITUZ(line1); + continent = _extractContinent(line1); + masterPrefix = _extractMasterPrefix(line1).trimmed(); + country = _extractName(line1).trimmed(); + + if (!masterPrefix.isEmpty() || !country.isEmpty()) + { + bool more = true; + QStringList prefixs; + + while (more) + { + QStringList p = _extractPrefix(line2,more); + prefixs += p; + + if (more) { + line2 = in.readLine(); + } + } + + QString p,_cqz,_ituz,_continent; + + foreach(p,prefixs) + { + if (!p.isEmpty()) + { + _cqz = _removeBrackets(p,"(",")"); + + if (_cqz.isEmpty()) { + _cqz = cqz; + } + if (_cqz.size() == 1) { + _cqz = "0" + _cqz; + } + + _ituz = _removeBrackets(p,"[","]"); + + if (_ituz.isEmpty()) { + _ituz = ituz; + } + if (_ituz.size() == 1) { + _ituz = "0" + _ituz; + } + + _continent = _removeBrackets(p,"{","}"); + + if (_continent.isEmpty()) { + _continent = continent; + } + + _countries.insert(p, CountryInfo{_continent, masterPrefix, country, _cqz, _ituz}); + } + } + } + } + } + + inputFile.close(); + } +} diff --git a/android/app/src/main/cpp/util/countrydat.h b/android/app/src/main/cpp/util/countrydat.h new file mode 100644 index 0000000..e193adb --- /dev/null +++ b/android/app/src/main/cpp/util/countrydat.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// This source code file was last time modified by Arvo ES1JA on January 5th, 2019 +// All changes are shown in the patch file coming together with the full JTDX source code. + +/* + * Reads cty.dat file + * Establishes a map between prefixes and their country names + * VK3ACF July 2013 + */ + + +#ifndef INCLUDE_COUNTRYDAT_H +#define INCLUDE_COUNTRYDAT_H + +#include +#include +#include +#include +#include + +#include "export.h" + +class SDRBASE_API CountryDat +{ +public: + struct CountryInfo { + QString continent; + QString masterPrefix; + QString country; + QString cqZone; + QString ituZone; + }; + + void init(); + void load(); + const QHash& getCountries() const { return _countries; } + static const CountryInfo nullCountry; + +private: + QString _extractName(const QString line); + QString _extractMasterPrefix(const QString line); + QString _extractContinent(const QString line); + QString _extractCQZ(const QString line); + QString _extractITUZ(const QString line); + QString _removeBrackets(QString &line, const QString a, const QString b); + QStringList _extractPrefix(QString &line, bool &more); + + QString _filename; + QHash _name; + QHash _countries; +}; + +#endif diff --git a/android/app/src/main/cpp/util/crc.cpp b/android/app/src/main/cpp/util/crc.cpp new file mode 100644 index 0000000..876f24d --- /dev/null +++ b/android/app/src/main/cpp/util/crc.cpp @@ -0,0 +1,91 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "crc.h" + +// Reverse bit ordering +uint32_t crc::reverse(uint32_t val, int bits) +{ + uint32_t temp; + int i; + + temp = 0; + for (i = 0; i < bits; i++) + temp |= ((val >> i) & 1) << (bits - 1 - i); + return temp; +} + +// Calculate CRC for specified number of bits +void crc::calculate(uint32_t data, int data_bits) +{ + uint32_t tmp; + uint32_t mask; + uint32_t msb; + int bit, i; + + if (m_msb_first) + { + mask = (1 << m_poly_bits) - 1; + msb = 1 << (m_poly_bits - 1); + tmp = m_crc ^ (data << (m_poly_bits - data_bits)); + for (i = 0; i < data_bits; i++) + { + if (tmp & msb) + tmp = (tmp << 1) ^ m_polynomial; + else + tmp = tmp << 1; + tmp = tmp & mask; + } + m_crc = tmp; + } + else + { + tmp = m_crc; + for (i = 0; i < data_bits; i++) { + bit = ((data >> i) & 1) ^ (tmp & 1); + if (bit) + tmp = (tmp >> 1) ^ m_polynomial_rev; + else + tmp = tmp >> 1; + } + m_crc = tmp; + } +} + +// Calculate CRC for specified array +void crc::calculate(const uint8_t *data, int length) +{ + int i; + uint32_t mask1; + uint32_t mask2; + + if (m_msb_first) + { + mask1 = (1 << m_poly_bits) - 1; + mask2 = 0xff << (m_poly_bits - 8); + for (i = 0; i < length; i++) + m_crc = mask1 & ((m_crc << 8) ^ m_lut[data[i] ^ ((m_crc & mask2) >> (m_poly_bits-8))]); + } + else + { + for (i = 0; i < length; i++) + m_crc = (m_crc >> 8) ^ m_lut[data[i] ^ (m_crc & 0xff)]; + } +} diff --git a/android/app/src/main/cpp/util/crc.h b/android/app/src/main/cpp/util/crc.h new file mode 100644 index 0000000..472298b --- /dev/null +++ b/android/app/src/main/cpp/util/crc.h @@ -0,0 +1,133 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CRC_H +#define INCLUDE_CRC_H + +#include + +#include "export.h" + +// Class to calculate arbitrary CRCs (up to 32-bits) +class SDRBASE_API crc +{ +public: + // Create and initialise CRC with specified polynomial and parameters + crc(int poly_bits, uint32_t polynomial, bool msb_first, uint32_t init_value, uint32_t final_xor) : + m_polynomial(polynomial), + m_poly_bits(poly_bits), + m_msb_first(msb_first), + m_init_value(init_value), + m_final_xor(final_xor) + { + // Reverse polynomial for LSB first + if (!msb_first) + { + int shift; + + shift = 32 - m_poly_bits; + m_polynomial_rev = reverse (m_polynomial << shift, 32); + } + // Create LUT + for (int i = 0; i < 256; i++) + { + m_crc = 0; + calculate(i, 8); + m_lut[i] = m_crc; + } + init(); + } + + // Initialise CRC state + void init() + { + m_crc = m_init_value; + } + + // Calculate CRC for supplied data + void calculate(uint32_t data, int data_bits); + void calculate(const uint8_t *data, int length); + + // Get final CRC + uint32_t get() + { + uint32_t t; + + t = m_final_xor ^ m_crc; + return t; + } + +private: + static uint32_t reverse(uint32_t val, int bits); + + uint32_t m_crc; + uint32_t m_polynomial; + uint32_t m_polynomial_rev; + uint32_t m_poly_bits; + bool m_msb_first; + uint32_t m_init_value; + uint32_t m_final_xor; + uint32_t m_lut[256]; +}; + +class SDRBASE_API crc16ansi : public crc +{ +public: + crc16ansi() : crc(16, 0x8005, false, 0x0000, 0) {} +}; + +class SDRBASE_API crc16ccitt : public crc +{ +public: + crc16ccitt() : crc(16, 0x1021, true, 0xffff, 0) {} +}; + +class SDRBASE_API crc16itut : public crc +{ +public: + crc16itut() : crc(16, 0x1021, false, 0x0000, 0) {} +}; + +class SDRBASE_API crc16x25 : public crc +{ +public: + crc16x25() : crc(16, 0x1021, false, 0xffff, 0xffff) {} +}; + +class SDRBASE_API crc32 : public crc +{ +public: + crc32() : crc(32, 0x04C11DB7, false, 0xffffffff, 0xffffffff) {} +}; + +// Should probably try to use SSE 4.2's CRC32C instruction if available +// See _mm_crc32_u8 and _mm_crc32_u64 intrinsics +class SDRBASE_API crc32c : public crc +{ +public: + crc32c() : crc(32, 0x1EDC6F41, false, 0xffffffff, 0) {} +}; + +// ADS-B - https://mode-s.org/decode/adsb/introduction.html +class SDRBASE_API crcadsb : public crc +{ +public: + crcadsb() : crc(24, 0xfff409, true, 0, 0) {} +}; + +#endif diff --git a/android/app/src/main/cpp/util/csv.cpp b/android/app/src/main/cpp/util/csv.cpp new file mode 100644 index 0000000..7354e2a --- /dev/null +++ b/android/app/src/main/cpp/util/csv.cpp @@ -0,0 +1,177 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB // +// Copyright (C) 2020-2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "csv.h" + +#include +#include +#include +#include +#include +#include + +// Create a hash map from a CSV file with two columns +QHash *CSV::hash(const QString& filename, int reserve) +{ + int cnt = 0; + QHash *map = nullptr; + + qDebug() << "csvHash: " << filename; + + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) + { + // Read header + if (!file.atEnd()) + { + QByteArray row = file.readLine().trimmed(); + if (row.split(',').size() == 2) + { + map = new QHash(); + if (reserve > 0) + map->reserve(reserve); + // Read data + while (!file.atEnd()) + { + row = file.readLine().trimmed(); + QList cols = row.split(','); + map->insert(QString(cols[0]), QString(cols[1])); + cnt++; + } + } + else + qDebug() << "csvHash: Unexpected header"; + } + else + qDebug() << "csvHash: Empty file"; + file.close(); + } + else + qDebug() << "csvHash: Failed to open " << filename; + + qDebug() << "csvHash: " << filename << ": read " << cnt << " entries"; + + return map; +} + +// Read a row from a CSV file (handling quotes) +// https://stackoverflow.com/questions/27318631/parsing-through-a-csv-file-in-qt +bool CSV::readRow(QTextStream &in, QStringList *row, char separator) +{ + static const int delta[][5] = { + // , " \n ? eof + { 1, 2, -1, 0, -1 }, // 0: parsing (store char) + { 1, 2, -1, 0, -1 }, // 1: parsing (store column) + { 3, 4, 3, 3, -2 }, // 2: quote entered (no-op) + { 3, 4, 3, 3, -2 }, // 3: parsing inside quotes (store char) + { 1, 3, -1, 0, -1 }, // 4: quote exited (no-op) + // -1: end of row, store column, success + // -2: eof inside quotes + }; + + row->clear(); + + if (in.atEnd()) + return false; + + int state = 0, t; + char ch; + QString cell; + + while (state >= 0) + { + if (in.atEnd()) + { + t = 4; + } + else + { + in >> ch; + if (ch == separator) { + t = 0; + } else if (ch == '\"') { + t = 1; + } else if (ch == '\n') { + t = 2; + } else { + t = 3; + } + } + + state = delta[state][t]; + + switch (state) { + case 0: + case 3: + cell += ch; + break; + case -1: + case 1: + row->append(cell); + cell = ""; + break; + } + + } + + if (state == -2) { + return false; + } + + return true; +} + +// Read header row from CSV file and return a hash mapping names to column numbers +// Returns error if header row can't be read, or if all of requiredColumns aren't found +QHash CSV::readHeader(QTextStream &in, QStringList requiredColumns, QString &error, char separator) +{ + QHash colNumbers; + QStringList row; + + // Read column names + if (CSV::readRow(in, &row, separator)) + { + // Create hash mapping column names to indices + for (int i = 0; i < row.size(); i++) { + colNumbers.insert(row[i], i); + } + // Check all required columns exist + for (const auto& col : requiredColumns) + { + if (!colNumbers.contains(col)) { + error = QString("Missing column %1").arg(col); + } + } + } + else + { + error = "Failed to read header row"; + } + + return colNumbers; +} + +QString CSV::escape(const QString& string) +{ + QString s = string; + s.replace('"', "\"\""); + s = QString("\"%1\"").arg(s); + return s; +} diff --git a/android/app/src/main/cpp/util/csv.h b/android/app/src/main/cpp/util/csv.h new file mode 100644 index 0000000..34117ed --- /dev/null +++ b/android/app/src/main/cpp/util/csv.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020-2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CSV_H +#define INCLUDE_CSV_H + +#include +#include +#include + +#include "export.h" + +// Extract string from CSV line, updating pp to next column (This doesn't handle , inside quotes) +static inline char *csvNext(char **pp, char delimiter=',') +{ + char *p = *pp; + + if (p[0] == '\0') + return nullptr; + + char *start = p; + + while ((*p != delimiter) && (*p != '\n')) + p++; + *p++ = '\0'; + *pp = p; + + return start; +} + +struct SDRBASE_API CSV { + + static QHash *hash(const QString& filename, int reserve=0); + + static bool readRow(QTextStream &in, QStringList *row, char seperator=','); + static QHash readHeader(QTextStream &in, QStringList requiredColumns, QString &error, char seperator=','); + + static QString escape(const QString& string); + +}; + +#endif /* INCLUDE_CSV_H */ diff --git a/android/app/src/main/cpp/util/db.cpp b/android/app/src/main/cpp/util/db.cpp new file mode 100644 index 0000000..9936c8a --- /dev/null +++ b/android/app/src/main/cpp/util/db.cpp @@ -0,0 +1,45 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2017, 2019-2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/db.h" +#include + +double CalcDb::dbPower(double magsq, double floor) +{ + if (floor <= 0.0) { + return -100.0; + } + + if (magsq > floor) { + return 10.0 * log10(magsq); + } else { + return 10.0 * log10(floor); + } +} + +double CalcDb::powerFromdB(double powerdB) +{ + return pow(10.0, powerdB / 10.0); +} + +double CalcDb::frexp10(double arg, int *exp) +{ + *exp = (arg == 0) ? 0 : 1 + (int)std::floor(std::log10(std::fabs(arg) ) ); + return arg * std::pow(10 , -(*exp)); +} diff --git a/android/app/src/main/cpp/util/db.h b/android/app/src/main/cpp/util/db.h new file mode 100644 index 0000000..8a2dce9 --- /dev/null +++ b/android/app/src/main/cpp/util/db.h @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_DB_H_ +#define INCLUDE_UTIL_DB_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API CalcDb +{ +public: + static double dbPower(double magsq, double floor = 1e-15); // Floor at -150dB + static double powerFromdB(double powerdB); + static double frexp10(double arg, int *exp); +}; + +#endif /* INCLUDE_UTIL_DB_H_ */ diff --git a/android/app/src/main/cpp/util/doublebuffer.h b/android/app/src/main/cpp/util/doublebuffer.h new file mode 100644 index 0000000..27ca418 --- /dev/null +++ b/android/app/src/main/cpp/util/doublebuffer.h @@ -0,0 +1,143 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_DOUBLEBUFFER_H_ +#define SDRBASE_UTIL_DOUBLEBUFFER_H_ + +#include +#include + +#include + +#include "simpleserializer.h" + +template +class DoubleBufferSimple +{ +public: + DoubleBufferSimple() + { + m_size = 0; + m_currentPosition = 0; + } + + ~DoubleBufferSimple() {} + + DoubleBufferSimple(const DoubleBufferSimple& other) + { + m_size = other.m_size; + m_data = other.m_data; + m_currentPosition = 0; + } + + DoubleBufferSimple& operator=(const DoubleBufferSimple& other) + { + if (&other == this) { + return *this; + } + + m_size = other.m_size; + m_data = other.m_data; + m_currentPosition = 0; + return *this; + } + + void resize(int size) + { + m_size = size; + m_data.resize(2*size); + m_currentPosition = 0; + } + + void write(const typename std::vector::const_iterator& begin, int length) + { + int insize = length > m_size ? m_size : length; + + std::copy(begin, begin + insize, m_data.begin() + m_currentPosition); + + if ((m_currentPosition + insize) > m_size) + { + int sizeLeft = m_size - m_currentPosition; + std::copy(begin, begin + sizeLeft, m_data.begin() + m_currentPosition + m_size); + std::copy(begin + sizeLeft, begin + insize, m_data.begin()); + m_currentPosition = insize - sizeLeft; + } + else + { + std::copy(begin, begin + insize, m_data.begin() + m_currentPosition + m_size); + m_currentPosition += insize; + } + } + + typename std::vector::iterator getCurrent() { return m_data.begin() + m_currentPosition + m_size; } + void getCurrent(typename std::vector::iterator& it) { it = m_data.begin() + m_currentPosition + m_size; } + typename std::vector::const_iterator begin() const { return m_data.begin(); } + typename std::vector::iterator begin() { return m_data.begin(); } + unsigned int absoluteFill() const { return m_currentPosition; } + void reset() { m_currentPosition = 0; } + + QByteArray serialize() const + { + SimpleSerializer s(1); + + QByteArray buf(reinterpret_cast(m_data.data()), m_data.size()*sizeof(T)); + s.writeS32(1, m_size); + s.writeU32(2, m_currentPosition); + s.writeBlob(3, buf); + + return s.final(); + } + + bool deserialize(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + unsigned int tmpUInt; + QByteArray buf; + + d.readS32(1, &m_size, m_data.size()/2); + m_data.resize(2*m_size); + d.readU32(2, &tmpUInt, 0); + m_currentPosition = tmpUInt; + d.readBlob(3, &buf); + //qDebug("DoubleBufferSimple::deserialize: m_data.size(): %u buf.size(): %d", m_data.size(), buf.size()); + //std::copy(reinterpret_cast(m_data.data()), buf.data(), buf.data() + buf.size()); // bug + memcpy(reinterpret_cast(m_data.data()), buf.data(), buf.size()); + + return true; + } + else + { + return false; + } + } + +private: + int m_size; + std::vector m_data; + int m_currentPosition; +}; + +#endif /* SDRBASE_UTIL_DOUBLEBUFFER_H_ */ diff --git a/android/app/src/main/cpp/util/doublebufferfifo.h b/android/app/src/main/cpp/util/doublebufferfifo.h new file mode 100644 index 0000000..d4a68fa --- /dev/null +++ b/android/app/src/main/cpp/util/doublebufferfifo.h @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_DOUBLEBUFFERFIFO_H_ +#define SDRBASE_UTIL_DOUBLEBUFFERFIFO_H_ + + +template +class DoubleBufferFIFO +{ +public: + DoubleBufferFIFO(int size) : m_size(size), m_writeIndex(0), m_currentIndex(0) + { + m_data = new T[2*m_size]; + } + + ~DoubleBufferFIFO() + { + delete[] m_data; + } + + void resize(int size) + { + delete[] m_data; + m_size = size; + m_data = new T[2*m_size]; + m_writeIndex = 0; + m_currentIndex = 0; + } + + void write(const T& element) + { + m_data[m_writeIndex] = element; + m_data[m_writeIndex+m_size] = element; + m_currentIndex = m_writeIndex; + + if (m_writeIndex < m_size - 1) { + m_writeIndex++; + } else { + m_writeIndex = 0; + } + } + + T& readBack(int delay) + { + if (delay > m_size) { + delay = m_size; + } + + return m_data[m_currentIndex + m_size - delay]; + } + + void zeroBack(int delay) + { + if (delay > m_size) { + delay = m_size; + } + + for (int i = 0; i < delay; i++) { + m_data[m_currentIndex + m_size - i] = 0; + } + } + +private: + int m_size; + T *m_data; + int m_writeIndex; + int m_currentIndex; +}; + +#endif /* SDRBASE_UTIL_DOUBLEBUFFERFIFO_H_ */ diff --git a/android/app/src/main/cpp/util/doublebuffermultiple.h b/android/app/src/main/cpp/util/doublebuffermultiple.h new file mode 100644 index 0000000..1f96ec5 --- /dev/null +++ b/android/app/src/main/cpp/util/doublebuffermultiple.h @@ -0,0 +1,268 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_DOUBLEBUFFERMULTIPLE_H_ +#define SDRBASE_UTIL_DOUBLEBUFFERMULTIPLE_H_ + +#include +#include + +#include + +#include "simpleserializer.h" + +template +class DoubleBufferMultiple +{ +public: + DoubleBufferMultiple() + { + m_size = 0; + m_currentPosition = 0; + m_current = m_data.end(); + } + + ~DoubleBufferMultiple() {} + + DoubleBufferMultiple(const DoubleBufferMultiple& other) + { + m_size = other.m_size; + m_data.resize(other.m_data.size()); + m_current.resize(other.m_data.size()); + m_currentPosition = 0; + + for (unsigned int i = 0; i < other.m_data.size(); i++) + { + m_data[i] = other.m_data[i]; + m_current[i] = m_data[i].begin(); + } + } + + DoubleBufferMultiple& operator=(const DoubleBufferMultiple& other) + { + if (&other == this) { + return *this; + } + + m_size = other.m_size; + m_data.resize(other.m_data.size()); + m_current.resize(other.m_data.size()); + m_currentPosition = 0; + + for (unsigned int i = 0; i < other.m_data.size(); i++) + { + m_data[i] = other.m_data[i]; + m_current[i] = m_data[i].begin(); + } + + return *this; + } + + void resize(int size) + { + m_size = size; + m_currentPosition = 0; + + for (unsigned int i = 0; i < m_data.size(); i++) + { + m_data[i].resize(2*size); + m_current[i] = m_data[i].begin(); + } + } + + void addStream() + { + m_data.push_back(std::vector(2*m_size)); + m_current.push_back(std::vector::iterator()); + m_current.back() = m_data.back().begin(); + } + + void write(const std::vector::const_iterator>& vbegin, int length) + { + int insize = length > m_size ? m_size : length; + + if ((m_currentPosition + insize) > m_size) + { + int sizeLeft = m_size - m_currentPosition; + m_currentPosition = insize - sizeLeft; + } + else + { + m_currentPosition += insize; + } + + for (unsigned int i = 0; i < vbegin.size(); i++) + { + if (i >= m_data.size()) { + break; + } + + std::copy(vbegin[i], vbegin[i] + insize, m_current[i]); + + if (((m_current[i] - m_data[i].begin()) + insize) > m_size) + { + int sizeLeft = m_size - (m_current[i] - m_data[i].begin()); + std::copy(vbegin[i], vbegin[i] + sizeLeft, m_current[i] + m_size); + std::copy(vbegin[i] + sizeLeft, vbegin[i] + insize, m_data[i].begin()); + m_current[i] = m_data[i].begin() + (insize - sizeLeft); + } + else + { + std::copy(vbegin[i], vbegin[i] + insize, m_current[i] + m_size); + m_current[i] += insize; + } + } + } + + // void write(const typename std::vector::const_iterator& begin, const typename std::vector::const_iterator& cend) + // { + // typename std::vector::const_iterator end = cend; + + // if ((end - begin) > m_size) { + // end = begin + m_size; + // } + + // int insize = end - begin; + + // std::copy(begin, end, m_current); + + // if (((m_current - m_data.begin()) + insize) > m_size) + // { + // int sizeLeft = m_size - (m_current - m_data.begin()); + // std::copy(begin, begin + sizeLeft, m_current + m_size); + // std::copy(begin + sizeLeft, end, m_data.begin()); + // m_current = m_data.begin() + (insize - sizeLeft); + // } + // else + // { + // std::copy(begin, end, m_current + m_size); + // m_current += insize; + // } + // } + + typename std::vector::iterator getCurrent(unsigned int i) const { return m_current[i] + m_size; } + typename std::vector::const_iterator begin(unsigned int i) const { return m_data[i].begin(); } + typename std::vector::iterator begin(unsigned int i) { return m_data[i].begin(); } + unsigned int absoluteFill(unsigned int i) const { return m_current[i] - m_data[i].begin(); } + void reset(unsigned int i) { m_current[i] = m_data[i].begin(); } + + void getCurrent(std::vector::iterator>& vcurrent) const + { + vcurrent.clear(); + + for (unsigned int i = 0; i < m_data.size(); i++) { + vcurrent.push_back(m_data[i].begin() + m_currentPosition + m_size); + } + } + + int getCurrentPosition() const { + return m_currentPosition + m_size; + } + + void begin(typename std::vector::const_iterator>& vbegin) const + { + vbegin.clear(); + + for (unsigned int i = 0; i < m_data.size(); i++) { + vbegin.push_back(m_data[i].begin()); + } + } + + void begin(typename std::vector::iterator>& vbegin) + { + vbegin.clear(); + + for (unsigned int i = 0; i < m_data.size(); i++) { + vbegin.push_back(m_data[i].begin()); + } + } + + unsigned int absoluteFill() const { return m_currentPosition; } + + void reset() + { + m_currentPosition = 0; + + for (unsigned int i = 0; i < m_current.size(); i++) { + m_current[i] = m_data[i].begin(); + } + } + + QByteArray serialize() const + { + SimpleSerializer s(1); + + s.writeU32(1, std::min(m_data.size(), 10U)); + s.writeS32(2, m_size); + + for (unsigned int i = 0; i < std::min(m_data.size(), 10U); i++) + { + QByteArray buf(reinterpret_cast(m_data[i].data()), m_data[i].size()*sizeof(T)); + s.writeU32(10*i + 11, m_current[i] - m_data[i].begin()); + s.writeBlob(10*i + 12, buf); + } + + return s.final(); + } + + bool deserialize(const QByteArray& data) + { + SimpleDeserializer d(data); + + if(!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + unsigned int tmpUInt; + unsigned int nbStreams; + QByteArray buf; + + d.readU32(1, &nbStreams, 0); + nbStreams = nbStreams > 10 ? 10 : nbStreams; + m_data.resize(nbStreams); + m_current.resize(nbStreams); + d.readS32(2, &m_size, 0); + + for (unsigned int i = 0; i < nbStreams; i++) + { + m_data.resize(2*m_size); + d.readU32(10*i + 11, &tmpUInt, 0); + d.readBlob(10*i + 12, &buf); + m_current[i] = m_data[i].begin() + tmpUInt; + memcpy(reinterpret_cast(m_data[i].data()), buf.data(), buf.size()); + //qDebug("DoubleBufferMutiple::deserialize: i: %u m_data.size(): %u buf.size(): %d", i, m_data[i].size(), buf.size()); + //std::copy(reinterpret_cast(m_data.data()), buf.data(), buf.data() + buf.size()); // bug + } + + return true; + } + else + { + return false; + } + } + +private: + int m_size; + std::vector> m_data; + std::vector::iterator> m_current; + int m_currentPosition; +}; + +#endif /* SDRBASE_UTIL_DOUBLEBUFFERMULTIPLE_H_ */ diff --git a/android/app/src/main/cpp/util/dsc.cpp b/android/app/src/main/cpp/util/dsc.cpp new file mode 100644 index 0000000..827aec5 --- /dev/null +++ b/android/app/src/main/cpp/util/dsc.cpp @@ -0,0 +1,972 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/dsc.h" +#include "util/popcount.h" + +// "short" strings are meant to be compatible with YaDDNet + +QMap DSCMessage::m_formatSpecifierStrings = { + {GEOGRAPHIC_CALL, "Geographic call"}, + {DISTRESS_ALERT, "Distress alert"}, + {GROUP_CALL, "Group call"}, + {ALL_SHIPS, "All ships"}, + {SELECTIVE_CALL, "Selective call"}, + {AUTOMATIC_CALL, "Automatic call"} +}; + +QMap DSCMessage::m_formatSpecifierShortStrings = { + {GEOGRAPHIC_CALL, "AREA"}, + {DISTRESS_ALERT, "DIS"}, + {GROUP_CALL, "GRP"}, + {ALL_SHIPS, "ALL"}, + {SELECTIVE_CALL, "SEL"}, + {AUTOMATIC_CALL, "AUT"} +}; + +QMap DSCMessage::m_categoryStrings = { + {ROUTINE, "Routine"}, + {SAFETY, "Safety"}, + {URGENCY, "Urgency"}, + {DISTRESS, "Distress"} +}; + +QMap DSCMessage::m_categoryShortStrings = { + {ROUTINE, "RTN"}, + {SAFETY, "SAF"}, + {URGENCY, "URG"}, + {DISTRESS, "DIS"} +}; + +QMap DSCMessage::m_telecommand1Strings = { + {F3E_G3E_ALL_MODES_TP, "F3E (FM speech)/G3E (phase modulated speech) all modes telephony"}, + {F3E_G3E_DUPLEX_TP, "F3E (FM speech)/G3E (phase modulated speech) duplex telephony"}, + {POLLING, "Polling"}, + {UNABLE_TO_COMPLY, "Unable to comply"}, + {END_OF_CALL, "End of call"}, + {DATA, "Data"}, + {J3E_TP, "J3E (SSB) telephony"}, + {DISTRESS_ACKNOWLEDGEMENT, "Distress acknowledgement"}, + {DISTRESS_ALERT_RELAY, "Distress alert relay"}, + {F1B_J2B_TTY_FEC, "F1B (FSK) J2B (FSK via SSB) TTY FEC"}, + {F1B_J2B_TTY_AQR, "F1B (FSK) J2B (FSK via SSB) TTY AQR"}, + {TEST, "Test"}, + {POSITION_UPDATE, "Position update"}, + {NO_INFORMATION, "No information"} +}; + +QMap DSCMessage::m_telecommand1ShortStrings = { + {F3E_G3E_ALL_MODES_TP, "F3E/G3E"}, + {F3E_G3E_DUPLEX_TP, "F3E/G3E, Duplex TP"}, + {POLLING, "POLL"}, + {UNABLE_TO_COMPLY, "UNABLE TO COMPLY"}, + {END_OF_CALL, "EOC"}, + {DATA, "DATA"}, + {J3E_TP, "J3E TP"}, + {DISTRESS_ACKNOWLEDGEMENT, "DISTRESS ACK"}, + {DISTRESS_ALERT_RELAY, "DISTRESS RELAY"}, + {F1B_J2B_TTY_FEC, "F1B/J2B TTY-FEC"}, + {F1B_J2B_TTY_AQR, "F1B/J2B TTY-ARQ"}, + {TEST, "TEST"}, + {POSITION_UPDATE, "POSUPD"}, + {NO_INFORMATION, "NOINF"} +}; + +QMap DSCMessage::m_telecommand2Strings = { + {NO_REASON, "No reason"}, + {CONGESTION, "Congestion at switching centre"}, + {BUSY, "Busy"}, + {QUEUE, "Queue indication"}, + {BARRED, "Station barred"}, + {NO_OPERATOR, "No operator available"}, + {OPERATOR_UNAVAILABLE, "Operator temporarily unavailable"}, + {EQUIPMENT_DISABLED, "Equipment disabled"}, + {UNABLE_TO_USE_CHANNEL, "Unable to use proposed channel"}, + {UNABLE_TO_USE_MODE, "Unable to use proposed mode"}, + {NOT_PARTIES_TO_CONFLICT, "Ships and aircraft of States not parties to an armed conflict"}, + {MEDICAL_TRANSPORTS, "Medical transports"}, + {PAY_PHONE, "Pay-phone/public call office"}, + {FAX, "Facsimile"}, + {NO_INFORMATION_2, "No information"} +}; + +QMap DSCMessage::m_telecommand2ShortStrings = { + {NO_REASON, "NO REASON GIVEN"}, + {CONGESTION, "CONGESTION AT MARITIME CENTRE"}, + {BUSY, "BUSY"}, + {QUEUE, "QUEUE INDICATION"}, + {BARRED, "STATION BARRED"}, + {NO_OPERATOR, "NO OPERATOR AVAILABLE"}, + {OPERATOR_UNAVAILABLE, "OPERATOR TEMPORARILY UNAVAILABLE"}, + {EQUIPMENT_DISABLED, "EQUIPMENT DISABLED"}, + {UNABLE_TO_USE_CHANNEL, "UNABLE TO USE PROPOSED CHANNEL"}, + {UNABLE_TO_USE_MODE, "UNABLE TO USE PROPOSED MODE"}, + {NOT_PARTIES_TO_CONFLICT, "SHIPS/AIRCRAFT OF STATES NOT PARTIES TO ARMED CONFLICT"}, + {MEDICAL_TRANSPORTS, "MEDICAL TRANSPORTS"}, + {PAY_PHONE, "PAY-PHONE/PUBLIC CALL OFFICE"}, + {FAX, "FAX/DATA ACCORDING ITU-R M1081"}, + {NO_INFORMATION_2, "NOINF"} +}; + +QMap DSCMessage::m_distressNatureStrings = { + {FIRE, "Fire, explosion"}, + {FLOODING, "Flooding"}, + {COLLISION, "Collision"}, + {GROUNDING, "Grounding"}, + {LISTING, "Listing"}, + {SINKING, "Sinking"}, + {ADRIFT, "Adrift"}, + {UNDESIGNATED, "Undesignated"}, + {ABANDONING_SHIP, "Abandoning ship"}, + {PIRACY, "Piracy, armed attack"}, + {MAN_OVERBOARD, "Man overboard"}, + {EPIRB, "EPIRB"} +}; + +QMap DSCMessage::m_endOfSignalStrings = { + {REQ, "Req ACK"}, + {ACK, "ACK"}, + {EOS, "EOS"} +}; + +QMap DSCMessage::m_endOfSignalShortStrings = { + {REQ, "REQ"}, + {ACK, "ACK"}, + {EOS, "EOS"} +}; + +DSCMessage::DSCMessage(const QByteArray& data, QDateTime dateTime) : + m_dateTime(dateTime), + m_data(data) +{ + decode(data); +} + +QString DSCMessage::toString(const QString separator) const +{ + QStringList s; + + s.append(QString("Format specifier: %1").arg(formatSpecifier())); + + if (m_hasAddress) { + s.append(QString("Address: %1").arg(m_address)); + } + if (m_hasCategory) { + s.append(QString("Category: %1").arg(category())); + } + + s.append(QString("Self Id: %1").arg(m_selfId)); + + if (m_hasTelecommand1) { + s.append(QString("Telecommand 1: %1").arg(telecommand1(m_telecommand1))); + } + if (m_hasTelecommand2) { + s.append(QString("Telecommand 2: %1").arg(telecommand2(m_telecommand2))); + } + + if (m_hasDistressId) { + s.append(QString("Distress Id: %1").arg(m_distressId)); + } + if (m_hasDistressNature) + { + s.append(QString("Distress nature: %1").arg(distressNature(m_distressNature))); + s.append(QString("Distress coordinates: %1").arg(m_position)); + } + else if (m_hasPosition) + { + s.append(QString("Position: %1").arg(m_position)); + } + + if (m_hasFrequency1) { + s.append(QString("RX Frequency: %1Hz").arg(m_frequency1)); + } + if (m_hasChannel1) { + s.append(QString("RX Channel: %1").arg(m_channel1)); + } + if (m_hasFrequency2) { + s.append(QString("TX Frequency: %1Hz").arg(m_frequency2)); + } + if (m_hasChannel2) { + s.append(QString("TX Channel: %1").arg(m_channel2)); + } + if (m_hasNumber) { + s.append(QString("Phone Number: %1").arg(m_number)); + } + + if (m_hasTime) { + s.append(QString("Time: %1").arg(m_time.toString())); + } + if (m_hasSubsequenceComms) { + s.append(QString("Subsequent comms: %1").arg(telecommand1(m_subsequenceComms))); + } + + return s.join(separator); +} + +QString DSCMessage::toYaddNetFormat(const QString& id, qint64 frequency) const +{ + QStringList s; + + // rx_id + s.append(QString("[%1]").arg(id)); + // rx_freq + float frequencyKHZ = frequency / 1000.0f; + s.append(QString("%1").arg(frequencyKHZ, 0, 'f', 1)); + // fmt + s.append(formatSpecifier(true)); + // to + if (m_hasAddress) + { + if (m_formatSpecifier == GEOGRAPHIC_CALL) + { + char ns = m_addressLatitude >= 0 ? 'N' : 'S'; + char ew = m_addressLongitude >= 0 ? 'E' : 'W'; + int lat = abs(m_addressLatitude); + int lon = abs(m_addressLongitude); + s.append(QString("AREA %2%1%6=>%4%1 %3%1%7=>%5%1") + .arg(QChar(0xb0)) // degree + .arg(lat, 2, 10, QChar('0')) + .arg(lon, 3, 10, QChar('0')) + .arg(m_addressLatAngle, 2, 10, QChar('0')) + .arg(m_addressLonAngle, 2, 10, QChar('0')) + .arg(ns) + .arg(ew)); + } + else + { + s.append(m_address); + } + } + else + { + s.append(""); + } + // cat + s.append(category(true)); + // from + s.append(m_selfId); + + // tc1 + if (m_hasTelecommand1) { + s.append(telecommand1(m_telecommand1, true)); + } else { + s.append("--"); + } + // tc2 + if (m_hasTelecommand2) { + s.append(telecommand2(m_telecommand2, true)); + } else { + s.append("--"); + } + // distress fields don't appear to be used! + // freq + if (m_hasFrequency1 && m_hasFrequency2) { + s.append(QString("%1/%2KHz").arg(m_frequency1/1000.0, 7, 'f', 1, QChar('0')).arg(m_frequency2/1000.0, 7, 'f', 1, QChar('0'))); + } else if (m_hasFrequency1) { + s.append(QString("%1KHz").arg(m_frequency1/1000.0, 7, 'f', 1, QChar('0'))); + } else if (m_hasFrequency2) { + s.append(QString("%1KHz").arg(m_frequency2/1000.0, 7, 'f', 1, QChar('0'))); + } else if (m_hasChannel1 && m_hasChannel2) { + s.append(QString("%1/%2").arg(m_channel1).arg(m_channel2)); + } else if (m_hasChannel1) { + s.append(QString("%1").arg(m_channel1)); + } else if (m_hasChannel2) { + s.append(QString("%1").arg(m_channel2)); + } else { + s.append("--"); + } + // pos + if (m_hasPosition) { + s.append(m_position); // FIXME: Format?? + } else { + s.append("--"); // Sometimes this is " -- ". in YaDD Why? + } + + // eos + s.append(endOfSignal(m_eos, true)); + // ecc + s.append(QString("ECC %1 %2").arg(m_calculatedECC).arg(m_eccOk ? "OK" : "ERR")); + + return s.join(";"); +} + +QString DSCMessage::formatSpecifier(bool shortString) const +{ + if (shortString) + { + if (m_formatSpecifierShortStrings.contains(m_formatSpecifier)) { + return m_formatSpecifierShortStrings[m_formatSpecifier]; + } else { + return QString("UNK/ERR").arg(m_formatSpecifier); + } + } + else + { + if (m_formatSpecifierStrings.contains(m_formatSpecifier)) { + return m_formatSpecifierStrings[m_formatSpecifier]; + } else { + return QString("Unknown (%1)").arg(m_formatSpecifier); + } + } +} + + +QString DSCMessage::category(bool shortString) const +{ + if (shortString) + { + if (m_categoryShortStrings.contains(m_category)) { + return m_categoryShortStrings[m_category]; + } else { + return QString("UNK/ERR").arg(m_category); + } + } + else + { + if (!m_hasCategory) { + return "N/A"; + } else if (m_categoryStrings.contains(m_category)) { + return m_categoryStrings[m_category]; + } else { + return QString("Unknown (%1)").arg(m_category); + } + } +} + +QString DSCMessage::telecommand1(FirstTelecommand telecommand, bool shortString) +{ + if (shortString) + { + if (m_telecommand1ShortStrings.contains(telecommand)) { + return m_telecommand1ShortStrings[telecommand]; + } else { + return QString("UNK/ERR").arg(telecommand); + } + } + else + { + if (m_telecommand1Strings.contains(telecommand)) { + return m_telecommand1Strings[telecommand]; + } else { + return QString("Unknown (%1)").arg(telecommand); + } + } +} + +QString DSCMessage::telecommand2(SecondTelecommand telecommand, bool shortString) +{ + if (shortString) + { + if (m_telecommand2ShortStrings.contains(telecommand)) { + return m_telecommand2ShortStrings[telecommand]; + } else { + return QString("UNK/ERR").arg(telecommand); + } + } + else + { + if (m_telecommand2Strings.contains(telecommand)) { + return m_telecommand2Strings[telecommand]; + } else { + return QString("Unknown (%1)").arg(telecommand); + } + } +} + +QString DSCMessage::distressNature(DistressNature nature) +{ + if (m_distressNatureStrings.contains(nature)) { + return m_distressNatureStrings[nature]; + } else { + return QString("Unknown (%1)").arg(nature); + } +} + +QString DSCMessage::endOfSignal(EndOfSignal eos, bool shortString) +{ + if (shortString) + { + if (m_endOfSignalShortStrings.contains(eos)) { + return m_endOfSignalShortStrings[eos]; + } else { + return QString("UNK/ERR").arg(eos); + } + } + else + { + if (m_endOfSignalStrings.contains(eos)) { + return m_endOfSignalStrings[eos]; + } else { + return QString("Unknown (%1)").arg(eos); + } + } +} + + +QString DSCMessage::symbolsToDigits(const QByteArray data, int startIdx, int length) +{ + QString s; + + for (int i = 0; i < length; i++) + { + QString digits = QString("%1").arg((int)data[startIdx+i], 2, 10, QChar('0')); + s = s.append(digits); + } + + return s; +} + +QString DSCMessage::formatCoordinates(int latitude, int longitude) +{ + QString lat, lon; + if (latitude >= 0) { + lat = QString("%1%2N").arg(latitude).arg(QChar(0xb0)); + } else { + lat = QString("%1%2S").arg(-latitude).arg(QChar(0xb0)); + } + if (longitude >= 0) { + lon = QString("%1%2E").arg(longitude).arg(QChar(0xb0)); + } else { + lon = QString("%1%2W").arg(-longitude).arg(QChar(0xb0)); + } + return QString("%1 %2").arg(lat).arg(lon); +} + +void DSCMessage::decode(const QByteArray& data) +{ + int idx = 0; + + // Format specifier + m_formatSpecifier = (FormatSpecifier) data[idx++]; + m_formatSpecifierMatch = m_formatSpecifier == data[idx++]; + + // Address and category + if (m_formatSpecifier != DISTRESS_ALERT) + { + if (m_formatSpecifier != ALL_SHIPS) + { + m_address = symbolsToDigits(data, idx, 5); + idx += 5; + m_hasAddress = true; + + if (m_formatSpecifier == SELECTIVE_CALL) + { + m_address = formatAddress(m_address); + } + else if (m_formatSpecifier == GEOGRAPHIC_CALL) + { + // Address defines a geographic rectangle. We have NW coord + 2 angles + QChar azimuthSector = m_address[0]; + m_addressLatitude = m_address[1].digitValue() * 10 + m_address[2].digitValue(); // In degrees + m_addressLongitude = m_address[3].digitValue() * 100 + m_address[4].digitValue() * 10 + m_address[5].digitValue(); // In degrees + switch (azimuthSector.toLatin1()) + { + case '0': // NE + break; + case '1': // NW + m_addressLongitude = -m_addressLongitude; + break; + case '2': // SE + m_addressLatitude = -m_addressLatitude; + break; + case '3': // SW + m_addressLongitude = -m_addressLongitude; + m_addressLatitude = -m_addressLatitude; + break; + default: + break; + } + m_addressLatAngle = m_address[6].digitValue() * 10 + m_address[7].digitValue(); + m_addressLonAngle = m_address[8].digitValue() * 10 + m_address[9].digitValue(); + + int latitude2 = m_addressLatitude + m_addressLatAngle; + int longitude2 = m_addressLongitude + m_addressLonAngle; + + /*m_address = QString("Lat %2%1 Lon %3%1 %4%5%6%1 %4%7%8%1") + .arg(QChar(0xb0)) // degree + .arg(m_addressLatitude) + .arg(m_addressLongitude) + .arg(QChar(0x0394)) // delta + .arg(QChar(0x03C6)) // phi + .arg(m_addressLatAngle) + .arg(QChar(0x03BB)) // lambda + .arg(m_addressLonAngle);*/ + m_address = QString("%1 - %2") + .arg(formatCoordinates(m_addressLatitude, m_addressLongitude)) + .arg(formatCoordinates(latitude2, longitude2)); + } + } + else + { + m_hasAddress = false; + } + m_category = (Category) data[idx++]; + m_hasCategory = true; + } + else + { + m_hasAddress = false; + m_hasCategory = true; + } + + // Self Id + m_selfId = symbolsToDigits(data, idx, 5); + m_selfId = formatAddress(m_selfId); + idx += 5; + + // Telecommands + if (m_formatSpecifier != DISTRESS_ALERT) + { + m_telecommand1 = (FirstTelecommand) data[idx++]; + m_hasTelecommand1 = true; + + if (m_category != DISTRESS) // Not Distress Alert Ack / Relay + { + m_telecommand2 = (SecondTelecommand) data[idx++]; + m_hasTelecommand2 = true; + } + else + { + m_hasTelecommand2 = false; + } + } + else + { + m_hasTelecommand1 = false; + m_hasTelecommand2 = false; + } + + // ID of source of distress for relays and acks + if (m_hasCategory && m_category == DISTRESS) + { + m_distressId = symbolsToDigits(data, idx, 5); + m_distressId = formatAddress(m_distressId); + idx += 5; + m_hasDistressId = true; + } + else + { + m_hasDistressId = false; + } + + if (m_formatSpecifier == DISTRESS_ALERT) + { + m_distressNature = (DistressNature) data[idx++]; + m_position = formatCoordinates(symbolsToDigits(data, idx, 5)); + idx += 5; + m_hasDistressNature = true; + m_hasPosition = true; + + m_hasFrequency1 = false; + m_hasChannel1 = false; + m_hasFrequency2 = false; + m_hasChannel2 = false; + } + else if (m_hasCategory && (m_category != DISTRESS)) + { + m_hasDistressNature = false; + // Frequency or position + if (data[idx] == 55) + { + // Position 6 + m_position = formatCoordinates(symbolsToDigits(data, idx, 5)); + idx += 5; + m_hasPosition = true; + + m_hasFrequency1 = false; + m_hasChannel1 = false; + m_hasFrequency2 = false; + m_hasChannel2 = false; + } + else + { + m_hasPosition = false; + // Frequency + m_frequency1 = 0; + decodeFrequency(data, idx, m_frequency1, m_channel1); + m_hasFrequency1 = m_frequency1 != 0; + m_hasChannel1 = !m_channel1.isEmpty(); + + if (m_formatSpecifier != AUTOMATIC_CALL) + { + m_frequency2 = 0; + decodeFrequency(data, idx, m_frequency2, m_channel2); + m_hasFrequency2 = m_frequency2 != 0; + m_hasChannel2 = !m_channel2.isEmpty(); + } + else + { + m_hasFrequency2 = false; + m_hasChannel2 = false; + } + } + } + else + { + m_hasDistressNature = false; + m_hasPosition = false; + m_hasFrequency1 = false; + m_hasChannel1 = false; + m_hasFrequency2 = false; + m_hasChannel2 = false; + } + + if (m_formatSpecifier == AUTOMATIC_CALL) + { + signed char oddEven = data[idx++]; + int len = data.size() - idx - 2; // EOS + ECC + m_number = symbolsToDigits(data, idx, len); + idx += len; + if (oddEven == 105) { // Is number an odd number? + m_number = m_number.mid(1); // Drop leading digit (which should be a 0) + } + m_hasNumber = true; + } + else + { + m_hasNumber = false; + } + + // Time + if ( (m_formatSpecifier == DISTRESS_ALERT) + || (m_hasCategory && (m_category == DISTRESS)) + //|| (m_formatSpecifier == SELECTIVE_CALL) && (m_category == SAFETY) && (m_telecommand1 == POSITION_UPDATE) && (m_telecommand2 == 126) && (m_frequency == pos4)) + ) + { + // 8 8 8 8 for no time + QString time = symbolsToDigits(data, idx, 2); + if (time != "8888") + { + m_time = QTime(time.left(2).toInt(), time.right(2).toInt()); + m_hasTime = true; + } + else + { + m_hasTime = false; + } + // FIXME: Convert to QTime? + } + else + { + m_hasTime = false; + } + + // Subsequent communications + if ((m_formatSpecifier == DISTRESS_ALERT) || (m_hasCategory && (m_category == DISTRESS))) + { + m_subsequenceComms = (FirstTelecommand)data[idx++]; + m_hasSubsequenceComms = true; + } + else + { + m_hasSubsequenceComms = false; + } + + m_eos = (EndOfSignal) data[idx++]; + m_ecc = data[idx++]; + + checkECC(data); + + // Indicate message as being invalid if any unexpected data, too long, or ECC didn't match + if ( m_formatSpecifierStrings.contains(m_formatSpecifier) + && (!m_hasCategory || (m_hasCategory && m_categoryStrings.contains(m_category))) + && (!m_hasTelecommand1 || (m_hasTelecommand1 && m_telecommand1Strings.contains(m_telecommand1))) + && (!m_hasTelecommand2 || (m_hasTelecommand2 && m_telecommand2Strings.contains(m_telecommand2))) + && (!m_hasDistressNature || (m_hasDistressNature && m_distressNatureStrings.contains(m_distressNature))) + && m_endOfSignalStrings.contains(m_eos) + && (!data.contains(-1)) + && (data.size() < DSCDecoder::m_maxBytes) + && m_eccOk + ) { + m_valid = true; + } else { + m_valid = false; + } + +} + +void DSCMessage::checkECC(const QByteArray& data) +{ + m_calculatedECC = 0; + // Only use one format specifier and one EOS + for (int i = 1; i < data.size() - 1; i++) { + m_calculatedECC ^= data[i]; + } + m_eccOk = m_calculatedECC == m_ecc; +} + +void DSCMessage::decodeFrequency(const QByteArray& data, int& idx, int& frequency, QString& channel) +{ + // No frequency information is indicated by 126 repeated 3 times + if ((data[idx] == 126) && (data[idx+1] == 126) && (data[idx+2] == 126)) + { + idx += 3; + return; + } + + // Extract frequency digits + QString s = symbolsToDigits(data, idx, 3); + idx += 3; + if (s[0] == '4') + { + s = s.append(symbolsToDigits(data, idx, 1)); + idx++; + } + + if ((s[0] == '0') || (s[0] == '1') || (s[0] == '2')) + { + frequency = s.toInt() * 100; + } + else if (s[0] == '3') + { + channel = "CH" + s.mid(1); // HF/MF + } + else if (s[0] == '4') + { + frequency = s.mid(1).toInt() * 10; // Frequency in multiples of 10Hz + } + else if (s[0] == '9') + { + channel = "CH" + s.mid(2) + "VHF"; // VHF + } +} + +QString DSCMessage::formatAddress(const QString &address) const +{ + // First 9 digits should be MMSI + // Last digit should always be 0, except for ITU-R M.1080, which allows 10th digit to specify different equipement on same vessel + if (address.right(1) == "0") { + return address.left(9); + } else { + return QString("%1-%2").arg(address.left(9)).arg(address.right(1)); + } +} + +QString DSCMessage::formatCoordinates(const QString& coords) +{ + if (coords == "9999999999") + { + return "Not available"; + } + else + { + QChar quadrant = coords[0]; + QString latitude = QString("%1%3%2\'") + .arg(coords.mid(1, 2)) + .arg(coords.mid(3, 2)) + .arg(QChar(0xb0)); + QString longitude = QString("%1%3%2\'") + .arg(coords.mid(1, 3)) + .arg(coords.mid(4, 2)) + .arg(QChar(0xb0)); + switch (quadrant.toLatin1()) + { + case '0': + latitude = latitude.append('N'); + longitude = longitude.append('E'); + break; + case '1': + latitude = latitude.append('N'); + longitude = longitude.append('W'); + break; + case '2': + latitude = latitude.append('S'); + longitude = longitude.append('E'); + break; + case '3': + latitude = latitude.append('S'); + longitude = longitude.append('W'); + break; + } + return QString("%1 %2").arg(latitude).arg(longitude); + } +} + +// Doesn't include 125 111 125 as these will have be detected already, in DSDDemodSink +const signed char DSCDecoder::m_expectedSymbols[] = { + 110, + 125, 109, + 125, 108, + 125, 107, + 125, 106 +}; + +int DSCDecoder::m_maxBytes = 40; // Max bytes in any message + +void DSCDecoder::init(int offset) +{ + if (offset == 0) + { + m_state = FILL_DX; + } + else + { + m_phaseIdx = offset; + m_state = PHASING; + } + m_idx = 0; + m_errors = 0; + m_bytes = QByteArray(); + m_eos = false; +} + +bool DSCDecoder::decodeSymbol(signed char symbol) +{ + bool ret = false; + + switch (m_state) + { + case PHASING: + // Check if received phasing signals are as expected + if (symbol != m_expectedSymbols[9-m_phaseIdx]) { + m_errors++; + } + m_phaseIdx--; + if (m_phaseIdx == 0) { + m_state = FILL_DX; + } + break; + + case FILL_DX: + // Fill up buffer + m_buf[m_idx++] = symbol; + if (m_idx == BUFFER_SIZE) + { + m_state = RX; + m_idx = 0; + } + else + { + m_state = FILL_RX; + } + break; + + case FILL_RX: + if ( ((m_idx == 1) && (symbol != 106)) + || ((m_idx == 2) && (symbol != 105)) + ) + { + m_errors++; + } + m_state = FILL_DX; + break; + + case RX: + { + signed char a = selectSymbol(m_buf[m_idx], symbol); + + if (DSCMessage::m_endOfSignalStrings.contains((DSCMessage::EndOfSignal) a)) { + m_state = DX_EOS; + } else { + m_state = DX; + } + + if (m_bytes.size() > m_maxBytes) + { + ret = true; + m_state = NO_EOS; + } + } + break; + + case DX: + // Save received character in buffer + m_buf[m_idx] = symbol; + m_idx = (m_idx + 1) % BUFFER_SIZE; + m_state = RX; + break; + + case DX_EOS: + // Save, EOS symbol + m_buf[m_idx] = symbol; + m_idx = (m_idx + 1) % BUFFER_SIZE; + m_state = RX_EOS; + break; + + case RX_EOS: + selectSymbol(m_buf[m_idx], symbol); + m_state = DONE; + ret = true; + break; + + case DONE: + case NO_EOS: + break; + + } + + return ret; +} + +// Reverse order of bits in a byte +unsigned char DSCDecoder::reverse(unsigned char b) +{ + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + +// Convert 10 bits to a symbol +// Returns -1 if error detected +signed char DSCDecoder::bitsToSymbol(unsigned int bits) +{ + signed char data = reverse(bits >> 3) >> 1; + int zeros = 7-popcount(data); + int expectedZeros = bits & 0x7; + if (zeros == expectedZeros) { + return data; + } else { + return -1; + } +} + +// Decode 10-bits to symbols then remove errors using repeated symbols +bool DSCDecoder::decodeBits(int bits) +{ + signed char symbol = bitsToSymbol(bits); + //qDebug() << "Bits2sym: " << Qt::hex << bits << Qt::hex << symbol; + return decodeSymbol(symbol); +} + +// Select time diversity symbol without errors +signed char DSCDecoder::selectSymbol(signed char dx, signed char rx) +{ + signed char s; + if (dx != -1) + { + s = dx; // First received character has no detectable error + if (dx != rx) { + m_errors++; + } + } + else if (rx != -1) + { + s = rx; // Second received character has no detectable error + m_errors++; + } + else + { + s = '*'; // Both received characters have errors + m_errors += 2; + } + m_bytes.append(s); + + return s; +} diff --git a/android/app/src/main/cpp/util/dsc.h b/android/app/src/main/cpp/util/dsc.h new file mode 100644 index 0000000..d0461c1 --- /dev/null +++ b/android/app/src/main/cpp/util/dsc.h @@ -0,0 +1,234 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_DSC_H +#define INCLUDE_UTIL_DSC_H + +#include "export.h" + +#include +#include +#include + +// Digital Select Calling +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.493-15-201901-I!!PDF-E.pdf + +class SDRBASE_API DSCDecoder { + +public: + + void init(int offset); + bool decodeBits(int bits); + QByteArray getMessage() const { return m_bytes; } + int getErrors() const { return m_errors; } + + static int m_maxBytes; + +private: + + static const int BUFFER_SIZE = 3; + signed char m_buf[3]; + enum State { + PHASING, + FILL_DX, + FILL_RX, + DX, + RX, + DX_EOS, + RX_EOS, + DONE, + NO_EOS + } m_state; + int m_idx; + int m_errors; + int m_phaseIdx; + bool m_eos; + static const signed char m_expectedSymbols[]; + + QByteArray m_bytes; + + bool decodeSymbol(signed char symbol); + static signed char bitsToSymbol(unsigned int bits); + static unsigned char reverse(unsigned char b); + signed char selectSymbol(signed char dx, signed char rx); + +}; + +class SDRBASE_API DSCMessage { +public: + + enum FormatSpecifier { + GEOGRAPHIC_CALL = 102, + DISTRESS_ALERT = 112, + GROUP_CALL = 114, + ALL_SHIPS = 116, + SELECTIVE_CALL = 120, + AUTOMATIC_CALL = 123 + }; + + enum Category { + ROUTINE = 100, + SAFETY = 108, + URGENCY = 110, + DISTRESS = 112 + }; + + enum FirstTelecommand { + F3E_G3E_ALL_MODES_TP = 100, + F3E_G3E_DUPLEX_TP = 101, + POLLING = 103, + UNABLE_TO_COMPLY = 104, + END_OF_CALL = 105, + DATA = 106, + J3E_TP = 109, + DISTRESS_ACKNOWLEDGEMENT = 110, + DISTRESS_ALERT_RELAY = 112, + F1B_J2B_TTY_FEC = 113, + F1B_J2B_TTY_AQR = 115, + TEST = 118, + POSITION_UPDATE = 121, + NO_INFORMATION = 126 + }; + + enum SecondTelecommand { + NO_REASON = 100, + CONGESTION = 101, + BUSY = 102, + QUEUE = 103, + BARRED = 104, + NO_OPERATOR = 105, + OPERATOR_UNAVAILABLE = 106, + EQUIPMENT_DISABLED = 107, + UNABLE_TO_USE_CHANNEL = 108, + UNABLE_TO_USE_MODE = 109, + NOT_PARTIES_TO_CONFLICT = 110, + MEDICAL_TRANSPORTS = 111, + PAY_PHONE = 112, + FAX = 113, + NO_INFORMATION_2 = 126 + }; + + enum DistressNature { + FIRE = 100, + FLOODING = 101, + COLLISION = 102, + GROUNDING = 103, + LISTING = 104, + SINKING = 105, + ADRIFT = 106, + UNDESIGNATED = 107, + ABANDONING_SHIP = 108, + PIRACY = 109, + MAN_OVERBOARD = 110, + EPIRB = 112 + }; + + enum EndOfSignal { + REQ = 117, + ACK = 122, + EOS = 127 + }; + + static QMap m_formatSpecifierStrings; + static QMap m_formatSpecifierShortStrings; + static QMap m_categoryStrings; + static QMap m_categoryShortStrings; + static QMap m_telecommand1Strings; + static QMap m_telecommand1ShortStrings; + static QMap m_telecommand2Strings; + static QMap m_telecommand2ShortStrings; + static QMap m_distressNatureStrings; + static QMap m_endOfSignalStrings; + static QMap m_endOfSignalShortStrings; + + FormatSpecifier m_formatSpecifier; + bool m_formatSpecifierMatch; + QString m_address; + bool m_hasAddress; + int m_addressLatitude; // For GEOGRAPHIC_CALL + int m_addressLongitude; + int m_addressLatAngle; + int m_addressLonAngle; + + Category m_category; + bool m_hasCategory; + QString m_selfId; + FirstTelecommand m_telecommand1; + bool m_hasTelecommand1; + SecondTelecommand m_telecommand2; + bool m_hasTelecommand2; + + QString m_distressId; + bool m_hasDistressId; + + DistressNature m_distressNature; + bool m_hasDistressNature; + + QString m_position; + bool m_hasPosition; + + int m_frequency1; // Rx + bool m_hasFrequency1; + QString m_channel1; + bool m_hasChannel1; + int m_frequency2; // Tx + bool m_hasFrequency2; + QString m_channel2; + bool m_hasChannel2; + + QString m_number; // Phone number + bool m_hasNumber; + + QTime m_time; + bool m_hasTime; + + FirstTelecommand m_subsequenceComms; + bool m_hasSubsequenceComms; + + EndOfSignal m_eos; + signed char m_ecc; // Error checking code (parity) + signed char m_calculatedECC; + bool m_eccOk; + bool m_valid; // Data is within defined values + + QDateTime m_dateTime; // Date/time when received + QByteArray m_data; + + DSCMessage(const QByteArray& data, QDateTime dateTime); + QString toString(const QString separator = " ") const; + QString toYaddNetFormat(const QString& id, qint64 frequency) const; + QString formatSpecifier(bool shortString=false) const; + QString category(bool shortString=false) const; + + static QString telecommand1(FirstTelecommand telecommand, bool shortString=false); + static QString telecommand2(SecondTelecommand telecommand, bool shortString=false); + static QString distressNature(DistressNature nature); + static QString endOfSignal(EndOfSignal eos, bool shortString=false); + +protected: + + QString symbolsToDigits(const QByteArray data, int startIdx, int length); + QString formatCoordinates(int latitude, int longitude); + void decode(const QByteArray& data); + void checkECC(const QByteArray& data); + void decodeFrequency(const QByteArray& data, int& idx, int& frequency, QString& channel); + QString formatAddress(const QString &address) const; + QString formatCoordinates(const QString& coords); + +}; + +#endif /* INCLUDE_UTIL_DSC_H */ diff --git a/android/app/src/main/cpp/util/fits.cpp b/android/app/src/main/cpp/util/fits.cpp new file mode 100644 index 0000000..f91cddf --- /dev/null +++ b/android/app/src/main/cpp/util/fits.cpp @@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include +#include + +#include "fits.h" + +FITS::FITS(QString resourceName) : + m_valid(false) +{ + QResource m_res(resourceName); + if (!m_res.isValid()) { + qWarning() << "FITS: - " << resourceName << " is not a valid resource"; + return; + } + int m_headerSize = 2880; + qint64 m_fileSize; +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + m_data = m_res.uncompressedData(); + m_fileSize = m_res.uncompressedSize(); +#else + m_data = QByteArray::fromRawData((const char *)m_res.data(), m_res.size()); + if (m_res.isCompressed()) { + m_data = qUncompress(m_data); + } + m_fileSize = m_res.size(); +#endif + int hLen = std::min((qint64)m_headerSize * 3, m_fileSize); // Could possibly be bigger + QByteArray headerBytes = m_data.left(hLen); + QString header = QString::fromLatin1(headerBytes); + QRegularExpression widthRE("NAXIS1 *= *([0-9]+)"); + QRegularExpression heightRE("NAXIS2 *= *([0-9]+)"); + QRegularExpression bitsPerPixelRE("BITPIX *= *(-?[0-9]+)"); + QRegularExpression bzeroRE("BZERO *= *([0-9]+)"); + QRegularExpression bscaleRE("BSCALE *= *(-?[0-9]+(.[0-9]+)?)"); + QRegularExpression buintRE("BUNIT *= *\\'([A-Z ]+)\\'"); + QRegularExpression cdelt1RE("CDELT1 *= *(-?[0-9]+(.[0-9]+)?)"); + QRegularExpression cdelt2RE("CDELT2 *= *(-?[0-9]+(.[0-9]+)?)"); + QRegularExpression endRE("END {77}"); + QRegularExpressionMatch match; + + match = widthRE.match(header); + if (match.hasMatch()) + m_width = match.capturedTexts()[1].toInt(); + else + { + qWarning() << "FITS: NAXIS1 missing"; + return; + } + + match = heightRE.match(header); + if (match.hasMatch()) + m_height = match.capturedTexts()[1].toInt(); + else + { + qWarning() << "FITS: NAXIS2 missing"; + return; + } + + match = bitsPerPixelRE.match(header); + if (match.hasMatch()) + m_bitsPerPixel = match.capturedTexts()[1].toInt(); + else + { + qWarning() << "FITS: BITPIX missing"; + return; + } + + m_bytesPerPixel = abs(m_bitsPerPixel)/8; + match = bzeroRE.match(header); + if (match.hasMatch()) + m_bzero = match.capturedTexts()[1].toInt(); + else + m_bzero = 0; + + match = bscaleRE.match(header); + if (match.hasMatch()) + m_bscale = match.capturedTexts()[1].toDouble(); + else + m_bscale = 1.0; + + match = cdelt1RE.match(header); + if (match.hasMatch()) + m_cdelta1 = match.capturedTexts()[1].toDouble(); + else + m_cdelta1 = 0.0; + + match = cdelt2RE.match(header); + if (match.hasMatch()) + m_cdelta2 = match.capturedTexts()[1].toDouble(); + else + m_cdelta2 = 0.0; + + match = buintRE.match(header); + if (match.hasMatch()) + { + m_buint = match.capturedTexts()[1].trimmed(); + if (m_buint.contains("MILLI")) + m_uintScale = 0.001f; + else + m_uintScale = 1.0f; + } + else + m_uintScale = 1.0f; + + match = endRE.match(header); + int endIdx = match.capturedStart(0); + if (!match.hasMatch()) + { + qWarning() << "FITS: END missing"; + return; + } + m_dataStart = ((endIdx + m_headerSize) / m_headerSize) * m_headerSize; + m_valid = true; +} + +float FITS::value(int x, int y) const +{ + int offset = m_dataStart + (m_height-1-y) * m_width * m_bytesPerPixel + x * m_bytesPerPixel; + const uchar *data = (const uchar *)m_data.data(); + // Big-endian + int v = 0; + for (int i = m_bytesPerPixel - 1; i >= 0; i--) + v += data[offset++] << (i*8); + if (m_bitsPerPixel > 0) + { + // Sign-extend + switch (m_bytesPerPixel) + { + case 1: + v = (char)v; + break; + case 2: + v = (qint16)v; + break; + case 3: + v = (qint32)v; + break; + } + return v * m_bscale + m_bzero; + } + else + { + // Type-punning via unions apparently undefined behaviour in C++ + uint32_t i = (uint32_t)v; + float f; + memcpy(&f, &i, sizeof(f)); + return f; + } +} + +float FITS::scaledValue(int x, int y) const +{ + float v = value(x, y); + return v * m_uintScale; +} + +int FITS::mod(int a, int b) const +{ + return a - b * floor(a/(double)b); +} + +float FITS::scaledWrappedValue(int x, int y) const +{ + float v = value(mod(x, m_width), mod(y, m_height)); + return v * m_uintScale; +} diff --git a/android/app/src/main/cpp/util/fits.h b/android/app/src/main/cpp/util/fits.h new file mode 100644 index 0000000..c8a7c5f --- /dev/null +++ b/android/app/src/main/cpp/util/fits.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_FITS_H +#define INCLUDE_UTIL_FITS_H + +#include +#include + +#include "export.h" + +// Flexible Image Transport System +// Specification: https://fits.gsfc.nasa.gov/standard40/fits_standard40aa-le.pdf +// This just implements the minimum functionality used by the files +// used by the StarTracker plugin +class SDRBASE_API FITS { + + bool m_valid; + int m_width; // In pixels + int m_height; // In pixels + int m_bitsPerPixel; // BITPIX=-32 means single precision floating point + int m_bytesPerPixel; + int m_bzero; + double m_bscale; + QString m_buint; // "K TB" "MILLIK TB" "K" + float m_uintScale; + double m_cdelta1; // How many degrees RA per horizontal pixel + double m_cdelta2; // How many degrees Declination per vertical pixel + + int m_dataStart; + QByteArray m_data; + +public: + + FITS(QString resourceName); + + float value(int x, int y) const; + float scaledValue(int x, int y) const; + float scaledWrappedValue(int x, int y) const; + + double degreesPerPixelH() const { return m_cdelta1; } + double degreesPerPixelV() const { return m_cdelta2; } + int width() const { return m_width; } + int height() const { return m_height; } + bool valid() const { return m_valid; } + +protected: + + int mod(int a, int b) const; +}; + +#endif // INCLUDE_UTIL_FITS_H diff --git a/android/app/src/main/cpp/util/fixed.h b/android/app/src/main/cpp/util/fixed.h new file mode 100644 index 0000000..9771a10 --- /dev/null +++ b/android/app/src/main/cpp/util/fixed.h @@ -0,0 +1,2441 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// (C) Copyright 2007 Anthony Williams // +// // +// Distributed under the Boost Software License, Version 1.0. // +// See: http://www.boost.org/LICENSE_1_0.txt) // +// // +// Original article: // +// http://www.drdobbs.com/cpp/optimizing-math-intensive-applications-w/207000448 // +// // +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// Modified as fully templatized class with variable size and type internal // +// representation // +// // +// sqrt requires even IntBits // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_FIXED_H_ +#define SDRBASE_UTIL_FIXED_H_ + +#include +#include +#include +#include + +#include "fixedtraits.h" + +template +class Fixed +{ +private: + IntType m_nVal; + + static int64_t scale_cordic_result(int64_t a); + static int64_t right_shift(int64_t val,int shift); + static void perform_cordic_rotation(int64_t&px, int64_t&py, int64_t theta); + static void perform_cordic_polarization(int64_t& argx, int64_t&argy); + +public: + static const Fixed fixed_max; + static const Fixed fixed_one; + static const Fixed fixed_zero; + static const Fixed fixed_half; + static const Fixed fixed_pi; + static const Fixed fixed_two_pi; + static const Fixed fixed_half_pi; + static const Fixed fixed_quarter_pi; + + struct internal + {}; + + Fixed(): + m_nVal(0) + {} + + Fixed(internal, IntType nVal): + m_nVal(nVal) + {} + Fixed(int64_t nVal): + m_nVal(nVal << FixedTraits::fixed_resolution_shift) + {} + + Fixed(int nVal): + m_nVal(int64_t(nVal) << FixedTraits::fixed_resolution_shift) + {} + + Fixed(short nVal): + m_nVal(int64_t(nVal) << FixedTraits::fixed_resolution_shift) + {} + + Fixed(uint64_t nVal): + m_nVal(nVal << FixedTraits::fixed_resolution_shift) + {} + + Fixed(unsigned int nVal): + m_nVal(int64_t(nVal) << FixedTraits::fixed_resolution_shift) + {} + Fixed(unsigned short nVal): + m_nVal(int64_t(nVal) << FixedTraits::fixed_resolution_shift) + {} + Fixed(double nVal): + m_nVal(static_cast(nVal*static_cast(FixedTraits::fixed_resolution))) + {} + Fixed(float nVal): + m_nVal(static_cast(nVal*static_cast(FixedTraits::fixed_resolution))) + {} + + template + Fixed& operator=(T other) + { + m_nVal = Fixed(other).m_nVal; + return *this; + } + + Fixed& operator=(Fixed const& other) + { + m_nVal = other.m_nVal; + return *this; + } + + friend bool operator==(Fixed const& lhs,Fixed const& rhs) + { + return lhs.m_nVal == rhs.m_nVal; + } + + friend bool operator!=(Fixed const& lhs,Fixed const& rhs) + { + return lhs.m_nVal != rhs.m_nVal; + } + + friend bool operator<(Fixed const& lhs,Fixed const& rhs) + { + return lhs.m_nVal < rhs.m_nVal; + } + + friend bool operator>(Fixed const& lhs,Fixed const& rhs) + { + return lhs.m_nVal > rhs.m_nVal; + } + + friend bool operator<=(Fixed const& lhs,Fixed const& rhs) + { + return lhs.m_nVal <= rhs.m_nVal; + } + + friend bool operator>=(Fixed const& lhs,Fixed const& rhs) + { + return lhs.m_nVal >= rhs.m_nVal; + } + + operator bool() const + { + return m_nVal ? true : false; + } + + inline operator double() const + { + return as_double(); + } + + IntType as_internal() const + { + return m_nVal; + } + + float as_float() const + { + return m_nVal / (float) FixedTraits::fixed_resolution; + } + + double as_double() const + { + return m_nVal / (double) FixedTraits::fixed_resolution; + } + + int64_t as_long() const + { + return (int64_t) (m_nVal / FixedTraits::fixed_resolution); + } + int64_t as_int64() const + { + return m_nVal / FixedTraits::fixed_resolution; + } + + int as_int() const + { + return (int) (m_nVal / FixedTraits::fixed_resolution); + } + + uint64_t as_unsigned_long() const + { + return (uint64_t) (m_nVal / FixedTraits::fixed_resolution); + } + uint64_t as_unsigned_int64() const + { + return (uint64_t) (m_nVal / FixedTraits::fixed_resolution); + } + + unsigned int as_unsigned_int() const + { + return (unsigned int) (m_nVal / FixedTraits::fixed_resolution); + } + + short as_short() const + { + return (short) (m_nVal / FixedTraits::fixed_resolution); + } + + unsigned short as_unsigned_short() const + { + return (unsigned short) (m_nVal / FixedTraits::fixed_resolution); + } + + Fixed operator++() + { + m_nVal += FixedTraits::fixed_resolution; + return *this; + } + + Fixed operator--() + { + m_nVal -= FixedTraits::fixed_resolution; + return *this; + } + + Fixed floor() const; + Fixed ceil() const; + Fixed sqrt() const; + Fixed exp() const; + Fixed log() const; + Fixed& operator%=(Fixed const& other); + Fixed& operator*=(Fixed const& val); + Fixed& operator/=(Fixed const& val); + + Fixed& operator-=(Fixed const& val) + { + m_nVal -= val.m_nVal; + return *this; + } + + Fixed& operator+=(Fixed const& val) + { + m_nVal += val.m_nVal; + return *this; + } + + Fixed& operator*=(double val) + { + return (*this) *= Fixed(val); + } + + Fixed& operator*=(float val) + { + return (*this) *= Fixed(val); + } + + Fixed& operator*=(int64_t val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator*=(int val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator*=(short val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator*=(char val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator*=(uint64_t val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator*=(unsigned int val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator*=(unsigned short val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator*=(unsigned char val) + { + m_nVal *= val; + return *this; + } + + Fixed& operator/=(double val) + { + return (*this) /= Fixed(val); + } + + Fixed& operator/=(float val) + { + return (*this) /= Fixed(val); + } + + Fixed& operator/=(int64_t val) + { + m_nVal /= val; + return *this; + } + + Fixed& operator/=(int val) + { + m_nVal /= val; + return *this; + } + + Fixed& operator/=(short val) + { + m_nVal /= val; + return *this; + } + + Fixed& operator/=(char val) + { + m_nVal /= val; + return *this; + } + + Fixed& operator/=(uint64_t val) + { + m_nVal /= val; + return *this; + } + + Fixed& operator/=(unsigned int val) + { + m_nVal/=val; + return *this; + } + + Fixed& operator/=(unsigned short val) + { + m_nVal /= val; + return *this; + } + + Fixed& operator/=(unsigned char val) + { + m_nVal /= val; + return *this; + } + + bool operator!() const + { + return m_nVal == 0; + } + + Fixed modf(Fixed* integral_part) const; + Fixed atan() const; + + static void sin_cos(Fixed const& theta,Fixed* s,Fixed*c); + static void to_polar(Fixed const& x,Fixed const& y,Fixed* r,Fixed*theta); + + Fixed sin() const; + Fixed cos() const; + Fixed tan() const; + Fixed operator-() const; + Fixed abs() const; +}; + +/* why always as double ? +inline std::ostream& operator<<(std::ostream& os,fixed const& value) +{ + return os< +Fixed& Fixed::operator%=(Fixed const& other) +{ + m_nVal = m_nVal%other.m_nVal; + return *this; +} + +template +Fixed& Fixed::operator*=(Fixed const& val) +{ + bool const val_negative = val.m_nVal < 0; + bool const this_negative = m_nVal < 0; + bool const negate = val_negative ^ this_negative; + uint64_t const other = val_negative ? -val.m_nVal : val.m_nVal; + uint64_t const self = this_negative ? -m_nVal : m_nVal; + + if (uint64_t const self_upper = (self >> 32)) { + m_nVal = (self_upper*other) << (32 - FixedTraits::fixed_resolution_shift); + } else { + m_nVal = 0; + } + + if (uint64_t const self_lower = (self&0xffffffff)) + { + uint64_t const other_upper = static_cast(other >> 32); + uint64_t const other_lower = static_cast(other & 0xffffffff); + uint64_t const lower_self_upper_other_res = self_lower*other_upper; + uint64_t const lower_self_lower_other_res = self_lower*other_lower; + m_nVal += (lower_self_upper_other_res << (32 - FixedTraits::fixed_resolution_shift)) + + (lower_self_lower_other_res >> FixedTraits::fixed_resolution_shift); + } + + if (negate) { + m_nVal=-m_nVal; + } + + return *this; +} + +template +Fixed& Fixed::operator/=(Fixed const& divisor) +{ + if( !divisor.m_nVal) + { + m_nVal = fixed_max.m_nVal; + } + else + { + bool const negate_this = (m_nVal < 0); + bool const negate_divisor = (divisor.m_nVal < 0); + bool const negate = negate_this ^ negate_divisor; + uint64_t a = negate_this ? -m_nVal : m_nVal; + uint64_t b = negate_divisor ? -divisor.m_nVal : divisor.m_nVal; + + uint64_t res = 0; + + uint64_t temp = b; + bool const a_large = a > b; + unsigned shift = FixedTraits::fixed_resolution_shift; + + if(a_large) + { + uint64_t const half_a = a>>1; + + while (temp < half_a) + { + temp <<= 1; + ++shift; + } + } + + uint64_t d = 1LL << shift; + + if (a_large) + { + a -= temp; + res += d; + } + + while (a && temp && shift) + { + unsigned right_shift = 0; + + while(right_shift < shift && (temp > a)) + { + temp >>= 1; + ++right_shift; + } + + d >>= right_shift; + shift -= right_shift; + a -= temp; + res += d; + } + + m_nVal = (negate ? -(int64_t) res : res); + } + + return *this; +} + +template +Fixed Fixed::sqrt() const +{ + unsigned int const max_shift = 62; + uint64_t a_squared = 1LL << max_shift; + unsigned int b_shift = (max_shift + FixedTraits::fixed_resolution_shift) / 2; + uint64_t a = 1LL << b_shift; + + uint64_t x = m_nVal; + + while (b_shift && (a_squared > x)) + { + a >>= 1; + a_squared >>= 2; + --b_shift; + } + + uint64_t remainder = x - a_squared; + --b_shift; + + while (remainder && b_shift) + { + uint64_t b_squared = 1LL << (2*b_shift - FixedTraits::fixed_resolution_shift); + int const two_a_b_shift = b_shift + 1 - FixedTraits::fixed_resolution_shift; + uint64_t two_a_b = (two_a_b_shift > 0) ? (a << two_a_b_shift) : (a >> -two_a_b_shift); + + while (b_shift && (remainder < (b_squared+two_a_b))) + { + b_squared >>= 2; + two_a_b >>= 1; + --b_shift; + } + + uint64_t const delta = b_squared + two_a_b; + + if ((2*remainder) > delta) + { + a += (1LL << b_shift); + remainder -= delta; + + if (b_shift) { + --b_shift; + } + } + } + + return Fixed(Fixed::internal(), a); +} + +template +Fixed Fixed::exp() const +{ + if (m_nVal >= FixedTraits::log_two_power_n_reversed[0]) + { + return fixed_max; + } + + if (m_nVal < -FixedTraits::log_two_power_n_reversed[63 - 2*FixedTraits::fixed_resolution_shift]) + { + return Fixed(internal(), 0); + } + + if (!m_nVal) + { + return Fixed(internal(), FixedTraits::fixed_resolution); + } + + int64_t res = FixedTraits::fixed_resolution; + + if (m_nVal > 0) + { + int power = FixedTraits::max_power; + int64_t const* log_entry = FixedTraits::log_two_power_n_reversed; + int64_t temp=m_nVal; + + while (temp && power>(-(int)FixedTraits::fixed_resolution_shift)) + { + while (!power || (temp<*log_entry)) + { + if (!power) { + log_entry = FixedTraits::log_one_plus_two_power_minus_n; + } else { + ++log_entry; + } + + --power; + } + + temp -= *log_entry; + + if (power < 0) { + res += (res >> (-power)); + } else { + res <<= power; + } + } + } + else + { + int power = FixedTraits::fixed_resolution_shift; + int64_t const* log_entry = FixedTraits::log_two_power_n_reversed + (FixedTraits::max_power-power); + int64_t temp=m_nVal; + + while (temp && (power > (-(int) FixedTraits::fixed_resolution_shift))) + { + while (!power || (temp > (-*log_entry))) + { + if(!power) { + log_entry = FixedTraits::log_one_over_one_minus_two_power_minus_n; + } else { + ++log_entry; + } + + --power; + } + + temp += *log_entry; + + if (power <0 ) { + res -= (res >> (-power)); + } else { + res >>= power; + } + } + } + + return Fixed(Fixed::internal(), res); +} + +template +Fixed Fixed::log() const +{ + if ( m_nVal <= 0) { + return -fixed_max; + } + + if (m_nVal == FixedTraits::fixed_resolution) { + return fixed_zero; + } + + uint64_t temp = m_nVal; + int left_shift = 0; + uint64_t const scale_position = 0x8000000000000000ULL; + + while (temp < scale_position) + { + ++left_shift; + temp <<= 1; + } + + int64_t res = (left_shift < FixedTraits::max_power) ? + FixedTraits::log_two_power_n_reversed[left_shift] : + -FixedTraits::log_two_power_n_reversed[2*FixedTraits::max_power - left_shift]; + unsigned int right_shift = 1; + uint64_t shifted_temp = temp >> 1; + + while (temp && (right_shift < FixedTraits::fixed_resolution_shift)) + { + while ((right_shift < FixedTraits::fixed_resolution_shift) && (temp < (shifted_temp + scale_position))) + { + shifted_temp >>= 1; + ++right_shift; + } + + temp -= shifted_temp; + shifted_temp = temp >> right_shift; + res += FixedTraits::log_one_over_one_minus_two_power_minus_n[right_shift-1]; + } + + return Fixed(Fixed::internal(), res); +} + +template +int64_t Fixed::scale_cordic_result(int64_t a) +{ + int64_t const cordic_scale_factor = 0x22C2DD1C; /* 0.271572 * 2^31*/ + return (int64_t)((((int64_t)a)*cordic_scale_factor) >> 31); +} + +template +int64_t Fixed::right_shift(int64_t val, int shift) +{ + return (shift < 0) ? (val << -shift) : (val >> shift); +} + +template +void Fixed::perform_cordic_rotation(int64_t&px, int64_t&py, int64_t theta) +{ + int64_t x = px, y = py; + int64_t const *arctanptr = FixedTraits::arctantab; + for (int i = -1; i <= (int) FixedTraits::fixed_resolution_shift; ++i) + { + int64_t const yshift = right_shift(y,i); + int64_t const xshift = right_shift(x,i); + + if (theta < 0) + { + x += yshift; + y -= xshift; + theta += *arctanptr++; + } + else + { + x -= yshift; + y += xshift; + theta -= *arctanptr++; + } + } + + px = scale_cordic_result(x); + py = scale_cordic_result(y); +} + +template +void Fixed::perform_cordic_polarization(int64_t& argx, int64_t&argy) +{ + int64_t theta = 0; + int64_t x = argx, y = argy; + int64_t const *arctanptr = FixedTraits::arctantab; + + for (int i = -1; i <= (int) FixedTraits::fixed_resolution_shift; ++i) + { + int64_t const yshift = right_shift(y,i); + int64_t const xshift = right_shift(x,i); + + if(y < 0) + { + y += xshift; + x -= yshift; + theta -= *arctanptr++; + } + else + { + y -= xshift; + x += yshift; + theta += *arctanptr++; + } + } + + argx = scale_cordic_result(x); + argy = theta; +} + +template +void Fixed::sin_cos(Fixed const& theta, Fixed* s, Fixed*c) +{ + int64_t x = theta.m_nVal % FixedTraits::internal_two_pi; + + if (x < 0) { + x += FixedTraits::internal_two_pi; + } + + bool negate_cos = false; + bool negate_sin = false; + + if (x > FixedTraits::internal_pi) + { + x = FixedTraits::internal_two_pi - x; + negate_sin=true; + } + + if (x > FixedTraits::internal_half_pi) + { + x = FixedTraits::internal_pi - x; + negate_cos=true; + } + + int64_t x_cos = 1<::fixed_resolution_shift, x_sin = 0; + + perform_cordic_rotation(x_cos, x_sin, (int64_t) x); + + if (s) { + s->m_nVal = negate_sin ? -x_sin : x_sin; + } + + if(c) { + c->m_nVal = negate_cos ? -x_cos : x_cos; + } +} + +template +Fixed Fixed::atan() const +{ + Fixed r, theta; + to_polar(1, *this, &r, &theta); + return theta; +} + +template +void Fixed::to_polar(Fixed const& x, Fixed const& y, Fixed* r, Fixed *theta) +{ + bool const negative_x = x.m_nVal < 0; + bool const negative_y = y.m_nVal < 0; + + uint64_t a = negative_x ? -x.m_nVal : x.m_nVal; + uint64_t b = negative_y ? -y.m_nVal : y.m_nVal; + + unsigned int right_shift = 0; + unsigned const max_value = 1U << FixedTraits::fixed_resolution_shift; + + while ((a >= max_value) || (b >= max_value)) + { + ++right_shift; + a >>= 1; + b >>= 1; + } + + int64_t xtemp = (int64_t) a; + int64_t ytemp = (int64_t) b; + + perform_cordic_polarization(xtemp, ytemp); + + r->m_nVal = int64_t(xtemp) << right_shift; + theta->m_nVal = ytemp; + + if (negative_x && negative_y) { + theta->m_nVal -= FixedTraits::internal_pi; + } else if (negative_x) { + theta->m_nVal = FixedTraits::internal_pi - theta->m_nVal; + } + + else if(negative_y) { + theta->m_nVal = -theta->m_nVal; + } +} + +template +inline Fixed operator-(double a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + + +template +inline Fixed operator-(float a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(uint64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(int64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(unsigned a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(int a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(unsigned short a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(short a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(unsigned char a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(char a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, double b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, float b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a,uint64_t b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, int64_t b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, unsigned b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, int b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, unsigned short b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, short b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, unsigned char b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, char b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator-(Fixed const& a, Fixed const& b) +{ + Fixed temp(a); + return temp -= b; +} + +template +inline Fixed operator%(double a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(float a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(uint64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(int64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(unsigned a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(int a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(unsigned short a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(short a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(unsigned char a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(char a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a,double b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, float b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, uint64_t b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, int64_t b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, unsigned b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, int b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, unsigned short b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, short b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, unsigned char b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, char b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator%(Fixed const& a, Fixed const& b) +{ + Fixed temp(a); + return temp %= b; +} + +template +inline Fixed operator+(double a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(float a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(uint64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(int64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(unsigned a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(int a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(unsigned short a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(short a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(unsigned char a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(char a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, double b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, float b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, uint64_t b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, int64_t b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, unsigned b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, int b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, unsigned short b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, short b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, unsigned char b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, char b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator+(Fixed const& a, Fixed const& b) +{ + Fixed temp(a); + return temp += b; +} + +template +inline Fixed operator*(double a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(float a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(uint64_t a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(int64_t a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(unsigned a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(int a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(unsigned short a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(short a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(unsigned char a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(char a, Fixed const& b) +{ + Fixed temp(b); + return temp *= a; +} + +template +inline Fixed operator*(Fixed const& a, double b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, float b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, uint64_t b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, int64_t b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, unsigned b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, int b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, unsigned short b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, short b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, unsigned char b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, char b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator*(Fixed const& a, Fixed const& b) +{ + Fixed temp(a); + return temp *= b; +} + +template +inline Fixed operator/(double a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(float a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(uint64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(int64_t a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(unsigned a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(int a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(unsigned short a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(short a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(unsigned char a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(char a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, double b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, float b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, uint64_t b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, int64_t b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, unsigned b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, int b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, unsigned short b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, short b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, unsigned char b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, char b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline Fixed operator/(Fixed const& a, Fixed const& b) +{ + Fixed temp(a); + return temp /= b; +} + +template +inline bool operator==(double a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(float a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(uint64_t a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(int64_t a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(unsigned a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(int a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(unsigned short a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(short a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(unsigned char a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(char a, Fixed const& b) +{ + return Fixed(a) == b; +} + +template +inline bool operator==(Fixed const& a, double b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, float b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, uint64_t b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, int64_t b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, unsigned b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, int b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, unsigned short b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, short b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, unsigned char b) +{ + return a == Fixed(b); +} + +template +inline bool operator==(Fixed const& a, char b) +{ + return a == Fixed(b); +} + +template +inline bool operator!=(double a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(float a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(uint64_t a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(int64_t a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(unsigned a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(int a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(unsigned short a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(short a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(unsigned char a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(char a, Fixed const& b) +{ + return Fixed(a) != b; +} + +template +inline bool operator!=(Fixed const& a, double b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, float b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, uint64_t b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, int64_t b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, unsigned b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, int b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, unsigned short b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, short b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, unsigned char b) +{ + return a != Fixed(b); +} + +template +inline bool operator!=(Fixed const& a, char b) +{ + return a != Fixed(b); +} + +template +inline bool operator<(double a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(float a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(uint64_t a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(int64_t a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(unsigned a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(int a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(unsigned short a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(short a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(unsigned char a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(char a, Fixed const& b) +{ + return Fixed(a) < b; +} + +template +inline bool operator<(Fixed const& a, double b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, float b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, uint64_t b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, int64_t b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, unsigned b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, int b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, unsigned short b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, short b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, unsigned char b) +{ + return a < Fixed(b); +} + +template +inline bool operator<(Fixed const& a, char b) +{ + return a < Fixed(b); +} + +template +inline bool operator>(double a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(float a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(uint64_t a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(int64_t a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(unsigned a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(int a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(unsigned short a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(short a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(unsigned char a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(char a, Fixed const& b) +{ + return Fixed(a) > b; +} + +template +inline bool operator>(Fixed const& a, double b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, float b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, uint64_t b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, int64_t b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, unsigned b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, int b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, unsigned short b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, short b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, unsigned char b) +{ + return a > Fixed(b); +} + +template +inline bool operator>(Fixed const& a, char b) +{ + return a > Fixed(b); +} + +template +inline bool operator<=(double a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(float a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(uint64_t a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(int64_t a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(unsigned a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(int a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(unsigned short a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(short a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(unsigned char a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(char a, Fixed const& b) +{ + return Fixed(a) <= b; +} + +template +inline bool operator<=(Fixed const& a, double b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a, float b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a, uint64_t b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a, int64_t b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a, unsigned b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a, int b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a, unsigned short b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a, short b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a,unsigned char b) +{ + return a <= Fixed(b); +} + +template +inline bool operator<=(Fixed const& a,char b) +{ + return a <= Fixed(b); +} + +template +inline bool operator>=(double a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(float a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(uint64_t a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(int64_t a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(unsigned a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(int a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(unsigned short a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(short a, Fixed const& b) +{ + return Fixed(a)>=b; +} + +template +inline bool operator>=(unsigned char a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(char a, Fixed const& b) +{ + return Fixed(a) >= b; +} + +template +inline bool operator>=(Fixed const& a, double b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a,float b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, uint64_t b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, int64_t b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, unsigned b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, int b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, unsigned short b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, short b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, unsigned char b) +{ + return a >= Fixed(b); +} + +template +inline bool operator>=(Fixed const& a, char b) +{ + return a >= Fixed(b); +} + +template +inline Fixed sin(Fixed const& x) +{ + return x.sin(); +} + +template +inline Fixed cos(Fixed const& x) +{ + return x.cos(); +} + +template +inline Fixed tan(Fixed const& x) +{ + return x.tan(); +} + +template +inline Fixed sqrt(Fixed const& x) +{ + return x.sqrt(); +} + +template +inline Fixed exp(Fixed const& x) +{ + return x.exp(); +} + +template +inline Fixed log(Fixed const& x) +{ + return x.log(); +} + +template +inline Fixed floor(Fixed const& x) +{ + return x.floor(); +} + +template +inline Fixed ceil(Fixed const& x) +{ + return x.ceil(); +} + +template +inline Fixed abs(Fixed const& x) +{ + return x.abs(); +} + +template +inline Fixed modf(Fixed const& x, Fixed*integral_part) +{ + return x.modf(integral_part); +} + +template +inline Fixed Fixed::ceil() const +{ + if (m_nVal % FixedTraits::fixed_resolution) { + return floor()+1; + } else { + return *this; + } +} + +template +inline Fixed Fixed::floor() const +{ + Fixed res(*this); + int64_t const remainder = m_nVal % FixedTraits::fixed_resolution; + + if (remainder) + { + res.m_nVal -= remainder; + + if(m_nVal<0) { + res-=1; + } + } + + return res; +} + +template +inline Fixed Fixed::sin() const +{ + Fixed res; + sin_cos(*this, &res, 0); + return res; +} + +template +inline Fixed Fixed::cos() const +{ + Fixed res; + sin_cos(*this, 0, &res); + return res; +} + +template +inline Fixed Fixed::tan() const +{ + Fixed s, c; + sin_cos(*this, &s, &c); + return s/c; +} + +template +inline Fixed Fixed::operator-() const +{ + return Fixed(internal(), -m_nVal); +} + +template +inline Fixed Fixed::abs() const +{ + return Fixed(Fixed::internal(), m_nVal < 0 ? -m_nVal : m_nVal); +} + +template +inline Fixed Fixed::modf(Fixed *integral_part) const +{ + int64_t fractional_part = m_nVal % FixedTraits::fixed_resolution; + if ((m_nVal < 0) && (fractional_part > 0)) { + fractional_part -= FixedTraits::fixed_resolution; + } + + integral_part->m_nVal = m_nVal - fractional_part; + return Fixed(Fixed::internal(), fractional_part); +} + +template +inline Fixed arg(const std::complex >& val) +{ + Fixed r,theta; + Fixed::to_polar(val.real(),val.imag(),&r,&theta); + return theta; +} + +template +inline std::complex > polar(const Fixed& rho, const Fixed& theta) +{ + Fixed s,c; + Fixed::sin_cos(theta,&s,&c); + return std::complex >(rho * c, rho * s); +} + +template +const Fixed Fixed::fixed_max(Fixed::internal(), std::numeric_limits::max()); + +template +const Fixed Fixed::fixed_one(Fixed::internal(), 1LL << (FixedTraits::fixed_resolution_shift)); + +template +const Fixed Fixed::fixed_zero(Fixed::internal(), 0); + +template +const Fixed Fixed::fixed_half(Fixed::internal(), 1LL << (FixedTraits::fixed_resolution_shift-1)); + +template +const Fixed Fixed::fixed_pi(Fixed::internal(), FixedTraits::internal_pi); + +template +const Fixed Fixed::fixed_two_pi(Fixed::internal(), FixedTraits::internal_two_pi); + +template +const Fixed Fixed::fixed_half_pi(Fixed::internal(), FixedTraits::internal_half_pi); + +template +const Fixed Fixed::fixed_quarter_pi(Fixed::internal(), FixedTraits::internal_quarter_pi); + +#endif /* SDRBASE_UTIL_FIXED_H_ */ diff --git a/android/app/src/main/cpp/util/fixedaverage2d.h b/android/app/src/main/cpp/util/fixedaverage2d.h new file mode 100644 index 0000000..0aaa248 --- /dev/null +++ b/android/app/src/main/cpp/util/fixedaverage2d.h @@ -0,0 +1,125 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_FIXEDAVERAGE2D_H_ +#define SDRBASE_UTIL_FIXEDAVERAGE2D_H_ + +#include + +template +class FixedAverage2D +{ +public: + FixedAverage2D() : m_sum(0), m_maxSize(0), m_width(0), m_size(0), m_maxIndex(0) {} + + ~FixedAverage2D() + { + if (m_sum) { + delete[] m_sum; + } + } + + void resize(unsigned int width, unsigned int size) + { + if (width > m_maxSize) + { + m_maxSize = width; + if (m_sum) { + delete[] m_sum; + } + m_sum = new T[m_maxSize]; + } + + m_width = width; + m_size = size; + + std::fill(m_sum, m_sum+m_width, 0); + m_maxIndex = 0; + } + + bool storeAndGetAvg(T& avg, T v, unsigned int index) + { + if (m_size <= 1) + { + avg = v; + return true; + } + + m_sum[index] += v; + + if (m_maxIndex == m_size - 1) + { + avg = m_sum[index]/m_size; + return true; + } + else + { + return false; + } + } + + bool storeAndGetSum(T& sum, T v, unsigned int index) + { + if (m_size <= 1) + { + sum = v; + return true; + } + + m_sum[index] += v; + + if (m_maxIndex < m_size - 1) + { + sum = m_sum[index]; + return true; + } + else + { + return false; + } + } + + bool nextAverage() + { + if (m_size <= 1) { + return true; + } + + if (m_maxIndex == m_size - 1) + { + m_maxIndex = 0; + std::fill(m_sum, m_sum+m_width, 0); + return true; + } + else + { + m_maxIndex++; + return false; + } + } + +private: + T *m_sum; + unsigned int m_maxSize; + unsigned int m_width; + unsigned int m_size; + unsigned int m_maxIndex; +}; + + + +#endif /* SDRBASE_UTIL_FIXEDAVERAGE2D_H_ */ diff --git a/android/app/src/main/cpp/util/fixedtraits.cpp b/android/app/src/main/cpp/util/fixedtraits.cpp new file mode 100644 index 0000000..e63d775 --- /dev/null +++ b/android/app/src/main/cpp/util/fixedtraits.cpp @@ -0,0 +1,549 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "fixedtraits.h" + +// 1.0 = 2^28 internal representation + +// ln(1/2^n), n = index+1 then list is reversed +const int64_t FixedTraits<28>::log_two_power_n_reversed[35] = { + 0x18429946ELL, + 0x1791272EFLL, + 0x16DFB516FLL, + 0x162E42FF0LL, + 0x157CD0E70LL, + 0x14CB5ECF1LL, + 0x1419ECB71LL, + 0x13687A9F2LL, + 0x12B708872LL, + 0x1205966F3LL, + 0x115424573LL, + 0x10A2B23F4LL, + 0xFF140274LL, + 0xF3FCE0F5LL, + 0xE8E5BF75LL, + 0xDDCE9DF6LL, + 0xD2B77C76LL, + 0xC7A05AF7LL, + 0xBC893977LL, + 0xB17217F8LL, + 0xA65AF679LL, + 0x9B43D4F9LL, + 0x902CB379LL, + 0x851591FaLL, + 0x79FE707bLL, + 0x6EE74EFbLL, + 0x63D02D7BLL, + 0x58B90BFcLL, + 0x4DA1EA7CLL, + 0x428AC8FdLL, + 0x3773A77DLL, + 0x2C5C85FeLL, + 0x2145647ELL, + 0x162E42FfLL, + 0xB17217FLL +}; + +// ln(1+2^-n), n = index+1 +const int64_t FixedTraits<28>::log_one_plus_two_power_minus_n[28] = { + 0x67CC8FBLL, + 0x391FEF9LL, + 0x1E27077LL, + 0xF85186LL, + 0x7E0A6CLL, + 0x3F8151LL, + 0x1FE02ALL, + 0xFF805LL, + 0x7FE01LL, + 0x3FF80LL, + 0x1FFE0LL, + 0xFFF8LL, + 0x7FFELL, + 0x4000LL, + 0x2000LL, + 0x1000LL, + 0x800LL, + 0x400LL, + 0x200LL, + 0x100LL, + 0x80LL, + 0x40LL, + 0x20LL, + 0x10LL, + 0x8LL, + 0x4LL, + 0x2LL, + 0x1LL +}; + +// ln(1/1-2^-n), n = index+1 +const int64_t FixedTraits<28>::log_one_over_one_minus_two_power_minus_n[28] = { + 0xB172180LL, + 0x49A5884LL, + 0x222F1D0LL, + 0x108598BLL, + 0x820AECLL, + 0x408159LL, + 0x20202BLL, + 0x100805LL, + 0x80201LL, + 0x40080LL, + 0x20020LL, + 0x10008LL, + 0x8002LL, + 0x4001LL, + 0x2000LL, + 0x1000LL, + 0x800LL, + 0x400LL, + 0x200LL, + 0x100LL, + 0x80LL, + 0x40LL, + 0x20LL, + 0x10LL, + 0x8LL, + 0x4LL, + 0x2LL, + 0x1LL +}; + +const int64_t FixedTraits<28>::arctantab[32] = { + 297197971, + 210828714, + 124459457, + 65760959, + 33381290, + 16755422, + 8385879, + 4193963, + 2097109, + 1048571, + 524287, + 262144, + 131072, + 65536, + 32768, + 16384, + 8192, + 4096, + 2048, + 1024, + 512, + 256, + 128, + 64, + 32, + 16, + 8, + 4, + 2, + 1, + 0, + 0 +}; + +// 1.0 = 2^16 internal representation + +const int64_t FixedTraits<16>::log_two_power_n_reversed[47] = { + 2135026LL, + 2089600LL, + 2044174LL, + 1998748LL, + 1953322LL, + 1907896LL, + 1862470LL, + 1817044LL, + 1771618LL, + 1726192LL, + 1680765LL, + 1635339LL, + 1589913LL, + 1544487LL, + 1499061LL, + 1453635LL, + 1408209LL, + 1362783LL, + 1317357LL, + 1271931LL, + 1226505LL, + 1181078LL, + 1135652LL, + 1090226LL, + 1044800LL, + 999374LL, + 953948LL, + 908522LL, + 863096LL, + 817670LL, + 772244LL, + 726817LL, + 681391LL, + 635965LL, + 590539LL, + 545113LL, + 499687LL, + 454261LL, + 408835LL, + 363409LL, + 317983LL, + 272557LL, + 227130LL, + 181704LL, + 136278LL, + 90852LL, + 45426LL +}; + +const int64_t FixedTraits<16>::log_one_plus_two_power_minus_n[16] = { + 26573LL, + 14624LL, + 7719LL, + 3973LL, + 2017LL, + 1016LL, + 510LL, + 256LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL +}; + +const int64_t FixedTraits<16>::log_one_over_one_minus_two_power_minus_n[16] = { + 45426LL, + 18854LL, + 8751LL, + 4230LL, + 2081LL, + 1032LL, + 514LL, + 257LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL +}; + +const int64_t FixedTraits<16>::arctantab[32] = { + 72558LL, + 51471LL, + 30385LL, + 16054LL, + 8149LL, + 4090LL, + 2047LL, + 1023LL, + 511LL, + 255LL, + 127LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL +}; + +// 1.0 = 2^23 internal representation + +const int64_t FixedTraits<23>::log_two_power_n_reversed[40] = { + 232581599LL, + 226767059LL, + 220952519LL, + 215137979LL, + 209323439LL, + 203508899LL, + 197694359LL, + 191879819LL, + 186065279LL, + 180250740LL, + 174436200LL, + 168621660LL, + 162807120LL, + 156992580LL, + 151178040LL, + 145363500LL, + 139548960LL, + 133734420LL, + 127919880LL, + 122105340LL, + 116290800LL, + 110476260LL, + 104661720LL, + 98847180LL, + 93032640LL, + 87218100LL, + 81403560LL, + 75589020LL, + 69774480LL, + 63959940LL, + 58145400LL, + 52330860LL, + 46516320LL, + 40701780LL, + 34887240LL, + 29072700LL, + 23258160LL, + 17443620LL, + 11629080LL, + 5814540LL +}; + +const int64_t FixedTraits<23>::log_one_plus_two_power_minus_n[23] = { + 3401288LL, + 1871864LL, + 988036LL, + 508556LL, + 258131LL, + 130059LL, + 65281LL, + 32704LL, + 16368LL, + 8188LL, + 4095LL, + 2048LL, + 1024LL, + 512LL, + 256LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL +}; + +const int64_t FixedTraits<23>::log_one_over_one_minus_two_power_minus_n[23] = { + 5814540LL, + 2413252LL, + 1120143LL, + 541388LL, + 266327LL, + 132107LL, + 65793LL, + 32832LL, + 16400LL, + 8196LL, + 4097LL, + 2048LL, + 1024LL, + 512LL, + 256LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL +}; + +const int64_t FixedTraits<23>::arctantab[32] = { + 9287436LL, + 6588397LL, + 3889358LL, + 2055029LL, + 1043165LL, + 523606LL, + 262058LL, + 131061LL, + 65534LL, + 32767LL, + 16383LL, + 8192LL, + 4096LL, + 2048LL, + 1024LL, + 512LL, + 256LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL +}; + +// 1.0 = 2^24 internal representation + +const int64_t FixedTraits<24>::log_two_power_n_reversed[39] = { + 453534119LL, + 441905039LL, + 430275959LL, + 418646879LL, + 407017799LL, + 395388719LL, + 383759639LL, + 372130559LL, + 360501479LL, + 348872399LL, + 337243319LL, + 325614239LL, + 313985159LL, + 302356079LL, + 290726999LL, + 279097919LL, + 267468839LL, + 255839759LL, + 244210679LL, + 232581599LL, + 220952519LL, + 209323439LL, + 197694359LL, + 186065279LL, + 174436200LL, + 162807120LL, + 151178040LL, + 139548960LL, + 127919880LL, + 116290800LL, + 104661720LL, + 93032640LL, + 81403560LL, + 69774480LL, + 58145400LL, + 46516320LL, + 34887240LL, + 23258160LL, + 11629080LL +}; + +const int64_t FixedTraits<24>::log_one_plus_two_power_minus_n[24] = { + 6802576LL, + 3743728LL, + 1976071LL, + 1017112LL, + 516263LL, + 260117LL, + 130563LL, + 65408LL, + 32736LL, + 16376LL, + 8190LL, + 4096LL, + 2048LL, + 1024LL, + 512LL, + 256LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL +}; + +const int64_t FixedTraits<24>::log_one_over_one_minus_two_power_minus_n[24] = { + 11629080LL, + 4826504LL, + 2240285LL, + 1082777LL, + 532655LL, + 264214LL, + 131587LL, + 65664LL, + 32800LL, + 16392LL, + 8194LL, + 4097LL, + 2048LL, + 1024LL, + 512LL, + 256LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL +}; + +const int64_t FixedTraits<24>::arctantab[32] = { + 18574873LL, + 13176794LL, + 7778716LL, + 4110059LL, + 2086330LL, + 1047213LL, + 524117LL, + 262122LL, + 131069LL, + 65535LL, + 32767LL, + 16384LL, + 8192LL, + 4096LL, + 2048LL, + 1024LL, + 512LL, + 256LL, + 128LL, + 64LL, + 32LL, + 16LL, + 8LL, + 4LL, + 2LL, + 1LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL, + 0LL +}; + diff --git a/android/app/src/main/cpp/util/fixedtraits.h b/android/app/src/main/cpp/util/fixedtraits.h new file mode 100644 index 0000000..809b555 --- /dev/null +++ b/android/app/src/main/cpp/util/fixedtraits.h @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_FIXEDTRAITS_H_ +#define SDRBASE_UTIL_FIXEDTRAITS_H_ + +#include + +#include "export.h" + +template +class FixedTraits +{ +}; + +template<> +struct SDRBASE_API FixedTraits<28> +{ + static const uint32_t fixed_resolution_shift = 28; //!< 1.0 representation. 28 is the highest power of two that can represent 9.99999... safely on 64 bits internally + static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; + static const int32_t max_power = 63 - fixed_resolution_shift; + static const int64_t internal_pi = 0x3243f6a8; + static const int64_t internal_two_pi = 0x6487ed51; + static const int64_t internal_half_pi = 0x1921fb54; + static const int64_t internal_quarter_pi = 0xc90fdaa; + static const int64_t log_two_power_n_reversed[35]; // 35 = 63 - 28 + static const int64_t log_one_plus_two_power_minus_n[28]; + static const int64_t log_one_over_one_minus_two_power_minus_n[28]; + static const int64_t arctantab[32]; +}; + +template<> +struct SDRBASE_API FixedTraits<16> +{ + static const uint32_t fixed_resolution_shift = 16; + static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; + static const int32_t max_power = 63 - fixed_resolution_shift; + static const int64_t internal_pi = 205887; + static const int64_t internal_two_pi = 411775; + static const int64_t internal_half_pi = 102944; + static const int64_t internal_quarter_pi = 51472; + static const int64_t log_two_power_n_reversed[47]; // 47 = 63 - 16 + static const int64_t log_one_plus_two_power_minus_n[16]; + static const int64_t log_one_over_one_minus_two_power_minus_n[16]; + static const int64_t arctantab[32]; +}; + +template<> +struct SDRBASE_API FixedTraits<23> +{ + static const uint32_t fixed_resolution_shift = 23; + static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; + static const int32_t max_power = 63 - fixed_resolution_shift; + static const int64_t internal_pi = 26353589; + static const int64_t internal_two_pi = 52707179; + static const int64_t internal_half_pi = 13176795; + static const int64_t internal_quarter_pi = 6588397; + static const int64_t log_two_power_n_reversed[40]; // 40 = 63 - 23 + static const int64_t log_one_plus_two_power_minus_n[23]; + static const int64_t log_one_over_one_minus_two_power_minus_n[23]; + static const int64_t arctantab[32]; +}; + +template<> +struct SDRBASE_API FixedTraits<24> +{ + static const uint32_t fixed_resolution_shift = 24; + static const int64_t fixed_resolution = 1LL << fixed_resolution_shift; + static const int32_t max_power = 63 - fixed_resolution_shift; + static const int64_t internal_pi = 52707179; + static const int64_t internal_two_pi = 105414357; + static const int64_t internal_half_pi = 26353589; + static const int64_t internal_quarter_pi = 13176795; + static const int64_t log_two_power_n_reversed[39]; // 39 = 63 - 24 + static const int64_t log_one_plus_two_power_minus_n[24]; + static const int64_t log_one_over_one_minus_two_power_minus_n[24]; + static const int64_t arctantab[32]; +}; + +#endif /* SDRBASE_UTIL_FIXEDTRAITS_H_ */ diff --git a/android/app/src/main/cpp/util/flightinformation.cpp b/android/app/src/main/cpp/util/flightinformation.cpp new file mode 100644 index 0000000..c99abdc --- /dev/null +++ b/android/app/src/main/cpp/util/flightinformation.cpp @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "flightinformation.h" + +#include +#include +#include +#include +#include +#include +#include + +FlightInformation::FlightInformation() +{ +} + +FlightInformation* FlightInformation::create(const QString& apiKey, const QString& service) +{ + if (service == "aviationstack.com") + { + if (!apiKey.isEmpty()) + { + return new AviationStack(apiKey); + } + else + { + qDebug() << "FlightInformation::create: An API key is required for: " << service; + return nullptr; + } + } + else + { + qDebug() << "FlightInformation::create: Unsupported service: " << service; + return nullptr; + } +} + +AviationStack::AviationStack(const QString& apiKey) : + m_apiKey(apiKey) +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &AviationStack::handleReply + ); +} + +AviationStack::~AviationStack() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &AviationStack::handleReply + ); + delete m_networkManager; +} + +void AviationStack::getFlightInformation(const QString& flight) +{ + QUrl url(QString("http://api.aviationstack.com/v1/flights")); + QUrlQuery query; + query.addQueryItem("flight_icao",flight); + query.addQueryItem("access_key", m_apiKey); + url.setQuery(query); + + m_networkManager->get(QNetworkRequest(url)); +} + +void AviationStack::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + parseJson(reply->readAll()); + } + else + { + qDebug() << "AviationStack::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "AviationStack::handleReply: reply is null"; + } +} + +void AviationStack::parseJson(QByteArray bytes) +{ + QJsonDocument document = QJsonDocument::fromJson(bytes); + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("data"))) + { + QJsonArray data = obj.value(QStringLiteral("data")).toArray(); + if (data.size() > 0) + { + QJsonObject flightObj = data[0].toObject(); + + Flight flight; + + if (flightObj.contains(QStringLiteral("flight_status"))) { + flight.m_flightStatus = flightObj.value(QStringLiteral("flight_status")).toString(); + } + if (flightObj.contains(QStringLiteral("departure"))) + { + QJsonObject departure = flightObj.value(QStringLiteral("departure")).toObject(); + flight.m_departureAirport = departure.value(QStringLiteral("airport")).toString(); + flight.m_departureICAO = departure.value(QStringLiteral("icao")).toString(); + flight.m_departureTerminal = departure.value(QStringLiteral("terminal")).toString(); + flight.m_departureGate = departure.value(QStringLiteral("gate")).toString(); + flight.m_departureScheduled = QDateTime::fromString(departure.value(QStringLiteral("scheduled")).toString(), Qt::ISODate); + flight.m_departureEstimated = QDateTime::fromString(departure.value(QStringLiteral("estimated")).toString(), Qt::ISODate); + flight.m_departureActual = QDateTime::fromString(departure.value(QStringLiteral("actual")).toString(), Qt::ISODate); + } + if (flightObj.contains(QStringLiteral("arrival"))) + { + QJsonObject departure = flightObj.value(QStringLiteral("arrival")).toObject(); + flight.m_arrivalAirport = departure.value(QStringLiteral("airport")).toString(); + flight.m_arrivalICAO = departure.value(QStringLiteral("icao")).toString(); + flight.m_arrivalTerminal = departure.value(QStringLiteral("terminal")).toString(); + flight.m_arrivalGate = departure.value(QStringLiteral("gate")).toString(); + flight.m_arrivalScheduled = QDateTime::fromString(departure.value(QStringLiteral("scheduled")).toString(), Qt::ISODate); + flight.m_arrivalEstimated = QDateTime::fromString(departure.value(QStringLiteral("estimated")).toString(), Qt::ISODate); + flight.m_arrivalActual = QDateTime::fromString(departure.value(QStringLiteral("actual")).toString(), Qt::ISODate); + } + if (flightObj.contains(QStringLiteral("flight"))) + { + QJsonObject flightNo = flightObj.value(QStringLiteral("flight")).toObject(); + flight.m_flightICAO = flightNo.value(QStringLiteral("icao")).toString(); + flight.m_flightIATA = flightNo.value(QStringLiteral("iata")).toString(); + } + emit flightUpdated(flight); + } + else + { + qDebug() << "AviationStack::handleReply: data array is empty"; + } + } + else + { + qDebug() << "AviationStack::handleReply: Object doesn't contain data: " << obj; + } + } + else + { + qDebug() << "AviationStack::handleReply: Document is not an object: " << document; + } +} diff --git a/android/app/src/main/cpp/util/flightinformation.h b/android/app/src/main/cpp/util/flightinformation.h new file mode 100644 index 0000000..93a84c1 --- /dev/null +++ b/android/app/src/main/cpp/util/flightinformation.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FLIGHTINFORMATION_H +#define INCLUDE_FLIGHTINFORMATION_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +// Flight information API wrapper +// Allows searching for departure/arrival airports and status of a flight +// Currently supports aviationstack.com +class SDRBASE_API FlightInformation : public QObject +{ + Q_OBJECT + +protected: + FlightInformation(); + +public: + struct Flight { + QString m_flightICAO; + QString m_flightIATA; + QString m_flightStatus; // active, landed... + QString m_departureAirport; + QString m_departureICAO; + QString m_departureTerminal; + QString m_departureGate; + QDateTime m_departureScheduled; + QDateTime m_departureEstimated; + QDateTime m_departureActual; + QString m_arrivalAirport; + QString m_arrivalICAO; + QString m_arrivalTerminal; + QString m_arrivalGate; + QDateTime m_arrivalScheduled; + QDateTime m_arrivalEstimated; + QDateTime m_arrivalActual; + }; + + static FlightInformation* create(const QString& apiKey, const QString& service="aviationstack.com"); + + virtual void getFlightInformation(const QString& flight) = 0; + +signals: + void flightUpdated(const Flight& flight); // Called when new data available. + +private: + +}; + +class SDRBASE_API AviationStack : public FlightInformation { + Q_OBJECT +public: + + AviationStack(const QString& apiKey); + ~AviationStack(); + virtual void getFlightInformation(const QString& flight) override; + +private: + void parseJson(QByteArray bytes); + + QString m_apiKey; + QNetworkAccessManager *m_networkManager; + +public slots: + void handleReply(QNetworkReply* reply); + +}; + +#endif /* INCLUDE_FLIGHTINFORMATION_H */ diff --git a/android/app/src/main/cpp/util/ft8message.cpp b/android/app/src/main/cpp/util/ft8message.cpp new file mode 100644 index 0000000..8ecb0aa --- /dev/null +++ b/android/app/src/main/cpp/util/ft8message.cpp @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "ft8message.h" + +MESSAGE_CLASS_DEFINITION(MsgReportFT8Messages, Message) diff --git a/android/app/src/main/cpp/util/ft8message.h b/android/app/src/main/cpp/util/ft8message.h new file mode 100644 index 0000000..cd3ae00 --- /dev/null +++ b/android/app/src/main/cpp/util/ft8message.h @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_FT8MESSAGE_H +#define INCLUDE_FT8MESSAGE_H + +#include +#include +#include + +#include "export.h" +#include "message.h" + +struct SDRBASE_API FT8Message +{ + QDateTime ts; + QString type; + int pass; + int snr; + int nbCorrectBits; + float dt; + float df; + QString call1; + QString call2; + QString loc; + QString decoderInfo; +}; + +class SDRBASE_API MsgReportFT8Messages : public Message { + MESSAGE_CLASS_DECLARATION +public: + QList& getFT8Messages() { return m_ft8Messages; } + void setBaseFrequency(qint64 baseFrequency) { m_baseFrequency = baseFrequency; } + + static MsgReportFT8Messages* create() { + return new MsgReportFT8Messages(); + } + +private: + QList m_ft8Messages; + qint64 m_baseFrequency; + + MsgReportFT8Messages() : + Message(), + m_baseFrequency(0) + { } +}; + +#endif // INCLUDE_FT8MESSAGE_H diff --git a/android/app/src/main/cpp/util/giro.cpp b/android/app/src/main/cpp/util/giro.cpp new file mode 100644 index 0000000..09969a5 --- /dev/null +++ b/android/app/src/main/cpp/util/giro.cpp @@ -0,0 +1,324 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "giro.h" + +#include +#include +#include +#include +#include +#include + +GIRO::GIRO() +{ + connect(&m_indexTimer, &QTimer::timeout, this, &GIRO::getIndex); + connect(&m_dataTimer, &QTimer::timeout, this, &GIRO::getData); + connect(&m_mufTimer, &QTimer::timeout, this, qOverload<>(&GIRO::getMUF)); + connect(&m_foF2Timer, &QTimer::timeout, this, qOverload<>(&GIRO::getfoF2)); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &GIRO::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("giro"))) { + qDebug() << "Failed to create cache/giro"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("giro")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); +} + +GIRO::~GIRO() +{ + disconnect(&m_indexTimer, &QTimer::timeout, this, &GIRO::getIndex); + disconnect(&m_dataTimer, &QTimer::timeout, this, &GIRO::getData); + disconnect(&m_mufTimer, &QTimer::timeout, this, qOverload<>(&GIRO::getMUF)); + disconnect(&m_foF2Timer, &QTimer::timeout, this, qOverload<>(&GIRO::getfoF2)); + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &GIRO::handleReply); + delete m_networkManager; +} + +GIRO* GIRO::create(const QString& service) +{ + if (service == "prop.kc2g.com") + { + return new GIRO(); + } + else + { + qDebug() << "GIRO::create: Unsupported service: " << service; + return nullptr; + } +} + +void GIRO::getIndexPeriodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_indexTimer.setInterval(periodInMins*60*1000); + m_indexTimer.start(); + getIndex(); + } + else + { + m_indexTimer.stop(); + } +} + +void GIRO::getDataPeriodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_dataTimer.setInterval(periodInMins*60*1000); + m_dataTimer.start(); + getData(); + } + else + { + m_dataTimer.stop(); + } +} + +void GIRO::getMUFPeriodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_mufTimer.setInterval(periodInMins*60*1000); + m_mufTimer.start(); + getMUF(); + } + else + { + m_mufTimer.stop(); + } +} + +void GIRO::getfoF2Periodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_foF2Timer.setInterval(periodInMins*60*1000); + m_foF2Timer.start(); + getfoF2(); + } + else + { + m_foF2Timer.stop(); + } +} + +void GIRO::getIndex() +{ + QUrl url(QString("https://prop.kc2g.com/api/available_nowcasts.json?days=5")); + m_networkManager->get(QNetworkRequest(url)); +} + +void GIRO::getData() +{ + QUrl url(QString("https://prop.kc2g.com/api/stations.json")); + m_networkManager->get(QNetworkRequest(url)); +} + +void GIRO::getfoF2() +{ + getMUF("current"); +} + +void GIRO::getMUF() +{ + getMUF("current"); +} + +void GIRO::getfoF2(const QString& runId) +{ + QUrl url(QString("https://prop.kc2g.com/renders/%1/fof2-normal-now.geojson").arg(runId)); + m_networkManager->get(QNetworkRequest(url)); +} + +void GIRO::getMUF(const QString& runId) +{ + QUrl url(QString("https://prop.kc2g.com/renders/%1/mufd-normal-now.geojson").arg(runId)); + m_networkManager->get(QNetworkRequest(url)); +} + +bool GIRO::containsNonNull(const QJsonObject& obj, const QString &key) const +{ + if (obj.contains(key)) + { + QJsonValue val = obj.value(key); + return !val.isNull(); + } + return false; +} + +void GIRO::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + + QString fileName = reply->url().fileName(); + if (fileName == "available_nowcasts.json") + { + handleIndex(document); + } + else if (fileName == "stations.json") + { + handleStations(document); + } + else if (fileName == "mufd-normal-now.geojson") + { + emit mufUpdated(document); + } + else if (fileName == "fof2-normal-now.geojson") + { + emit foF2Updated(document); + } + else + { + qDebug() << "GIRO::handleReply: unexpected filename: " << fileName; + } + } + else + { + qDebug() << "GIRO::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "GIRO::handleReply: reply is null"; + } +} + +void GIRO::handleStations(QJsonDocument& document) +{ + if (document.isArray()) + { + QJsonArray array = document.array(); + for (auto valRef : array) + { + if (valRef.isObject()) + { + QJsonObject obj = valRef.toObject(); + + GIROStationData data; + + if (obj.contains(QStringLiteral("station"))) + { + QJsonObject stationObj = obj.value(QStringLiteral("station")).toObject(); + + if (stationObj.contains(QStringLiteral("name"))) { + data.m_station = stationObj.value(QStringLiteral("name")).toString(); + } + if (stationObj.contains(QStringLiteral("latitude"))) { + data.m_latitude = (float)stationObj.value(QStringLiteral("latitude")).toString().toFloat(); + } + if (stationObj.contains(QStringLiteral("longitude"))) { + data.m_longitude = (float)stationObj.value(QStringLiteral("longitude")).toString().toFloat(); + if (data.m_longitude >= 180.0f) { + data.m_longitude -= 360.0f; + } + } + } + + if (containsNonNull(obj, QStringLiteral("time"))) { + data.m_dateTime = QDateTime::fromString(obj.value(QStringLiteral("time")).toString(), Qt::ISODateWithMs); + } + if (containsNonNull(obj, QStringLiteral("mufd"))) { + data.m_mufd = (float)obj.value(QStringLiteral("mufd")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("md"))) { + data.m_md = obj.value(QStringLiteral("md")).toString().toFloat(); + } + if (containsNonNull(obj, QStringLiteral("tec"))) { + data.m_tec = (float)obj.value(QStringLiteral("tec")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("fof2"))) { + data.m_foF2 = (float)obj.value(QStringLiteral("fof2")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("hmf2"))) { + data.m_hmF2 = (float)obj.value(QStringLiteral("hmf2")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("foe"))) { + data.m_foE = (float)obj.value(QStringLiteral("foe")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("cs"))) { + data.m_confidence = (int)obj.value(QStringLiteral("cs")).toDouble(); + } + + emit dataUpdated(data); + } + else + { + qDebug() << "GIRO::handleReply: Array element is not an object: " << valRef; + } + } + } + else + { + qDebug() << "GIRO::handleReply: Document is not an array: " << document; + } +} + +void GIRO::handleIndex(QJsonDocument& document) +{ + if (document.isArray()) + { + QJsonArray array = document.array(); + + m_index.clear(); + + for (auto valRef : array) + { + if (valRef.isObject()) + { + QJsonObject obj = valRef.toObject(); + + DataSet item; + + int ts = obj.value(QStringLiteral("ts")).toInt(); + item.m_dateTime = QDateTime::fromSecsSinceEpoch(ts); + + item.m_runId = QString::number(obj.value(QStringLiteral("run_id")).toInt()); + + qDebug() << item.m_dateTime << item.m_runId; + + m_index.append(item); + } + } + + emit indexUpdated(m_index); + } +} + +QString GIRO::getRunId(const QDateTime& dateTime) +{ + // Index is ordered newest first + for (int i = 0; i < m_index.size(); i++) + { + if (dateTime > m_index[i].m_dateTime) { + return m_index[i].m_runId; + } + } + return ""; +} diff --git a/android/app/src/main/cpp/util/giro.h b/android/app/src/main/cpp/util/giro.h new file mode 100644 index 0000000..1c10ee0 --- /dev/null +++ b/android/app/src/main/cpp/util/giro.h @@ -0,0 +1,118 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GIRO_H +#define INCLUDE_GIRO_H + +#include +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// GIRO - Global Ionosphere Radio Observatory +// Gets MUFD, TEC, foF2 and other data for various stations around the world +// Also gets MUF and foF2 contours as GeoJSON +// Data from https://prop.kc2g.com/stations/ +class SDRBASE_API GIRO : public QObject +{ + Q_OBJECT +protected: + GIRO(); + +public: + // See the following paper for an explanation of some of these variables + // https://sbgf.org.br/mysbgf/eventos/expanded_abstracts/13th_CISBGf/A%20Simple%20Method%20to%20Calculate%20the%20Maximum%20Usable%20Frequency.pdf + struct GIROStationData { + QString m_station; + float m_latitude; + float m_longitude; + QDateTime m_dateTime; + float m_mufd; // Maximum usable frequency + float m_md; // Propagation coefficient? D=3000km? + float m_tec; // Total electron content + float m_foF2; // Critical frequency of F2 layer in ionosphere (highest frequency to be reflected) + float m_hmF2; // F2 layer height of peak electron density (km?) + float m_foE; // Critical frequency of E layer + int m_confidence; + GIROStationData() : + m_latitude(NAN), + m_longitude(NAN), + m_mufd(NAN), + m_md(NAN), + m_tec(NAN), + m_foF2(NAN), + m_hmF2(NAN), + m_foE(NAN), + m_confidence(-1) + { + } + }; + + struct DataSet { + QDateTime m_dateTime; + QString m_runId; + }; + + static GIRO* create(const QString& service="prop.kc2g.com"); + + ~GIRO(); + void getIndexPeriodically(int periodInMins=15); + void getDataPeriodically(int periodInMins=2); + void getMUFPeriodically(int periodInMins=15); + void getfoF2Periodically(int periodInMins=15); + + void getMUF(const QString& runId); + void getfoF2(const QString& runId); + + QString getRunId(const QDateTime& dateTime); + +public slots: + void getIndex(); + void getData(); + void getMUF(); + void getfoF2(); + +private slots: + void handleReply(QNetworkReply* reply); + +signals: + void indexUpdated(const QList& data); + void dataUpdated(const GIROStationData& data); // Called when new data available. + void mufUpdated(const QJsonDocument& doc); + void foF2Updated(const QJsonDocument& doc); + +private: + bool containsNonNull(const QJsonObject& obj, const QString &key) const; + void handleIndex(QJsonDocument& document); + void handleStations(QJsonDocument& document); + + QTimer m_indexTimer; + QTimer m_dataTimer; // Timer for periodic updates + QTimer m_mufTimer; + QTimer m_foF2Timer; + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + QList m_index; + +}; + +#endif /* INCLUDE_GIRO_H */ diff --git a/android/app/src/main/cpp/util/goesxray.cpp b/android/app/src/main/cpp/util/goesxray.cpp new file mode 100644 index 0000000..6e976f2 --- /dev/null +++ b/android/app/src/main/cpp/util/goesxray.cpp @@ -0,0 +1,223 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "goesxray.h" + +#include +#include +#include +#include +#include + +GOESXRay::GOESXRay() +{ + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &GOESXRay::handleReply); + connect(&m_dataTimer, &QTimer::timeout, this, &GOESXRay::getData); +} + + +GOESXRay::~GOESXRay() +{ + disconnect(&m_dataTimer, &QTimer::timeout, this, &GOESXRay::getData); + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &GOESXRay::handleReply); + delete m_networkManager; +} + +GOESXRay* GOESXRay::create(const QString& service) +{ + if (service == "services.swpc.noaa.gov") + { + return new GOESXRay(); + } + else + { + qDebug() << "GOESXRay::create: Unsupported service: " << service; + return nullptr; + } +} + +void GOESXRay::getDataPeriodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_dataTimer.setInterval(periodInMins*60*1000); + m_dataTimer.start(); + getData(); + } + else + { + m_dataTimer.stop(); + } +} + +void GOESXRay::getData() +{ + // Around 160kB per file + QUrl url(QString("https://services.swpc.noaa.gov/json/goes/primary/xrays-6-hour.json")); + m_networkManager->get(QNetworkRequest(url)); + + QUrl secondaryURL(QString("https://services.swpc.noaa.gov/json/goes/secondary/xrays-6-hour.json")); + m_networkManager->get(QNetworkRequest(secondaryURL)); + + QUrl protonPrimaryURL(QString("https://services.swpc.noaa.gov/json/goes/primary/integral-protons-plot-6-hour.json")); + m_networkManager->get(QNetworkRequest(protonPrimaryURL)); +} + +bool GOESXRay::containsNonNull(const QJsonObject& obj, const QString &key) const +{ + if (obj.contains(key)) + { + QJsonValue val = obj.value(key); + return !val.isNull(); + } + return false; +} + +void GOESXRay::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QByteArray bytes = reply->readAll(); + bool primary = reply->url().toString().contains("primary"); + + if (reply->url().fileName() == "xrays-6-hour.json") { + handleXRayJson(bytes, primary); + } else if (reply->url().fileName() == "integral-protons-plot-6-hour.json") { + handleProtonJson(bytes, primary); + } else { + qDebug() << "GOESXRay::handleReply: unexpected filename: " << reply->url().fileName(); + } + } + else + { + qDebug() << "GOESXRay::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "GOESXRay::handleReply: reply is null"; + } +} + +void GOESXRay::handleXRayJson(const QByteArray& bytes, bool primary) +{ + QJsonDocument document = QJsonDocument::fromJson(bytes); + if (document.isArray()) + { + QJsonArray array = document.array(); + QList data; + for (auto valRef : array) + { + if (valRef.isObject()) + { + QJsonObject obj = valRef.toObject(); + + XRayData measurement; + + if (obj.contains(QStringLiteral("satellite"))) { + measurement.m_satellite = QString("GOES %1").arg(obj.value(QStringLiteral("satellite")).toInt()); + } + if (containsNonNull(obj, QStringLiteral("time_tag"))) { + measurement.m_dateTime = QDateTime::fromString(obj.value(QStringLiteral("time_tag")).toString(), Qt::ISODate); + } + if (containsNonNull(obj, QStringLiteral("flux"))) { + measurement.m_flux = obj.value(QStringLiteral("flux")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("energy"))) + { + QString energy = obj.value(QStringLiteral("energy")).toString(); + if (energy == "0.05-0.4nm") { + measurement.m_band = XRayData::SHORT; + } else if (energy == "0.1-0.8nm") { + measurement.m_band = XRayData::LONG; + } else { + qDebug() << "GOESXRay::handleXRayJson: Unknown energy: " << energy; + } + } + + data.append(measurement); + } + else + { + qDebug() << "GOESXRay::handleXRayJson: Array element is not an object: " << valRef; + } + } + if (data.size() > 0) { + emit xRayDataUpdated(data, primary); + } else { + qDebug() << "GOESXRay::handleXRayJson: No data in array: " << document; + } + } + else + { + qDebug() << "GOESXRay::handleXRayJson: Document is not an array: " << document; + } +} + +void GOESXRay::handleProtonJson(const QByteArray& bytes, bool primary) +{ + QJsonDocument document = QJsonDocument::fromJson(bytes); + if (document.isArray()) + { + QJsonArray array = document.array(); + QList data; + for (auto valRef : array) + { + if (valRef.isObject()) + { + QJsonObject obj = valRef.toObject(); + + ProtonData measurement; + + if (obj.contains(QStringLiteral("satellite"))) { + measurement.m_satellite = QString("GOES %1").arg(obj.value(QStringLiteral("satellite")).toInt()); + } + if (containsNonNull(obj, QStringLiteral("time_tag"))) { + measurement.m_dateTime = QDateTime::fromString(obj.value(QStringLiteral("time_tag")).toString(), Qt::ISODate); + } + if (containsNonNull(obj, QStringLiteral("flux"))) { + measurement.m_flux = obj.value(QStringLiteral("flux")).toDouble(); + } + if (containsNonNull(obj, QStringLiteral("energy"))) + { + QString energy = obj.value(QStringLiteral("energy")).toString(); + QString value = energy.mid(2).split(' ')[0]; + measurement.m_energy = value.toInt(); // String like: ">=50 MeV" + } + + data.append(measurement); + } + else + { + qDebug() << "GOESXRay::handleProtonJson: Array element is not an object: " << valRef; + } + } + if (data.size() > 0) { + emit protonDataUpdated(data, primary); + } else { + qDebug() << "GOESXRay::handleProtonJson: No data in array: " << document; + } + } + else + { + qDebug() << "GOESXRay::handleProtonJson: Document is not an array: " << document; + } +} diff --git a/android/app/src/main/cpp/util/goesxray.h b/android/app/src/main/cpp/util/goesxray.h new file mode 100644 index 0000000..c7e6d0f --- /dev/null +++ b/android/app/src/main/cpp/util/goesxray.h @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GOESXRAY_H +#define INCLUDE_GOESXRAY_H + +#include +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +// GOES X-Ray data +// This gets 1-minute averages of solar X-rays the 1-8 Angstrom (0.1-0.8 nm) and 0.5-4.0 Angstrom (0.05-0.4 nm) passbands from the GOES satellites +// https://www.swpc.noaa.gov/products/goes-x-ray-flux +// There are primary and secondary data sources, from different satellites, as sometimes they can be in eclipse +// Also gets Proton flux (Which may be observed on Earth a couple of days after a large flare/CME) +class SDRBASE_API GOESXRay : public QObject +{ + Q_OBJECT +protected: + GOESXRay(); + +public: + struct XRayData { + QDateTime m_dateTime; + QString m_satellite; + double m_flux; + enum Band { + UNKNOWN, + SHORT, // 0.05-0.4nm + LONG // 0.1-0.8nm + } m_band; + XRayData() : + m_flux(NAN), + m_band(UNKNOWN) + { + } + }; + + struct ProtonData { + QDateTime m_dateTime; + QString m_satellite; + double m_flux; + int m_energy; // 10=10MeV, 50MeV, 100MeV, 500MeV + ProtonData() : + m_flux(NAN), + m_energy(0) + { + } + }; + + static GOESXRay* create(const QString& service="services.swpc.noaa.gov"); + + ~GOESXRay(); + void getDataPeriodically(int periodInMins=10); + +public slots: + void getData(); + +private slots: + void handleReply(QNetworkReply* reply); + +signals: + void xRayDataUpdated(const QList& data, bool primary); // Called when new data available. + void protonDataUpdated(const QList &data, bool primary); + +private: + bool containsNonNull(const QJsonObject& obj, const QString &key) const; + void handleXRayJson(const QByteArray& bytes, bool primary); + void handleProtonJson(const QByteArray& bytes, bool primary); + + QTimer m_dataTimer; // Timer for periodic updates + QNetworkAccessManager *m_networkManager; + +}; + +#endif /* INCLUDE_GOESXRAY_H */ + diff --git a/android/app/src/main/cpp/util/golay2312.cpp b/android/app/src/main/cpp/util/golay2312.cpp new file mode 100644 index 0000000..0ad1cf7 --- /dev/null +++ b/android/app/src/main/cpp/util/golay2312.cpp @@ -0,0 +1,253 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "golay2312.h" + +const unsigned int Golay2312::m_B[11] = { + 0b101001001111, + 0b111101101000, + 0b011110110100, + 0b001111011010, + 0b000111101101, + 0b101010111001, + 0b111100010011, + 0b110111000110, + 0b011011100011, + 0b100100111110, + 0b010010011111, +}; + +const unsigned int Golay2312::m_I11[11] = { + 0b10000000000, + 0b01000000000, + 0b00100000000, + 0b00010000000, + 0b00001000000, + 0b00000100000, + 0b00000010000, + 0b00000001000, + 0b00000000100, + 0b00000000010, + 0b00000000001, +}; + +const unsigned int Golay2312::m_I12[12] = { + 0b100000000000, + 0b010000000000, + 0b001000000000, + 0b000100000000, + 0b000010000000, + 0b000001000000, + 0b000000100000, + 0b000000010000, + 0b000000001000, + 0b000000000100, + 0b000000000010, + 0b000000000001, +}; + +Golay2312::Golay2312() +{ + initG(); + initH(); + buildCorrMatrix(m_corrPL, m_HPL); + buildCorrMatrix(m_corrPF, m_HPF, true); +} + +Golay2312::~Golay2312() +{ +} + +void Golay2312::initG() +{ + for (int r = 0; r < 23; r++) + { + // parity last + if (r < 12) { + m_GPL[r] = m_I12[r]; + } else { + m_GPL[r] = m_B[r-12]; + } + // parity first + if (r < 11) { + m_GPF[r] = m_B[r]; + } else { + m_GPF[r] = m_I12[r-11]; + } + } +} + +void Golay2312::initH() +{ + for (int r = 0; r < 11; r++) + { + m_HPL[r] = (m_B[r] << 11) + m_I11[r]; // parity last + m_HPF[r] = (m_I11[r] << 12) + m_B[r]; // parity first + } +} + +void Golay2312::encodeParityLast(unsigned int msg, unsigned int *tx) +{ + *tx = 0; + + for (int r = 0; r < 23; r++) { + *tx += (std::bitset<32>(m_GPL[r] & msg).count() % 2) << (22-r); + } +} + +void Golay2312::encodeParityFirst(unsigned int msg, unsigned int *tx) +{ + *tx = 0; + + for (int r = 0; r < 23; r++) { + *tx += (std::bitset<32>(m_GPF[r] & msg).count() % 2) << (22-r); + } +} + +bool Golay2312::decodeParityLast(unsigned int *rx) +{ + unsigned int s = syn(m_HPL, *rx); + return lut(m_corrPL, s, rx); +} + +bool Golay2312::decodeParityFirst(unsigned int *rx) +{ + unsigned int s = syn(m_HPF, *rx); + return lut(m_corrPF, s, rx); +} + +unsigned int Golay2312::syn(unsigned int *H, unsigned int rx) +{ + unsigned int s = 0; + + for (int r = 0; r < 11; r++) { + s += (std::bitset<32>(H[r] & rx).count() % 2) << (10-r); + } + + return s; +} + +bool Golay2312::lut(unsigned char *corr, unsigned int syndrome, unsigned int *rx) +{ + if (syndrome == 0) { + return true; + } + + int i = 0; + + for (; i < 3; i++) + { + if (corr[3*syndrome + i] == 0xFF) { + break; + } else { + *rx ^= (1 << corr[3*syndrome + i]); // flip bit + } + } + + if (i == 0) { + return false; + } + + return true; +} + +void Golay2312::buildCorrMatrix(unsigned char *corr, unsigned int *H, bool pf) +{ + int shiftP = pf ? 12 : 0; // shift in position value for parity bits + int shiftW = pf ? 0 : 11; // shift in position value for message word bits + std::fill(corr, corr + 3*2048, 0xFF); + int syndromeI; + unsigned int cw; + + for (int i1 = 0; i1 < 12; i1++) + { + for (int i2 = i1+1; i2 < 12; i2++) + { + for (int i3 = i2+1; i3 < 12; i3++) + { + // 3 bit patterns (in message) + cw = (1 << (i1+shiftW)) + (1 << (i2+shiftW)) + (1 << (i3+shiftW)); + syndromeI = syn(H, cw); + corr[3*syndromeI + 0] = i1 + shiftW; + corr[3*syndromeI + 1] = i2 + shiftW; + corr[3*syndromeI + 2] = i3 + shiftW; + } + + // 2 bit patterns (in message) + cw = (1 << (i1+shiftW)) + (1 << (i2+shiftW)); + syndromeI = syn(H, cw); + corr[3*syndromeI + 0] = i1 + shiftW; + corr[3*syndromeI + 1] = i2 + shiftW; + + // 1 possible bit flip left in the parity part + for (int ip = 0; ip < 11; ip++) + { + int syndromeIP = syndromeI ^ (1 << (10-ip)); + corr[3*syndromeIP + 0] = i1 + shiftW; + corr[3*syndromeIP + 1] = i2 + shiftW; + corr[3*syndromeIP + 2] = 10 - ip + shiftP; + } + } + + // single bit patterns (in message) + cw = (1 << (i1+shiftW)); + syndromeI = syn(H, cw); + corr[3*syndromeI + 0] = i1 + shiftW; + + for (int ip1 = 0; ip1 < 11; ip1++) // 1 more bit flip in parity + { + int syndromeIP1 = syndromeI ^ (1 << (10-ip1)); + corr[3*syndromeIP1 + 0] = i1 + shiftW; + corr[3*syndromeIP1 + 1] = 10 - ip1 + shiftP; + + for (int ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity + { + int syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2)); + corr[3*syndromeIP2 + 0] = i1 + shiftW; + corr[3*syndromeIP2 + 1] = 10 - ip1 + shiftP; + corr[3*syndromeIP2 + 2] = 10 - ip2 + shiftP; + } + } + } + + // no bit patterns (in message) -> all in parity + + for (int ip1 = 0; ip1 < 11; ip1++) // 1 bit flip in parity + { + int syndromeIP1 = (1 << (10-ip1)); + corr[3*syndromeIP1 + 0] = 10 - ip1 + shiftP; + + for (int ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity + { + int syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2)); + corr[3*syndromeIP2 + 0] = 10 - ip1 + shiftP; + corr[3*syndromeIP2 + 1] = 10 - ip2 + shiftP; + + for (int ip3 = ip2+1; ip3 < 11; ip3++) // 1 more bit flip in parity + { + int syndromeIP3 = syndromeIP2 ^ (1 << (10-ip3)); + corr[3*syndromeIP3 + 0] = 10 - ip1 + shiftP; + corr[3*syndromeIP3 + 1] = 10 - ip2 + shiftP; + corr[3*syndromeIP3 + 2] = 10 - ip3 + shiftP; + } + } + } +} + diff --git a/android/app/src/main/cpp/util/golay2312.h b/android/app/src/main/cpp/util/golay2312.h new file mode 100644 index 0000000..ec4bd19 --- /dev/null +++ b/android/app/src/main/cpp/util/golay2312.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GOLAY2312_H_ +#define INCLUDE_GOLAY2312_H_ + +#include "export.h" + +class SDRBASE_API Golay2312 +{ +public: + Golay2312(); + ~Golay2312(); + + void encodeParityLast(unsigned int msg, unsigned int *tx); + void encodeParityFirst(unsigned int msg, unsigned int *tx); + bool decodeParityLast(unsigned int *rx); + bool decodeParityFirst(unsigned int *rx); + +private: + inline int bitAt(int i, unsigned int v) { + return (v>>i) & 0x01; + } + void initG(); + void initH(); + void buildCorrMatrix(unsigned char *corr, unsigned int *H, bool pf = false); + unsigned int syn(unsigned int *H, unsigned int rx); + bool lut(unsigned char *corr, unsigned int syndrome, unsigned int *rx); + + unsigned char m_corrPL[2048*3]; //!< up to 3 bit error correction by syndrome index - parity last + unsigned char m_corrPF[2048*3]; //!< up to 3 bit error correction by syndrome index - parity first + unsigned int m_GPL[23]; //!< Generator matrix of 23x12 bits - parity first (MSB) + unsigned int m_GPF[23]; //!< Generator matrix of 23x12 bits - parity last (LSB) + unsigned int m_HPL[11]; //!< Parity check matrix of 11x23 bits - parity last (LSB) + unsigned int m_HPF[11]; //!< Parity check matrix of 11x23 bits - parity first (MSB) + // building arrays + static const unsigned int m_B[11]; //!< Coding matrix (11x12 bits) + static const unsigned int m_I12[12]; //!< 12x12 identity matrix (12x12 bits) + static const unsigned int m_I11[11]; //!< 11x11 identity matrix (11x11 bits) + +}; + +#endif // INCLUDE_GOLAY2312_H_ diff --git a/android/app/src/main/cpp/util/grb.cpp b/android/app/src/main/cpp/util/grb.cpp new file mode 100644 index 0000000..e42593b --- /dev/null +++ b/android/app/src/main/cpp/util/grb.cpp @@ -0,0 +1,198 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "grb.h" +#include "util/csv.h" + +#include +#include +#include +#include +#include + +GRB::GRB() +{ + connect(&m_dataTimer, &QTimer::timeout, this,&GRB::getData); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &GRB::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("grb"))) { + qDebug() << "Failed to create cache/grb"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("grb")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); +} + +GRB::~GRB() +{ + disconnect(&m_dataTimer, &QTimer::timeout, this, &GRB::getData); + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &GRB::handleReply); + delete m_networkManager; +} + +GRB* GRB::create() +{ + return new GRB(); +} + +void GRB::getDataPeriodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_dataTimer.setInterval(periodInMins*60*1000); + m_dataTimer.start(); + getData(); + } + else + { + m_dataTimer.stop(); + } +} + +void GRB::getData() +{ + QUrl url("https://user-web.icecube.wisc.edu/~grbweb_public/Summary_table.txt"); + + m_networkManager->get(QNetworkRequest(url)); +} + +void GRB::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + if (reply->url().fileName().endsWith(".txt")) + { + QByteArray bytes = reply->readAll(); + handleText(bytes); + } + else + { + qDebug() << "GRB::handleReply: Unexpected file" << reply->url().fileName(); + } + } + else + { + qDebug() << "GRB::handleReply: Error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "GRB::handleReply: Reply is null"; + } +} + +void GRB::handleText(QByteArray& bytes) +{ + // Convert to CSV + QString s(bytes); + QStringList l = s.split("\n"); + for (int i = 0; i < l.size(); i++) { + l[i] = l[i].simplified().replace(" ", ","); + } + s = l.join("\n"); + + QTextStream in(&s); + + // Skip header + for (int i = 0; i < 4; i++) { + in.readLine(); + } + + QList grbs; + QStringList cols; + while(CSV::readRow(in, &cols)) + { + Data grb; + + if (cols.length() >= 10) + { + grb.m_name = cols[0]; + grb.m_fermiName = cols[1]; + int year = grb.m_name.mid(3, 2).toInt(); + if (year >= 90) { + year += 1900; + } else { + year += 2000; + } + QDate date(year, grb.m_name.mid(5, 2).toInt(), grb.m_name.mid(7, 2).toInt()); + QTime time = QTime::fromString(cols[2]); + grb.m_dateTime = QDateTime(date, time); + grb.m_ra = cols[3].toFloat(); + grb.m_dec = cols[4].toFloat(); + grb.m_fluence = cols[9].toFloat(); + + //qDebug() << grb.m_name << grb.m_dateTime.toString() << grb.m_ra << grb.m_dec << grb.m_fluence ; + + if (grb.m_dateTime.isValid()) { + grbs.append(grb); + } + } + } + + emit dataUpdated(grbs); +} + + QString GRB::Data::getFermiURL() const +{ + if (m_fermiName.isEmpty() || (m_fermiName == "None")) { + return ""; + } + QString base = "https://heasarc.gsfc.nasa.gov/FTP/fermi/data/gbm/bursts/"; + QString yearDir = "20" + m_fermiName.mid(3, 2); + QString dataDir = m_fermiName; + dataDir.replace("GRB", "bn"); + return base + yearDir + "/" + dataDir + "/current/"; +} + +QString GRB::Data::getFermiPlotURL() const +{ + QString base = getFermiURL(); + if (base.isEmpty()) { + return ""; + } + + QString name = m_fermiName; + name.replace("GRB", "bn"); + return getFermiURL() + "glg_lc_all_" + name + "_v00.gif"; // Could be v01.gif? How to know without fetching index? +} + +QString GRB::Data::getFermiSkyMapURL() const +{ + QString base = getFermiURL(); + if (base.isEmpty()) { + return ""; + } + + QString name = m_fermiName; + name.replace("GRB", "bn"); + return getFermiURL() + "glg_skymap_all_" + name + "_v00.png"; +} + +QString GRB::Data::getSwiftURL() const +{ + QString name = m_name; + name.replace("GRB", ""); + return "https://swift.gsfc.nasa.gov/archive/grb_table/" + name; +} diff --git a/android/app/src/main/cpp/util/grb.h b/android/app/src/main/cpp/util/grb.h new file mode 100644 index 0000000..a0075de --- /dev/null +++ b/android/app/src/main/cpp/util/grb.h @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GRB_H +#define INCLUDE_GRB_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// GRB (Gamma Ray Burst) database +// Gets GRB database from GRBweb https://user-web.icecube.wisc.edu/~grbweb_public/ +// Uses summary .txt file so only contains last 1000 GRBs +class SDRBASE_API GRB : public QObject +{ + Q_OBJECT +protected: + GRB(); + +public: + + struct SDRBASE_API Data { + + QString m_name; // E.g: GRB240310A + QString m_fermiName; // Name used by Fermi telescope. E.g. GRB240310236. Can be None if not detected by Fermi + QDateTime m_dateTime; + float m_ra; // Right Ascension + float m_dec; // Declination + float m_fluence; // erg/cm^2 + + QString getFermiURL() const; // Get URL where Fermi data is stored + QString getFermiPlotURL() const; + QString getFermiSkyMapURL() const; + QString getSwiftURL() const; + + }; + + static GRB* create(); + + ~GRB(); + void getDataPeriodically(int periodInMins=1440); // GRBweb is updated every 24 hours, usually just after 9am UTC + +public slots: + void getData(); + +private slots: + void handleReply(QNetworkReply* reply); + +signals: + void dataUpdated(const QListdata); // Called when new data is available. + +private: + + QTimer m_dataTimer; // Timer for periodic updates + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + + void handleText(QByteArray& bytes); + +}; + +#endif /* INCLUDE_GRB_H */ diff --git a/android/app/src/main/cpp/util/httpdownloadmanager.cpp b/android/app/src/main/cpp/util/httpdownloadmanager.cpp new file mode 100644 index 0000000..f98da9c --- /dev/null +++ b/android/app/src/main/cpp/util/httpdownloadmanager.cpp @@ -0,0 +1,233 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "httpdownloadmanager.h" + +#include +#include +#include +#include + +HttpDownloadManager::HttpDownloadManager() +{ + connect(&manager, &QNetworkAccessManager::finished, this, &HttpDownloadManager::downloadFinished); +} + +QNetworkReply *HttpDownloadManager::download(const QUrl &url, const QString &filename) +{ + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + QNetworkReply *reply = manager.get(request); + + connect(reply, &QNetworkReply::sslErrors, this, &HttpDownloadManager::sslErrors); + + qDebug() << "HttpDownloadManager: Downloading from " << url << " to " << filename; + m_downloads.append(reply); + m_filenames.append(filename); + return reply; +} + +// Indicate if we have any downloads in progress +bool HttpDownloadManager::downloading() const +{ + return m_filenames.size() > 0; +} + +qint64 HttpDownloadManager::fileAgeInDays(const QString& filename) +{ + QFile file(filename); + if (file.exists()) + { + QDateTime modified = file.fileTime(QFileDevice::FileModificationTime); + if (modified.isValid()) + return modified.daysTo(QDateTime::currentDateTime()); + else + return -1; + } + return -1; +} + +// Get default directory to write downloads to +QString HttpDownloadManager::downloadDir() +{ + // Get directory to store app data in + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + // First dir is writable + return locations[0]; +} + +void HttpDownloadManager::sslErrors(const QList &sslErrors) +{ + for (const QSslError &error : sslErrors) + { + qCritical() << "HttpDownloadManager: SSL error" << (int)error.error() << ": " << error.errorString(); +#ifdef ANDROID + // On Android 6 (but not on 12), we always seem to get: "The issuer certificate of a locally looked up certificate could not be found" + // which causes downloads to fail, so ignore + if (error.error() == QSslError::UnableToGetLocalIssuerCertificate) + { + QNetworkReply *reply = qobject_cast(sender()); + QList errorsThatCanBeIgnored; + errorsThatCanBeIgnored << QSslError(QSslError::UnableToGetLocalIssuerCertificate, error.certificate()); + reply->ignoreSslErrors(errorsThatCanBeIgnored); + } +#endif + } +} + +bool HttpDownloadManager::isHttpRedirect(QNetworkReply *reply) +{ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // 304 is file not changed, but maybe we did + return (status >= 301 && status <= 308); +} + +bool HttpDownloadManager::writeToFile(const QString &filename, const QByteArray &data) +{ + QFile file(filename); + + // Make sure directory to save the file in exists + QFileInfo fileInfo(filename); + QDir dir = fileInfo.absoluteDir(); + if (!dir.exists()) + dir.mkpath("."); + + if (file.open(QIODevice::WriteOnly)) + { + file.write(data); + file.close(); + return true; + } + else + { + qCritical() << "HttpDownloadManager: Could not open " << filename << " for writing: " << file.errorString(); + return false; + } +} + +void HttpDownloadManager::downloadFinished(QNetworkReply *reply) +{ + QString url = reply->url().toEncoded().constData(); + int idx = m_downloads.indexOf(reply); + QString filename = m_filenames[idx]; + bool success = false; + bool retry = false; + + if (!reply->error()) + { + if (!isHttpRedirect(reply)) + { + QByteArray data = reply->readAll(); + + // Google drive can redirect downloads to a virus scan warning page + // We need to use URL with confirm code and retry + if (url.startsWith("https://drive.google.com/uc?export=download") + && data.startsWith("") + && !filename.endsWith(".html") + ) + { + QRegularExpression regexp("action=\\\"(.*?)\\\""); + QRegularExpressionMatch match = regexp.match(data); + if (match.hasMatch()) + { + m_downloads.removeAll(reply); + m_filenames.remove(idx); + + QString action = match.captured(1); + action = action.replace("&", "&"); + qDebug() << "HttpDownloadManager: Skipping Google drive warning - downloading " << action; + QUrl newUrl(action); + QNetworkReply *newReply = download(newUrl, filename); + + // Indicate that we are retrying, so progress dialogs can be updated + emit retryDownload(filename, reply, newReply); + + retry = true; + } + else + { + qDebug() << "HttpDownloadManager: Can't find action URL in Google Drive page\nURL: " << url << "\nData:\n" << data; + } + } + else if (url.startsWith("https://drive.usercontent.google.com/download") + && data.startsWith("") + && !filename.endsWith(".html") + ) + { + QRegularExpression regexpAction("action=\\\"(.*?)\\\""); + QRegularExpressionMatch matchAction = regexpAction.match(data); + QRegularExpression regexpId("name=\"id\" value=\"([\\w-]+)\""); + QRegularExpressionMatch matchId = regexpId.match(data); + QRegularExpression regexpUuid("name=\"uuid\" value=\"([\\w-]+)\""); + QRegularExpressionMatch matchUuid = regexpUuid.match(data); + QRegularExpression regexpAt("name=\"at\" value=\"([\\w-]+\\:)\""); + QRegularExpressionMatch matchAt = regexpAt.match(data); + + if (matchAction.hasMatch() && matchId.hasMatch() && matchUuid.hasMatch()) + { + m_downloads.removeAll(reply); + m_filenames.remove(idx); + + QString newURLString = matchAction.captured(1) + + "?id=" + matchId.captured(1) + + "&export=download" + + "&authuser=0" + + "&confirm=t" + + "&uuid=" + matchUuid.captured(1) + ; + if (matchAt.hasMatch()) { + newURLString = newURLString + "at=" + matchAt.captured(1); + } + + qDebug() << "HttpDownloadManager: Skipping Google drive warning - downloading " << newURLString; + QUrl newUrl(newURLString); + QNetworkReply *newReply = download(newUrl, filename); + + // Indicate that we are retrying, so progress dialogs can be updated + emit retryDownload(filename, reply, newReply); + + retry = true; + } + else + { + qDebug() << "HttpDownloadManager: Can't find action URL in Google Drive page\nURL: " << url << "\nData:\n" << data; + } + } + else if (writeToFile(filename, data)) + { + success = true; + qDebug() << "HttpDownloadManager: Download from " << url << " to " << filename << " finished."; + } + } + else + { + qDebug() << "HttpDownloadManager: Request to download " << url << " was redirected."; + } + } + else + { + qCritical() << "HttpDownloadManager: Download of " << url << " failed: " << reply->errorString(); + } + + if (!retry) + { + m_downloads.removeAll(reply); + m_filenames.remove(idx); + emit downloadComplete(filename, success, url, reply->errorString()); + } + reply->deleteLater(); +} diff --git a/android/app/src/main/cpp/util/httpdownloadmanager.h b/android/app/src/main/cpp/util/httpdownloadmanager.h new file mode 100644 index 0000000..48b6c20 --- /dev/null +++ b/android/app/src/main/cpp/util/httpdownloadmanager.h @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020-2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_HTTPDOWNLOADMANAGER_H +#define INCLUDE_HTTPDOWNLOADMANAGER_H + +#include +#include + +#include "export.h" + +class QSslError; + +// Class to download files via http and write them to disk +class SDRBASE_API HttpDownloadManager : public QObject +{ + Q_OBJECT +public: + HttpDownloadManager(); + QNetworkReply *download(const QUrl &url, const QString &filename); + bool downloading() const; + + static QString downloadDir(); + +protected: + static qint64 fileAgeInDays(const QString& filename); + +private: + QNetworkAccessManager manager; + QVector m_downloads; + QVector m_filenames; + + static bool writeToFile(const QString &filename, const QByteArray &data); + static bool isHttpRedirect(QNetworkReply *reply); + +public slots: + void downloadFinished(QNetworkReply *reply); + void sslErrors(const QList &errors); + +signals: + void downloadComplete(const QString &filename, bool success, const QString &url, const QString &errorMessage); + void retryDownload(const QString &filename, QNetworkReply *oldReply, QNetworkReply *newReply); + +}; + +#endif /* INCLUDE_HTTPDOWNLOADMANAGER_H */ diff --git a/android/app/src/main/cpp/util/incrementalarray.h b/android/app/src/main/cpp/util/incrementalarray.h new file mode 100644 index 0000000..de04e01 --- /dev/null +++ b/android/app/src/main/cpp/util/incrementalarray.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_INCREMENTALARRAY_H_ +#define SDRBASE_UTIL_INCREMENTALARRAY_H_ + +#include + +template +class IncrementalArray +{ +public: + T *m_array; + + IncrementalArray(); + ~IncrementalArray(); + + void allocate(uint32_t size); + +private: + uint32_t m_size; +}; + +template +IncrementalArray::IncrementalArray() : + m_array(0), + m_size(0) +{ +} + +template +IncrementalArray::~IncrementalArray() +{ + if (m_array) { delete[] m_array; } +} + +template +void IncrementalArray::allocate(uint32_t size) +{ + if (size <= m_size) { return; } + if (m_array) { delete[] m_array; } + m_array = new T[size]; + m_size = size; +} + +#endif /* SDRBASE_UTIL_INCREMENTALARRAY_H_ */ diff --git a/android/app/src/main/cpp/util/incrementalvector.h b/android/app/src/main/cpp/util/incrementalvector.h new file mode 100644 index 0000000..674f985 --- /dev/null +++ b/android/app/src/main/cpp/util/incrementalvector.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_INCREMENTALVECTOR_H_ +#define SDRBASE_UTIL_INCREMENTALVECTOR_H_ + +#include +#include + +template +class IncrementalVector +{ +public: + std::vector m_vector; + + IncrementalVector(); + ~IncrementalVector(); + + void allocate(uint32_t size); + void allocate(uint32_t size, const T& init); + +private: + uint32_t m_size; +}; + +template +IncrementalVector::IncrementalVector() : + m_size(0) +{ +} + +template +IncrementalVector::~IncrementalVector() +{ +} + +template +void IncrementalVector::allocate(uint32_t size) +{ + if (size <= m_size) { return; } + m_vector.resize(size); + m_size = size; +} + +template +void IncrementalVector::allocate(uint32_t size, const T& init) +{ + if (size <= m_size) { return; } + m_vector.resize(size); + std::fill(m_vector.begin(), m_vector.end(), init); + m_size = size; +} + +#endif /* SDRBASE_UTIL_INCREMENTALVECTOR_H_ */ diff --git a/android/app/src/main/cpp/util/interpolation.cpp b/android/app/src/main/cpp/util/interpolation.cpp new file mode 100644 index 0000000..823987f --- /dev/null +++ b/android/app/src/main/cpp/util/interpolation.cpp @@ -0,0 +1,21 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020-2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "interpolation.h" diff --git a/android/app/src/main/cpp/util/interpolation.h b/android/app/src/main/cpp/util/interpolation.h new file mode 100644 index 0000000..eafa6c3 --- /dev/null +++ b/android/app/src/main/cpp/util/interpolation.h @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTERPOLATION_H +#define INCLUDE_INTERPOLATION_H + +#include "export.h" +#include + +class SDRBASE_API Interpolation { +public: + + // Linear interpolation/extrapolation + // Assumes x is sorted in increasing order + template + static T linear(Iter xBegin, Iter xEnd, Iter yBegin, T x) + { + // Find first point in x that target is bigger than + int i = 0; + while (xBegin != xEnd) + { + if (x < *xBegin) { + break; + } + ++xBegin; + i++; + } + T x0, x1, y0, y1; + if (i == 0) { + // Extrapolate left + x0 = *xBegin; + ++xBegin; + x1 = *xBegin; + y0 = *yBegin; + ++yBegin; + y1 = *yBegin; + + return extrapolate(x0, y0, x1, y1, x); + } else if (xBegin > xEnd) { + // Extrapolate right + Iter xCur = xBegin; + std::advance(xCur, i - 2); + x0 = *xCur; + ++xCur; + x1 = *xCur; + + Iter yCur = yBegin; + std::advance(yCur, i - 2); + y0 = *yCur; + ++yCur; + y1 = *yCur; + + return extrapolate(x0, y0, x1, y1, x); + } else { + // Interpolate + x1 = *xBegin; + --xBegin; + x0 = *xBegin; + + Iter yCur = yBegin; + std::advance(yCur, i - 1); + y0 = *yCur; + ++yCur; + y1 = *yCur; + + return interpolate(x0, y0, x1, y1, x); + } + } + + // Linear extrapolation + template + static T extrapolate(T x0, T y0, T x1, T y1, T x) + { + return y0 + ((x-x0)/(x1-x0)) * (y1-y0); + } + + // Linear interpolation + template + static T interpolate(T x0, T y0, T x1, T y1, T x) + { + return (y0*(x1-x) + y1*(x-x0)) / (x1-x0); + } + +}; + +#endif // INCLUDE_INTERPOLATION_H diff --git a/android/app/src/main/cpp/util/iot/device.cpp b/android/app/src/main/cpp/util/iot/device.cpp new file mode 100644 index 0000000..70269df --- /dev/null +++ b/android/app/src/main/cpp/util/iot/device.cpp @@ -0,0 +1,577 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "util/simpleserializer.h" +#include "util/iot/device.h" +#include "util/iot/tplink.h" +#include "util/iot/homeassistant.h" +#include "util/iot/visa.h" + +Device::Device(DeviceDiscoverer::DeviceInfo *info) +{ + if (info) { + m_info = *info; + } +} + +Device* Device::create(const QHash& settings, const QString& protocol, DeviceDiscoverer::DeviceInfo *info) +{ + if (checkSettings(settings, protocol)) + { + if (protocol == "TPLink") + { + if (settings.contains("deviceId")) + { + return new TPLinkDevice(settings.value("username").toString(), + settings.value("password").toString(), + settings.value("deviceId").toString(), + info); + } + else + { + qDebug() << "Device::create: A deviceId is required for: " << protocol; + } + } + else if (protocol == "HomeAssistant") + { + if (settings.contains("deviceId")) + { + return new HomeAssistantDevice(settings.value("apiKey").toString(), + settings.value("url").toString(), + settings.value("deviceId").toString(), + settings.value("controlIds").toStringList(), + settings.value("sensorIds").toStringList(), + info); + } + else + { + qDebug() << "Device::create: A deviceId is required for: " << protocol; + } + } + else if (protocol == "VISA") + { + if (settings.contains("deviceId")) + { + return new VISADevice(settings, + settings.value("deviceId").toString(), + settings.value("controlIds").toStringList(), + settings.value("sensorIds").toStringList(), + info); + } + else + { + qDebug() << "Device::create: A deviceId is required for: " << protocol; + } + } + } + return nullptr; +} + +bool Device::checkSettings(const QHash& settings, const QString& protocol) +{ + if (protocol == "TPLink") + { + if (settings.contains("username") && settings.contains("password")) + { + return true; + } + else + { + qDebug() << "Device::checkSettings: A username and password are required for: " << protocol; + return false; + } + } + else if (protocol == "HomeAssistant") + { + if (settings.contains("apiKey")) + { + if (settings.contains("url")) + { + return true; + } + else + { + qDebug() << "Device::checkSettings: A host url is required for: " << protocol; + return false; + } + } + else + { + qDebug() << "Device::checkSettings: An apiKey is required for: " << protocol; + return false; + } + } + else if (protocol == "VISA") + { + return true; + } + else + { + qDebug() << "Device::checkSettings: Unsupported protocol: " << protocol; + return false; + } +} + +void Device::setState(const QString &controlId, bool state) +{ + qDebug() << "Device::setState: " << getProtocol() << " doesn't support bool. Can't set " << controlId << " to " << state; +} + +void Device::setState(const QString &controlId, int state) +{ + qDebug() << "Device::setState: " << getProtocol() << " doesn't support int. Can't set " << controlId << " to " << state; +} + +void Device::setState(const QString &controlId, float state) +{ + qDebug() << "Device::setState: " << getProtocol() << " doesn't support float. Can't set " << controlId << " to " << state; +} + +void Device::setState(const QString &controlId, const QString &state) +{ + qDebug() << "Device::setState: " << getProtocol() << " doesn't support QString. Can't set " << controlId << " to " << state; +} + +void Device::recordGetRequest(void *ptr) +{ + m_getRequests.insert(ptr, QDateTime::currentDateTime()); +} + +void Device::removeGetRequest(void *ptr) +{ + m_getRequests.remove(ptr); +} + +void Device::recordSetRequest(const QString &id, int guardMS) +{ + m_setRequests.insert(id, QDateTime::currentDateTime().addMSecs(guardMS)); +} + +bool Device::getAfterSet(void *ptr, const QString &id) +{ + if (m_getRequests.contains(ptr) && m_setRequests.contains(id)) + { + QDateTime getTime = m_getRequests.value(ptr); + QDateTime setTime = m_setRequests.value(id); + return getTime > setTime; + } + else + { + return true; + } +} + +const QStringList DeviceDiscoverer::m_typeStrings = { + "Auto", + "Boolean", + "Integer", + "Float", + "String", + "List", + "Button" +}; + +const QStringList DeviceDiscoverer::m_widgetTypeStrings = { + "Spin box", + "Dial", + "Slider" +}; + +DeviceDiscoverer::DeviceDiscoverer() +{ +} + +DeviceDiscoverer *DeviceDiscoverer::getDiscoverer(const QHash& settings, const QString& protocol) +{ + if (Device::checkSettings(settings, protocol)) + { + if (protocol == "TPLink") + { + return new TPLinkDeviceDiscoverer(settings.value("username").toString(), settings.value("password").toString()); + } + else if (protocol == "HomeAssistant") + { + return new HomeAssistantDeviceDiscoverer(settings.value("apiKey").toString(), settings.value("url").toString()); + } + else if (protocol == "VISA") + { + return new VISADeviceDiscoverer(settings.value("resourceFilter").toString()); + } + } + return nullptr; +} + + +DeviceDiscoverer::DeviceInfo::DeviceInfo() +{ +} + +DeviceDiscoverer::DeviceInfo::DeviceInfo(const DeviceInfo &info) +{ + m_name = info.m_name; + m_id = info.m_id; + m_model = info.m_model; + // Take deep-copy of controls and sensors + for (auto const control : info.m_controls) { + ControlInfo *ci = control->clone(); + m_controls.append(ci); + } + for (auto const sensor : info.m_sensors) { + m_sensors.append(sensor->clone()); + } +} + +DeviceDiscoverer::DeviceInfo& DeviceDiscoverer::DeviceInfo::operator=(const DeviceInfo &info) +{ + m_name = info.m_name; + m_id = info.m_id; + m_model = info.m_model; + qDeleteAll(m_controls); + m_controls.clear(); + qDeleteAll(m_sensors); + m_sensors.clear(); + // Take deep-copy of controls and sensors + for (auto const control : info.m_controls) { + m_controls.append(control->clone()); + } + for (auto const sensor : info.m_sensors) { + m_sensors.append(sensor->clone()); + } + return *this; +} + +DeviceDiscoverer::DeviceInfo::~DeviceInfo() +{ + qDeleteAll(m_controls); + m_controls.clear(); + qDeleteAll(m_sensors); + m_sensors.clear(); +} + +DeviceDiscoverer::DeviceInfo::operator QString() const +{ + QString controls; + QString sensors; + + for (auto control : m_controls) { + controls.append((QString)*control); + } + for (auto sensor : m_sensors) { + sensors.append((QString)*sensor); + } + + return QString("DeviceInfo: m_name: %1 m_id: %2 m_model: %3 m_controls: %4 m_sensors: %5") + .arg(m_name) + .arg(m_id) + .arg(m_model) + .arg(controls) + .arg(sensors); +} + + +DeviceDiscoverer::ControlInfo::ControlInfo() : + m_type(AUTO), + m_min(-1000000), + m_max(1000000), + m_scale(1.0f), + m_precision(3), + m_widgetType(SPIN_BOX) +{ +} + +DeviceDiscoverer::ControlInfo::operator QString() const +{ + return QString("ControlInfo: m_name: %1 m_id: %2 m_type: %3") + .arg(m_name) + .arg(m_id) + .arg(DeviceDiscoverer::m_typeStrings[m_type]); +} + +DeviceDiscoverer::ControlInfo *DeviceDiscoverer::ControlInfo::clone() const +{ + return new ControlInfo(*this); +} + +QByteArray DeviceDiscoverer::ControlInfo::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_name); + s.writeString(2, m_id); + s.writeS32(3, (int)m_type); + s.writeFloat(4, m_min); + s.writeFloat(5, m_max); + s.writeFloat(6, m_scale); + s.writeS32(7, m_precision); + s.writeList(8, m_values); + s.writeS32(9, (int)m_widgetType); + s.writeString(10, m_units); + + return s.final(); +} + +bool DeviceDiscoverer::ControlInfo::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + d.readString(1, &m_name); + d.readString(2, &m_id); + d.readS32(3, (int*)&m_type); + d.readFloat(4, &m_min); + d.readFloat(5, &m_max); + d.readFloat(6, &m_scale, 1.0f); + d.readS32(7, &m_precision, 3); + d.readList(8, &m_values); + d.readS32(9, (int *)&m_widgetType); + d.readString(10, &m_units); + return true; + } + else + { + return false; + } +} + +DeviceDiscoverer::SensorInfo::operator QString() const +{ + return QString("SensorInfo: m_name: %1 m_id: %2 m_type: %3") + .arg(m_name) + .arg(m_id) + .arg(DeviceDiscoverer::m_typeStrings[m_type]); +} + +DeviceDiscoverer::SensorInfo *DeviceDiscoverer::SensorInfo::clone() const +{ + return new SensorInfo(*this); +} + +QByteArray DeviceDiscoverer::SensorInfo::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_name); + s.writeString(2, m_id); + s.writeS32(3, (int)m_type); + s.writeString(4, m_units); + + return s.final(); +} + +bool DeviceDiscoverer::SensorInfo::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + d.readString(1, &m_name); + d.readString(2, &m_id); + d.readS32(3, (int*)&m_type); + d.readString(4, &m_units); + return true; + } + else + { + return false; + } +} + +QByteArray DeviceDiscoverer::DeviceInfo::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_name); + s.writeString(2, m_id); + s.writeString(3, m_model); + s.writeList(10, m_controls); + s.writeList(11, m_sensors); + + return s.final(); +} + +bool DeviceDiscoverer::DeviceInfo::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + QByteArray blob; + + d.readString(1, &m_name); + d.readString(2, &m_id); + d.readString(3, &m_model); + d.readList(10, &m_controls); + d.readList(11, &m_sensors); + return true; + } + else + { + return false; + } +} + +DeviceDiscoverer::ControlInfo *DeviceDiscoverer::DeviceInfo::getControl(const QString &id) const +{ + for (auto c : m_controls) + { + if (c->m_id == id) { + return c; + } + } + return nullptr; +} + +DeviceDiscoverer::SensorInfo *DeviceDiscoverer::DeviceInfo::getSensor(const QString &id) const +{ + for (auto s : m_sensors) + { + if (s->m_id == id) { + return s; + } + } + return nullptr; +} + +void DeviceDiscoverer::DeviceInfo::deleteControl(const QString &id) +{ + for (int i = 0; i < m_controls.size(); i++) + { + if (m_controls[i]->m_id == id) + { + delete m_controls.takeAt(i); + return; + } + } +} + +void DeviceDiscoverer::DeviceInfo::deleteSensor(const QString &id) +{ + for (int i = 0; i < m_sensors.size(); i++) + { + if (m_sensors[i]->m_id == id) + { + delete m_sensors.takeAt(i); + return; + } + } +} + +QDataStream& operator<<(QDataStream& out, const DeviceDiscoverer::ControlInfo* control) +{ + int typeId; + if (dynamic_cast(control)) { + typeId = 1; + } else { + typeId = 0; + } + out << typeId; + out << control->serialize(); + return out; +} + +QDataStream& operator>>(QDataStream& in, DeviceDiscoverer::ControlInfo*& control) +{ + QByteArray data; + int typeId; + in >> typeId; + if (typeId == 1) { + control = new VISADevice::VISAControl(); + } else { + control = new DeviceDiscoverer::ControlInfo(); + } + in >> data; + control->deserialize(data); + return in; +} + +QDataStream& operator<<(QDataStream& out, const DeviceDiscoverer::SensorInfo* sensor) +{ + int typeId; + if (dynamic_cast(sensor)) { + typeId = 1; + } else { + typeId = 0; + } + out << typeId; + out << sensor->serialize(); + return out; +} + +QDataStream& operator>>(QDataStream& in, DeviceDiscoverer::SensorInfo*& sensor) +{ + + QByteArray data; + int typeId; + in >> typeId; + if (typeId == 1) { + sensor = new VISADevice::VISASensor(); + } else { + sensor = new DeviceDiscoverer::SensorInfo(); + } + in >> data; + sensor->deserialize(data); + return in; +} + +QDataStream& operator<<(QDataStream& out, const VISADevice::VISASensor &sensor) +{ + out << sensor.serialize(); + return out; +} + +QDataStream& operator>>(QDataStream& in, VISADevice::VISASensor& sensor) +{ + QByteArray data; + in >> data; + sensor.deserialize(data); + return in; +} + +QDataStream& operator<<(QDataStream& out, const VISADevice::VISAControl &control) +{ + out << control.serialize(); + return out; +} + +QDataStream& operator>>(QDataStream& in, VISADevice::VISAControl& control) +{ + QByteArray data; + in >> data; + control.deserialize(data); + return in; +} diff --git a/android/app/src/main/cpp/util/iot/device.h b/android/app/src/main/cpp/util/iot/device.h new file mode 100644 index 0000000..6a22ddd --- /dev/null +++ b/android/app/src/main/cpp/util/iot/device.h @@ -0,0 +1,152 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DEVICE_H +#define INCLUDE_DEVICE_H + +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +class SDRBASE_API DeviceDiscoverer : public QObject +{ + Q_OBJECT +public: + enum Type { + AUTO, + BOOL, + INT, + FLOAT, + STRING, + LIST, + BUTTON + }; + enum WidgetType { + SPIN_BOX, + DIAL, + SLIDER + }; + + struct SDRBASE_API ControlInfo { + QString m_name; + QString m_id; + Type m_type; // Data type + float m_min; // Min/max when m_type=INT/FLOAT + float m_max; + float m_scale; + int m_precision; + QStringList m_values; // Allowed values when m_type==LIST or label for button when m_type==BUTTON + WidgetType m_widgetType;// For m_type==FLOAT + QString m_units; + + ControlInfo(); + virtual ~ControlInfo() {} + operator QString() const; + virtual ControlInfo *clone() const; + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + }; + + struct SDRBASE_API SensorInfo { + QString m_name; + QString m_id; + Type m_type; + QString m_units; // W/Watts etc + + virtual ~SensorInfo() {} + operator QString() const; + virtual SensorInfo *clone() const; + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + }; + + struct SDRBASE_API DeviceInfo { + QString m_name; // User friendly name + QString m_id; // ID for the device used by the API + QString m_model; // Model name + QList m_controls; + QList m_sensors; + + DeviceInfo(); + DeviceInfo(const DeviceInfo &info); + ~DeviceInfo(); + DeviceInfo& operator=(const DeviceInfo &info); + operator QString() const; + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + ControlInfo *getControl(const QString &id) const; + SensorInfo *getSensor(const QString &id) const; + void deleteControl(const QString &id); + void deleteSensor(const QString &id); + }; + +protected: + DeviceDiscoverer(); + +public: + static DeviceDiscoverer *getDiscoverer(const QHash& settings, const QString& protocol="TPLink"); + static const QStringList m_typeStrings; + static const QStringList m_widgetTypeStrings; + + virtual void getDevices() = 0; + +signals: + void deviceList(const QList &devices); + void error(const QString &msg); +}; + +class SDRBASE_API Device : public QObject +{ + Q_OBJECT +protected: + Device(DeviceDiscoverer::DeviceInfo *info=nullptr); + +public: + + static Device* create(const QHash& settings, const QString& protocol="TPLink", DeviceDiscoverer::DeviceInfo *info=nullptr); + static bool checkSettings(const QHash& settings, const QString& protocol); + + virtual void getState() = 0; + virtual void setState(const QString &controlId, bool state); + virtual void setState(const QString &controlId, int state); + virtual void setState(const QString &controlId, float state); + virtual void setState(const QString &controlId, const QString &state); + virtual QString getProtocol() const = 0; + virtual QString getDeviceId() const = 0; + +signals: + void deviceUpdated(QHash); // Called when new state available. Hash keys are control and sensor IDs + void deviceUnavailable(); // Called when device is unavailable. error() isn't signalled, as we expect devices to come and go + void error(const QString &msg); // Called on terminal error, such as invalid authentication details + +protected: + DeviceDiscoverer::DeviceInfo m_info; + + QHash m_getRequests; // These data and functions help prevent using stale data from slow getStates + QHash m_setRequests; + + void recordGetRequest(void *ptr); + void removeGetRequest(void *ptr); + void recordSetRequest(const QString &id, int guardMS=0); + bool getAfterSet(void *ptr, const QString &id); + +}; + +#endif /* INCLUDE_DEVICE_H */ diff --git a/android/app/src/main/cpp/util/iot/homeassistant.cpp b/android/app/src/main/cpp/util/iot/homeassistant.cpp new file mode 100644 index 0000000..0f980f1 --- /dev/null +++ b/android/app/src/main/cpp/util/iot/homeassistant.cpp @@ -0,0 +1,331 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "util/iot/homeassistant.h" + +HomeAssistantDevice::HomeAssistantDevice(const QString& apiKey, const QString& url, const QString &deviceId, + const QStringList &controls, const QStringList &sensors, + DeviceDiscoverer::DeviceInfo *info) : + Device(info), + m_deviceId(deviceId), + m_apiKey(apiKey), + m_url(url) +{ + m_entities = controls; + m_entities.append(sensors); + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &HomeAssistantDevice::handleReply + ); +} + +HomeAssistantDevice::~HomeAssistantDevice() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &HomeAssistantDevice::handleReply + ); + delete m_networkManager; +} + +void HomeAssistantDevice::getState() +{ + // Get state for all entities of the device + for (auto entity : m_entities) + { + QUrl url(m_url + "/api/states/" + entity); + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer " + m_apiKey.toLocal8Bit()); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkReply *reply = m_networkManager->get(request); + recordGetRequest(reply); + } +} + +void HomeAssistantDevice::setState(const QString &controlId, bool state) +{ + QString domain = controlId.left(controlId.indexOf(".")); + QUrl url(m_url + "/api/services/" + domain + "/turn_" + (state ? "on" : "off")); + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer " + m_apiKey.toLocal8Bit()); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QJsonObject object { + {"entity_id", controlId} + }; + QJsonDocument document; + document.setObject(object); + + m_networkManager->post(request, document.toJson()); + + // 750ms guard, to try to avoid toggling of widget, while state updates on server + // Perhaps should be a setting + recordSetRequest(controlId, 750); +} + +void HomeAssistantDevice::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(data, &error); + if (!document.isNull()) + { + //qDebug() << "Received " << document; + // POSTs to /api/services return an array, GETs from /api/states return an object + if (document.isObject()) + { + QJsonObject obj = document.object(); + + if (obj.contains(QStringLiteral("entity_id")) && obj.contains(QStringLiteral("state"))) + { + QString entityId = obj.value(QStringLiteral("entity_id")).toString(); + if (getAfterSet(reply, entityId)) + { + QHash status; + QString state = obj.value(QStringLiteral("state")).toString(); + bool dOk; + bool iOk; + int i = state.toInt(&iOk); + double d = state.toDouble(&dOk); + if ((state == "on") || (state == "playing")) { + status.insert(entityId, 1); + } else if ((state == "off") || (state == "paused")) { + status.insert(entityId, 0); + } else if (iOk) { + status.insert(entityId, i); + } else if (dOk) { + status.insert(entityId, d); + } else { + status.insert(entityId, state); + } + emit deviceUpdated(status); + } + } + } + } + else + { + qDebug() << "HomeAssistantDevice::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset; + } + } + else + { + qDebug() << "HomeAssistantDevice::handleReply: error: " << reply->error(); + } + removeGetRequest(reply); + reply->deleteLater(); + } + else + { + qDebug() << "HomeAssistantDevice::handleReply: reply is null"; + } +} + +HomeAssistantDeviceDiscoverer::HomeAssistantDeviceDiscoverer(const QString& apiKey, const QString& url) : + m_apiKey(apiKey), + m_url(url) +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &HomeAssistantDeviceDiscoverer::handleReply + ); +} + +HomeAssistantDeviceDiscoverer::~HomeAssistantDeviceDiscoverer() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &HomeAssistantDeviceDiscoverer::handleReply + ); + delete m_networkManager; +} + +void HomeAssistantDeviceDiscoverer::getDevices() +{ + QUrl url(m_url+ "/api/template"); + + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer " + m_apiKey.toLocal8Bit()); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + // Use templates to get a list of devices and associated entities + QString tpl = + "{% set devices = states | map(attribute='entity_id') | map('device_id') | unique | reject('eq',None)| list %}\n" + "{%- set ns = namespace(devices = []) %}\n" + "{%- for device in devices %}\n" + " {%- set entities = device_entities(device) | list %}\n" + " {%- if entities %}\n" + " {%- set ens = namespace(entityobjs = []) %}\n" + " {%- for entity in entities %}\n" + " {%- set entityobj = {'entity_id': entity, 'name': state_attr(entity,'friendly_name'), 'unit_of_measurement': state_attr(entity,'unit_of_measurement')} %}\n" + " {%- set ens.entityobjs = ens.entityobjs + [ entityobj ] %}\n" + " {%- endfor %}\n" + " {%- set obj = {'device_id': device, 'name': device_attr(device,'name'), 'name_by_user': device_attr(device,'name_by_user'), 'model': device_attr(device,'model'), 'entities': ens.entityobjs } %}\n" + " {%- set ns.devices = ns.devices + [ obj ] %}\n" + " {%- endif %}\n" + "{%- endfor %}\n" + "{{ ns.devices | tojson }}"; + + QJsonObject object { + {"template", tpl} + }; + QJsonDocument document; + document.setObject(object); + + m_networkManager->post(request, document.toJson()); +} + +void HomeAssistantDeviceDiscoverer::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QList devices; + QByteArray data = reply->readAll(); + //qDebug() << "Received " << data; + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(data, &error); + if (!document.isNull()) + { + if (document.isArray()) + { + for (auto deviceRef : document.array()) + { + QJsonObject deviceObj = deviceRef.toObject(); + if (deviceObj.contains(QStringLiteral("device_id")) && deviceObj.contains(QStringLiteral("entities"))) + { + QJsonArray entitiesArray = deviceObj.value(QStringLiteral("entities")).toArray(); + if (entitiesArray.size() > 0) + { + DeviceInfo info; + info.m_id = deviceObj.value(QStringLiteral("device_id")).toString(); + + if (deviceObj.contains(QStringLiteral("name_by_user"))) { + info.m_name = deviceObj.value(QStringLiteral("name_by_user")).toString(); + } + if (info.m_name.isEmpty() && deviceObj.contains(QStringLiteral("name"))) { + info.m_name = deviceObj.value(QStringLiteral("name")).toString(); + } + if (deviceObj.contains(QStringLiteral("model"))) { + info.m_model = deviceObj.value(QStringLiteral("model")).toString(); + } + + for (auto entityRef : entitiesArray) + { + QJsonObject entityObj = entityRef.toObject(); + QString entity = entityObj.value(QStringLiteral("entity_id")).toString(); + QString name = entityObj.value(QStringLiteral("name")).toString(); + QString domain = entity.left(entity.indexOf('.')); + if (domain == "binary_sensor") + { + SensorInfo *sensorInfo = new SensorInfo(); + sensorInfo->m_name = name; + sensorInfo->m_id = entity; + sensorInfo->m_type = DeviceDiscoverer::BOOL; + sensorInfo->m_units = entityObj.value(QStringLiteral("unit_of_measurement")).toString(); + info.m_sensors.append(sensorInfo); + } + else if (domain == "sensor") + { + SensorInfo *sensorInfo = new SensorInfo(); + sensorInfo->m_name = name; + sensorInfo->m_id = entity; + sensorInfo->m_type = DeviceDiscoverer::FLOAT; // FIXME: Auto? + sensorInfo->m_units = entityObj.value(QStringLiteral("unit_of_measurement")).toString(); + info.m_sensors.append(sensorInfo); + } + else if ((domain == "switch") || (domain == "light") || (domain == "media_player")) // Entities that support turn_on/turn_off + { + ControlInfo *controlInfo = new ControlInfo(); + controlInfo->m_name = name; + controlInfo->m_id = entity; + controlInfo->m_type = DeviceDiscoverer::BOOL; + info.m_controls.append(controlInfo); + } + else + { + qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Unsupported domain: " << domain; + } + } + + if ((info.m_controls.size() > 0) || (info.m_sensors.size() > 0)) + { + devices.append(info); + } + } + else + { + //qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: No entities " << deviceObj.value(QStringLiteral("device_id")).toString(); + } + } + else + { + //qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: device_id or entities missing"; + } + } + } + else + { + qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Document is not an array: " << document; + } + } + else + { + qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset; + } + emit deviceList(devices); + } + else + { + qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: error: " << reply->error() << ":" << reply->errorString(); + // Get QNetworkReply::AuthenticationRequiredError if token is invalid + if (reply->error() == QNetworkReply::AuthenticationRequiredError) { + emit error("Home Assistant: Authentication failed. Check access token is valid."); + } else { + emit error(QString("Home Assistant: Network error. %1").arg(reply->errorString())); + } + } + reply->deleteLater(); + } + else + { + qDebug() << "HomeAssistantDeviceDiscoverer::handleReply: reply is null"; + } +} diff --git a/android/app/src/main/cpp/util/iot/homeassistant.h b/android/app/src/main/cpp/util/iot/homeassistant.h new file mode 100644 index 0000000..1380229 --- /dev/null +++ b/android/app/src/main/cpp/util/iot/homeassistant.h @@ -0,0 +1,71 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_IOT_HOMEASSISTANT_H +#define INCLUDE_IOT_HOMEASSISTANT_H + +#include "util/iot/device.h" + +// Supports Home Assistant devices - https://www.home-assistant.io/ +class SDRBASE_API HomeAssistantDevice : public Device { + Q_OBJECT +public: + + HomeAssistantDevice(const QString& apiKey, const QString& url, const QString &deviceId, + const QStringList &controls, const QStringList &sensors, + DeviceDiscoverer::DeviceInfo *info=nullptr); + ~HomeAssistantDevice(); + virtual void getState() override; + using Device::setState; + virtual void setState(const QString &controlId, bool state) override; + virtual QString getProtocol() const override { return "HomeAssistant"; } + virtual QString getDeviceId() const override { return m_deviceId; } + +private: + + QString m_deviceId; + QStringList m_entities; // List of entities that are part of the device, to get state for (controls and sensors) + QString m_apiKey; // Bearer token + QString m_url; // Typically http://homeassistant.local:8123 + QNetworkAccessManager *m_networkManager; + +public slots: + void handleReply(QNetworkReply* reply); + +}; + +class SDRBASE_API HomeAssistantDeviceDiscoverer : public DeviceDiscoverer { + Q_OBJECT +public: + + HomeAssistantDeviceDiscoverer(const QString& apiKey, const QString& url); + ~HomeAssistantDeviceDiscoverer(); + virtual void getDevices() override; + +private: + + QString m_deviceId; + QString m_apiKey; // Bearer token + QString m_url; // Typically http://homeassistant.local:8123 + QNetworkAccessManager *m_networkManager; + +public slots: + void handleReply(QNetworkReply* reply); + +}; + +#endif /* INCLUDE_IOT_HOMEASSISTANT_H */ diff --git a/android/app/src/main/cpp/util/iot/tplink.cpp b/android/app/src/main/cpp/util/iot/tplink.cpp new file mode 100644 index 0000000..31ce4a3 --- /dev/null +++ b/android/app/src/main/cpp/util/iot/tplink.cpp @@ -0,0 +1,659 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include +#include +#include + +#include "util/iot/tplink.h" +#include "util/simpleserializer.h" + +const QString TPLinkCommon::m_url = "https://wap.tplinkcloud.com"; + +TPLinkCommon::TPLinkCommon(const QString& username, const QString &password) : + m_loggedIn(false), + m_outstandingRequest(false), + m_username(username), + m_password(password), + m_networkManager(nullptr) +{ +} + +void TPLinkCommon::login() +{ + QUrl url(m_url); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QJsonObject params { + {"appType", "Kasa_Android"}, + {"cloudUserName", m_username}, + {"cloudPassword", m_password}, + {"terminalUUID", "9cc4653e-338f-48e4-b8ca-6ed3f67631e4"} + }; + QJsonObject object { + {"method", "login"}, + {"params", params} + }; + QJsonDocument document; + document.setObject(object); + + m_networkManager->post(request, document.toJson()); +} + +void TPLinkCommon::handleLoginReply(QNetworkReply* reply, QString &errorMessage) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + if (document.isObject()) + { + //qDebug() << "Received " << document; + if (!m_loggedIn) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("error_code"))) + { + int errorCode = obj.value(QStringLiteral("error_code")).toInt(); + if (!errorCode) + { + if (obj.contains(QStringLiteral("result"))) + { + QJsonObject result = obj.value(QStringLiteral("result")).toObject(); + if (result.contains(QStringLiteral("token"))) + { + m_loggedIn = true; + m_token = result.value(QStringLiteral("token")).toString(); + } + else + { + qDebug() << "TPLinkDevice::handleReply: Object doesn't contain a token: " << result; + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: Object doesn't contain a result object: " << obj; + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: Non-zero error_code while logging in: " << errorCode; + if (obj.contains(QStringLiteral("msg"))) + { + QString msg = obj.value(QStringLiteral("msg")).toString(); + qDebug() << "TPLinkDevice::handleReply: Error message: " << msg; + // Typical msg is "Incorrect email or password" + errorMessage = QString("TP-Link: Failed to log in. %1").arg(msg); + } + else + { + errorMessage = QString("TP-Link: Failed to log in. Error code: %1").arg(errorCode); + } + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: Object doesn't contain an error_code: " << obj; + } + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: Document is not an object: " << document; + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "TPLinkDevice::handleReply: reply is null"; + } + + if (!m_loggedIn && errorMessage.isEmpty()) { + errorMessage = "TP-Link: Failed to log in."; + } +} + +TPLinkDevice::TPLinkDevice(const QString& username, const QString &password, const QString &deviceId, DeviceDiscoverer::DeviceInfo *info) : + Device(info), + TPLinkCommon(username, password), + m_deviceId(deviceId) +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &TPLinkDevice::handleReply + ); + login(); +} + +TPLinkDevice::~TPLinkDevice() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &TPLinkDevice::handleReply + ); + delete m_networkManager; +} + +void TPLinkDevice::getState() +{ + if (!m_loggedIn) + { + m_outstandingRequest = true; + return; + } + QUrl url(m_url); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QJsonObject system; + system.insert("get_sysinfo", QJsonValue()); + QJsonObject emeter; + emeter.insert("get_realtime", QJsonValue()); + QJsonObject requestData { + {"system", system}, + {"emeter", emeter} + }; + QJsonObject params { + {"deviceId", m_deviceId}, + {"requestData", requestData}, + {"token", m_token} + }; + QJsonObject object { + {"method", "passthrough"}, + {"params", params} + }; + QJsonDocument document; + document.setObject(object); + + QNetworkReply *reply = m_networkManager->post(request, document.toJson()); + + recordGetRequest(reply); +} + +void TPLinkDevice::setState(const QString &controlId, bool state) +{ + if (!m_loggedIn) + { + // Should we queue these and apply after logged in? + qDebug() << "TPLinkDevice::setState: Unable to set state for " << controlId << " to " << state << " as not yet logged in"; + return; + } + QUrl url(m_url); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QJsonObject stateObj { + {"state", (int)state} + }; + QJsonObject system { + {"set_relay_state", stateObj} + }; + QJsonObject requestData { + {"system", system} + }; + if (controlId != "switch") { + QJsonArray childIds { + controlId + }; + QJsonObject context { + {"child_ids", childIds} + }; + requestData.insert("context", QJsonValue(context)); + } + QJsonObject params { + {"deviceId", m_deviceId}, + {"requestData", requestData}, + {"token", m_token} + }; + QJsonObject object { + {"method", "passthrough"}, + {"params", params} + }; + QJsonDocument document; + document.setObject(object); + + m_networkManager->post(request, document.toJson()); + + recordSetRequest(controlId); +} + +void TPLinkDevice::handleReply(QNetworkReply* reply) +{ + if (!m_loggedIn) + { + QString errorMessage; + TPLinkCommon::handleLoginReply(reply, errorMessage); + if (!errorMessage.isEmpty()) + { + emit error(errorMessage); + } + else if (m_outstandingRequest) + { + m_outstandingRequest = true; + getState(); + } + } + else if (reply) + { + if (!reply->error()) + { + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(data, &error); + if (!document.isNull()) + { + if (document.isObject()) + { + //qDebug() << "Received " << document; + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("result"))) + { + QJsonObject resultObj = obj.value(QStringLiteral("result")).toObject(); + QHash status; + + if (resultObj.contains(QStringLiteral("responseData"))) + { + QJsonObject responseDataObj = resultObj.value(QStringLiteral("responseData")).toObject(); + if (responseDataObj.contains(QStringLiteral("system"))) + { + QJsonObject systemObj = responseDataObj.value(QStringLiteral("system")).toObject(); + if (systemObj.contains(QStringLiteral("get_sysinfo"))) + { + QJsonObject sysInfoObj = systemObj.value(QStringLiteral("get_sysinfo")).toObject(); + if (sysInfoObj.contains(QStringLiteral("child_num"))) + { + QJsonArray children = sysInfoObj.value(QStringLiteral("children")).toArray(); + for (auto childRef : children) + { + QJsonObject childObj = childRef.toObject(); + if (childObj.contains(QStringLiteral("state")) && childObj.contains(QStringLiteral("id"))) + { + QString id = childObj.value(QStringLiteral("id")).toString(); + if (getAfterSet(reply, id)) + { + int state = childObj.value(QStringLiteral("state")).toInt(); + status.insert(id, state); // key should match id in discoverer + } + } + } + } + else if (sysInfoObj.contains(QStringLiteral("relay_state"))) + { + if (getAfterSet(reply, "switch")) + { + int state = sysInfoObj.value(QStringLiteral("relay_state")).toInt(); + status.insert("switch", state); // key should match id in discoverer + } + } + } + } + // KP115 has emeter, but KP105 doesn't + if (responseDataObj.contains(QStringLiteral("emeter"))) + { + QJsonObject emeterObj = responseDataObj.value(QStringLiteral("emeter")).toObject(); + if (emeterObj.contains(QStringLiteral("get_realtime"))) + { + QJsonObject realtimeObj = emeterObj.value(QStringLiteral("get_realtime")).toObject(); + if (realtimeObj.contains(QStringLiteral("current_ma"))) + { + double current = realtimeObj.value(QStringLiteral("current_ma")).toDouble(); + status.insert("current", current / 1000.0); + } + if (realtimeObj.contains(QStringLiteral("voltage_mv"))) + { + double voltage = realtimeObj.value(QStringLiteral("voltage_mv")).toDouble(); + status.insert("voltage", voltage / 1000.0); + } + if (realtimeObj.contains(QStringLiteral("power_mw"))) + { + double power = realtimeObj.value(QStringLiteral("power_mw")).toDouble(); + status.insert("power", power / 1000.0); + } + } + } + } + + emit deviceUpdated(status); + } + else if (obj.contains(QStringLiteral("error_code"))) + { + // If a device isn't available, we can get: + // {"error_code":-20002,"msg":"Request timeout"} + // {"error_code":-20571,"msg":"Device is offline"} + int errorCode = obj.value(QStringLiteral("error_code")).toInt(); + QString msg = obj.value(QStringLiteral("msg")).toString(); + qDebug() << "TPLinkDevice::handleReply: Error code: " << errorCode << " " << msg; + + emit deviceUnavailable(); + } + else + { + qDebug() << "TPLinkDevice::handleReply: Object doesn't contain a result or error_code: " << obj; + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: Document is not an object: " << document; + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset; + } + } + else + { + qDebug() << "TPLinkDevice::handleReply: error: " << reply->error(); + } + removeGetRequest(reply); + reply->deleteLater(); + } + else + { + qDebug() << "TPLinkDevice::handleReply: reply is null"; + } +} + +TPLinkDeviceDiscoverer::TPLinkDeviceDiscoverer(const QString& username, const QString &password) : + TPLinkCommon(username, password) +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &TPLinkDeviceDiscoverer::handleReply + ); + login(); +} + +TPLinkDeviceDiscoverer::~TPLinkDeviceDiscoverer() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &TPLinkDeviceDiscoverer::handleReply + ); + delete m_networkManager; +} + +void TPLinkDeviceDiscoverer::getDevices() +{ + if (!m_loggedIn) + { + m_outstandingRequest = true; + return; + } + QUrl url(m_url); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QJsonObject params { + {"token", m_token} + }; + QJsonObject object { + {"method", "getDeviceList"}, + {"params", params} + }; + QJsonDocument document; + document.setObject(object); + + m_networkManager->post(request, document.toJson()); +} + +void TPLinkDeviceDiscoverer::getState(const QString &deviceId) +{ + QUrl url(m_url); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QJsonObject system; + system.insert("get_sysinfo", QJsonValue()); + QJsonObject emeter; + emeter.insert("get_realtime", QJsonValue()); + QJsonObject requestData { + {"system", system}, + {"emeter", emeter} + }; + QJsonObject params { + {"deviceId", deviceId}, + {"requestData", requestData}, + {"token", m_token} + }; + QJsonObject object { + {"method", "passthrough"}, + {"params", params} + }; + QJsonDocument document; + document.setObject(object); + + m_getStateReplies.insert(m_networkManager->post(request, document.toJson()), deviceId); +} + +void TPLinkDeviceDiscoverer::handleReply(QNetworkReply* reply) +{ + if (!m_loggedIn) + { + QString errorMessage; + TPLinkCommon::handleLoginReply(reply, errorMessage); + if (!errorMessage.isEmpty()) + { + emit error(errorMessage); + } + else if (m_outstandingRequest) + { + m_outstandingRequest = false; + getDevices(); + } + } + else if (reply) + { + if (!reply->error()) + { + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(data, &error); + if (!document.isNull()) + { + if (document.isObject()) + { + //qDebug() << "Received " << document; + QJsonObject obj = document.object(); + + if (m_getStateReplies.contains(reply)) + { + // Reply for getState + m_getStateReplies.remove(reply); + QJsonObject resultObj = obj.value(QStringLiteral("result")).toObject(); + if (resultObj.contains(QStringLiteral("responseData"))) + { + QJsonObject responseDataObj = resultObj.value(QStringLiteral("responseData")).toObject(); + if (responseDataObj.contains(QStringLiteral("system"))) + { + DeviceInfo info; + QJsonObject systemObj = responseDataObj.value(QStringLiteral("system")).toObject(); + if (systemObj.contains(QStringLiteral("get_sysinfo"))) + { + QJsonObject sysInfoObj = systemObj.value(QStringLiteral("get_sysinfo")).toObject(); + if (sysInfoObj.contains(QStringLiteral("alias"))) { + info.m_name = sysInfoObj.value(QStringLiteral("alias")).toString(); + } + if (sysInfoObj.contains(QStringLiteral("model"))) { + info.m_model = sysInfoObj.value(QStringLiteral("model")).toString(); + } + if (sysInfoObj.contains(QStringLiteral("deviceId"))) { + info.m_id = sysInfoObj.value(QStringLiteral("deviceId")).toString(); + } + if (sysInfoObj.contains(QStringLiteral("child_num"))) + { + QJsonArray children = sysInfoObj.value(QStringLiteral("children")).toArray(); + for (auto childRef : children) + { + QJsonObject childObj = childRef.toObject(); + ControlInfo *controlInfo = new ControlInfo(); + controlInfo->m_id = childObj.value(QStringLiteral("id")).toString(); + if (childObj.contains(QStringLiteral("alias"))) { + controlInfo->m_name = childObj.value(QStringLiteral("alias")).toString(); + } + controlInfo->m_type = DeviceDiscoverer::BOOL; + info.m_controls.append(controlInfo); + } + } + else if (sysInfoObj.contains(QStringLiteral("relay_state"))) + { + ControlInfo *controlInfo = new ControlInfo(); + controlInfo->m_id = "switch"; + if (sysInfoObj.contains(QStringLiteral("alias"))) { + controlInfo->m_name = sysInfoObj.value(QStringLiteral("alias")).toString(); + } + controlInfo->m_type = DeviceDiscoverer::BOOL; + info.m_controls.append(controlInfo); + } + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: get_sysinfo missing"; + } + // KP115 has energy meter, but KP105 doesn't. KP105 will have emeter object, but without get_realtime sub-object + if (responseDataObj.contains(QStringLiteral("emeter"))) + { + QJsonObject emeterObj = responseDataObj.value(QStringLiteral("emeter")).toObject(); + if (emeterObj.contains(QStringLiteral("get_realtime"))) + { + QJsonObject realtimeObj = emeterObj.value(QStringLiteral("get_realtime")).toObject(); + if (realtimeObj.contains(QStringLiteral("current_ma"))) + { + SensorInfo *currentSensorInfo = new SensorInfo(); + currentSensorInfo->m_name = "Current"; + currentSensorInfo->m_id = "current"; + currentSensorInfo->m_type = DeviceDiscoverer::FLOAT; + currentSensorInfo->m_units = "A"; + info.m_sensors.append(currentSensorInfo); + } + if (realtimeObj.contains(QStringLiteral("voltage_mv"))) + { + SensorInfo *voltageSensorInfo = new SensorInfo(); + voltageSensorInfo->m_name = "Voltage"; + voltageSensorInfo->m_id = "voltage"; + voltageSensorInfo->m_type = DeviceDiscoverer::FLOAT; + voltageSensorInfo->m_units = "V"; + info.m_sensors.append(voltageSensorInfo); + } + if (realtimeObj.contains(QStringLiteral("power_mw"))) + { + SensorInfo *powerSensorInfo = new SensorInfo(); + powerSensorInfo->m_name = "Power"; + powerSensorInfo->m_id = "power"; + powerSensorInfo->m_type = DeviceDiscoverer::FLOAT; + powerSensorInfo->m_units = "W"; + info.m_sensors.append(powerSensorInfo); + } + } + } + if (info.m_controls.size() > 0) { + m_devices.append(info); + } else { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: No controls in info"; + } + + } + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: No responseData"; + } + + if (m_getStateReplies.size() == 0) + { + emit deviceList(m_devices); + m_devices.clear(); + } + + } + else + { + // Reply for getDevice + if (obj.contains(QStringLiteral("result"))) + { + QJsonObject resultObj = obj.value(QStringLiteral("result")).toObject(); + if (resultObj.contains(QStringLiteral("deviceList"))) + { + QJsonArray deviceArray = resultObj.value(QStringLiteral("deviceList")).toArray(); + for (auto deviceRef : deviceArray) + { + QJsonObject deviceObj = deviceRef.toObject(); + if (deviceObj.contains(QStringLiteral("deviceId")) && deviceObj.contains(QStringLiteral("deviceType"))) + { + // In order to discover what controls and sensors a device has, we need to get sysinfo + getState(deviceObj.value(QStringLiteral("deviceId")).toString()); + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: deviceList element doesn't contain a deviceId: " << deviceObj; + } + } + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: result doesn't contain a deviceList: " << resultObj; + } + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: Object doesn't contain a result: " << obj; + } + } + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: Document is not an object: " << document; + } + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: Error parsing JSON: " << error.errorString() << " at offset " << error.offset; + } + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "TPLinkDeviceDiscoverer::handleReply: reply is null"; + } +} diff --git a/android/app/src/main/cpp/util/iot/tplink.h b/android/app/src/main/cpp/util/iot/tplink.h new file mode 100644 index 0000000..ebea2bd --- /dev/null +++ b/android/app/src/main/cpp/util/iot/tplink.h @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_IOT_TPLINK_H +#define INCLUDE_IOT_TPLINK_H + +#include "util/iot/device.h" + +class SDRBASE_API TPLinkCommon { +protected: + TPLinkCommon(const QString& username, const QString &password); + void login(); + void handleLoginReply(QNetworkReply* reply, QString &errorMessage); + + bool m_loggedIn; + bool m_outstandingRequest; // Issue getState / getDevices after logged in + QString m_username; + QString m_password; + QString m_token; + QNetworkAccessManager *m_networkManager; + + static const QString m_url; +}; + +// Supports TPLink's Kasa plugs - https://www.tp-link.com/uk/smarthome/ +class SDRBASE_API TPLinkDevice : public Device, TPLinkCommon { + Q_OBJECT +public: + + TPLinkDevice(const QString& username, const QString &password, const QString &deviceId, DeviceDiscoverer::DeviceInfo *info=nullptr); + ~TPLinkDevice(); + virtual void getState() override; + using Device::setState; + virtual void setState(const QString &controlId, bool state) override; + virtual QString getProtocol() const override { return "TPLink"; } + virtual QString getDeviceId() const override { return m_deviceId; } + +private: + + QString m_deviceId; + +public slots: + void handleReply(QNetworkReply* reply); +}; + +class SDRBASE_API TPLinkDeviceDiscoverer : public DeviceDiscoverer, TPLinkCommon { + Q_OBJECT +public: + + TPLinkDeviceDiscoverer(const QString& username, const QString &password); + ~TPLinkDeviceDiscoverer(); + virtual void getDevices() override; + +private: + void getState(const QString &deviceId); + + QHash m_getStateReplies; + QList m_devices; + +public slots: + void handleReply(QNetworkReply* reply); +}; + +#endif /* INCLUDE_IOT_TPLINK_H */ diff --git a/android/app/src/main/cpp/util/iot/visa.cpp b/android/app/src/main/cpp/util/iot/visa.cpp new file mode 100644 index 0000000..53f5933 --- /dev/null +++ b/android/app/src/main/cpp/util/iot/visa.cpp @@ -0,0 +1,506 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "util/iot/visa.h" +#include "util/visa.h" +#include "util/simpleserializer.h" + +VISADevice::VISADevice(const QHash settings, const QString &deviceId, + const QStringList &controls, const QStringList &sensors, + DeviceDiscoverer::DeviceInfo *info) : + Device(info), + m_deviceId(deviceId), + m_session(0), + m_controls(controls), + m_sensors(sensors) +{ + m_visa.openDefault(); + + QHashIterator itr(settings); + while (itr.hasNext()) + { + itr.next(); + QString key = itr.key(); + QVariant value = itr.value(); + + if ((key == "deviceId") || (key == "controlIds") || (key == "sensorIds")) + { + // Nothing to do here + } + else if (key == "logIO") + { + m_visa.setDebugIO(value.toBool()); + } + else + { + qDebug() << "VISADevice::VISADevice: Unsupported setting key: " << key << " value: " << value; + } + } + + open(); +} + +VISADevice::~VISADevice() +{ + m_visa.close(m_session); + m_visa.closeDefault(); +} + +bool VISADevice::open() +{ + if (!m_session) { + m_session = m_visa.open(m_deviceId); + } + if (!m_session) { + emit deviceUnavailable(); + } + return m_session != 0; +} + +bool VISADevice::convertToBool(const QString &string, bool &ok) +{ + QString lower = string.trimmed().toLower(); + if ((lower == "0") || (lower == "false") || (lower == "off")) + { + ok = true; + return false; + } + else if ((lower == "1") || (lower == "true") || (lower == "on")) + { + ok = true; + return true; + } + else + { + ok = false; + return false; + } +} + +void VISADevice::convert(QHash &status, const QString &id, DeviceDiscoverer::Type type, const QString &state) +{ + if (type == DeviceDiscoverer::BOOL) + { + bool ok; + bool value = convertToBool(state, ok); + if (ok) { + status.insert(id, value); + } else { + status.insert(id, "error"); + } + } + else if (type == DeviceDiscoverer::INT) + { + bool ok; + int value = state.toInt(&ok); + if (ok) { + status.insert(id, value); + } else { + status.insert(id, "error"); + } + } + else if (type == DeviceDiscoverer::FLOAT) + { + bool ok; + float value = state.toFloat(&ok); + if (ok) { + status.insert(id, value); + } else { + status.insert(id, "error"); + } + } + else + { + status.insert(id, state); + } +} + +void VISADevice::getState() +{ + if (open()) + { + QHash status; + + for (auto c : m_info.m_controls) + { + if (m_controls.contains(c->m_id)) + { + VISAControl *control = reinterpret_cast(c); + QString cmds = control->m_getState.trimmed(); + if (!cmds.isEmpty()) + { + bool error; + QStringList results = m_visa.processCommands(m_session, cmds, &error); + if (!error && (results.size() > 0)) + { + // Take last returned value as the state + QString state = results[results.size()-1].trimmed(); + convert(status, control->m_id, control->m_type, state); + } + else + { + status.insert(control->m_id, "error"); + } + } + } + } + for (auto s : m_info.m_sensors) + { + if (m_sensors.contains(s->m_id)) + { + VISASensor *sensor = reinterpret_cast(s); + QString cmds = sensor->m_getState.trimmed(); + if (!cmds.isEmpty()) + { + bool error; + QStringList results = m_visa.processCommands(m_session, cmds, &error); + if (!error && (results.size() > 0)) + { + // Take last returned value as the state + QString state = results[results.size()-1].trimmed(); + convert(status, sensor->m_id, sensor->m_type, state); + } + else + { + status.insert(sensor->m_id, "error"); + } + } + } + } + + emit deviceUpdated(status); + } +} + +void VISADevice::setState(const QString &controlId, bool state) +{ + if (open()) + { + for (auto c : m_info.m_controls) + { + VISAControl *control = reinterpret_cast(c); + if (control->m_id == controlId) + { + QString commands = QString::asprintf(control->m_setState.toUtf8(), (int)state); + bool error; + m_visa.processCommands(m_session, commands, &error); + if (error) { + qDebug() << "VISADevice::setState: Failed to set state of " << controlId; + } + } + } + } +} + +void VISADevice::setState(const QString &controlId, int state) +{ + if (open()) + { + for (auto c : m_info.m_controls) + { + VISAControl *control = reinterpret_cast(c); + if (control->m_id == controlId) + { + QString commands = QString::asprintf(control->m_setState.toUtf8(), state); + bool error; + m_visa.processCommands(m_session, commands, &error); + if (error) { + qDebug() << "VISADevice::setState: Failed to set state of " << controlId; + } + } + } + } +} + +void VISADevice::setState(const QString &controlId, float state) +{ + if (open()) + { + for (auto c : m_info.m_controls) + { + VISAControl *control = reinterpret_cast(c); + if (control->m_id == controlId) + { + QString commands = QString::asprintf(control->m_setState.toUtf8(), state); + bool error; + m_visa.processCommands(m_session, commands, &error); + if (error) { + qDebug() << "VISADevice::setState: Failed to set state of " << controlId; + } + } + } + } +} + +void VISADevice::setState(const QString &controlId, const QString &state) +{ + if (open()) + { + for (auto c : m_info.m_controls) + { + VISAControl *control = reinterpret_cast(c); + if (control->m_id == controlId) + { + QString commands = QString::asprintf(control->m_setState.toUtf8(), state.toUtf8().data()); + bool error; + m_visa.processCommands(m_session, commands, &error); + if (error) { + qDebug() << "VISADevice::setState: Failed to set state of " << controlId; + } + } + } + } +} + +VISADeviceDiscoverer::VISADeviceDiscoverer(const QString& resourceFilter) : + m_resourceFilter(resourceFilter) +{ + m_session = m_visa.openDefault(); +} + +VISADeviceDiscoverer::~VISADeviceDiscoverer() +{ + m_visa.closeDefault(); +} + +void VISADeviceDiscoverer::getDevices() +{ + QRegularExpression *filterP = nullptr; + QRegularExpression filter(m_resourceFilter); + if (!m_resourceFilter.trimmed().isEmpty()) { + filterP = &filter; + } + + // Get list of VISA instruments + QList instruments = m_visa.instruments(filterP); + + // Convert to list of devices + QList devices; + for (auto const &instrument : instruments) + { + DeviceInfo info; + info.m_name = instrument.m_model; + info.m_id = instrument.m_resource; + info.m_model = instrument.m_model; + + if ((info.m_name == "DP832") || (info.m_name == "DP832A")) + { + for (int i = 1; i <= 3; i++) + { + VISADevice::VISAControl *output = new VISADevice::VISAControl(); + output->m_name = QString("CH%1").arg(i); + output->m_id = QString("control.ch%1").arg(i); + output->m_type = BOOL; + output->m_getState = QString(":OUTPUT? CH%1").arg(i); + output->m_setState = QString(":OUTPUT CH%1,%d").arg(i); + info.m_controls.append(output); + + VISADevice::VISAControl *setVoltage = new VISADevice::VISAControl(); + setVoltage->m_name = QString("V%1").arg(i); + setVoltage->m_id = QString("control.voltage%1").arg(i); + setVoltage->m_type = FLOAT; + setVoltage->m_min = 0.0f; + setVoltage->m_max = i == 3 ? 5.0f : 30.0f; + setVoltage->m_scale = 1.0f; + setVoltage->m_precision = 3; + setVoltage->m_widgetType = SPIN_BOX; + setVoltage->m_units = "V"; + setVoltage->m_getState = QString(":SOURCE%1:VOLTage?").arg(i); + setVoltage->m_setState = QString(":SOURCE%1:VOLTage %f").arg(i); + info.m_controls.append(setVoltage); + + VISADevice::VISAControl *setCurrent = new VISADevice::VISAControl(); + setCurrent->m_name = QString("i%1").arg(i); + setCurrent->m_id = QString("control.current%1").arg(i); + setCurrent->m_type = FLOAT; + setCurrent->m_min = 0.0f; + setCurrent->m_max = 3.0f; + setCurrent->m_scale = 1.0f; + setCurrent->m_precision = 3; + setCurrent->m_widgetType = SPIN_BOX; + setCurrent->m_units = "A"; + setCurrent->m_getState = QString(":SOURCE%1:CURRent?").arg(i); + setCurrent->m_setState = QString(":SOURCE%1:CURRent %f").arg(i); + info.m_controls.append(setCurrent); + + VISADevice::VISASensor *voltage = new VISADevice::VISASensor(); + voltage->m_name = QString("V%1").arg(i); + voltage->m_id = QString("sensor.voltage%1").arg(i); + voltage->m_type = FLOAT; + voltage->m_units = "V"; + voltage->m_getState = QString(":MEASure:VOLTage? CH%1").arg(i); + info.m_sensors.append(voltage); + + VISADevice::VISASensor *current = new VISADevice::VISASensor(); + current->m_name = QString("i%1").arg(i); + current->m_id = QString("sensor.current%1").arg(i); + current->m_type = FLOAT; + current->m_units = "A"; + current->m_getState = QString(":MEASure:CURRent? CH%1").arg(i); + info.m_sensors.append(current); + + VISADevice::VISASensor *power = new VISADevice::VISASensor(); + power->m_name = QString("P%1").arg(i); + power->m_id = QString("sensor.power%1").arg(i); + power->m_type = FLOAT; + power->m_units = "W"; + power->m_getState = QString(":MEASure:POWEr? CH%1").arg(i); + info.m_sensors.append(power); + } + } + else if (info.m_name == "SSA3032X") + { + VISADevice::VISAControl *frequency = new VISADevice::VISAControl(); + frequency->m_name = "Frequency"; + frequency->m_id = "control.frequency"; + frequency->m_type = FLOAT; + frequency->m_min = 0.0f; + frequency->m_max = 3.2e3f; + frequency->m_scale = 1e6f; + frequency->m_precision = 6; + frequency->m_widgetType = SPIN_BOX; + frequency->m_units = "MHz"; + frequency->m_getState = ":FREQuency:CENTer?"; + frequency->m_setState = ":FREQuency:CENTer %f"; + info.m_controls.append(frequency); + + VISADevice::VISAControl *span = new VISADevice::VISAControl(); + span->m_name = "Span"; + span->m_id = "control.span"; + span->m_type = FLOAT; + span->m_min = 0.0f; + span->m_max = 3.2e3f; + span->m_scale = 1e6; + span->m_precision = 3; + span->m_widgetType = SPIN_BOX; + span->m_units = "MHz"; + span->m_getState = ":FREQuency:SPAN?"; + span->m_setState = ":FREQuency:SPAN %f"; + info.m_controls.append(span); + + VISADevice::VISAControl *markerX = new VISADevice::VISAControl(); + markerX->m_name = "Marker X"; + markerX->m_id = "control.markerx"; + markerX->m_type = FLOAT; + markerX->m_min = 0.0f; + markerX->m_max = 3.2e3f; + markerX->m_scale = 1e6; + markerX->m_precision = 6; + markerX->m_widgetType = SPIN_BOX; + markerX->m_units = "MHz"; + markerX->m_getState = ":CALCulate:MARKer1:X?"; + markerX->m_setState = ":CALCulate:MARKer1:X %f"; + info.m_controls.append(markerX); + + VISADevice::VISASensor *markerY = new VISADevice::VISASensor(); + markerY->m_name = "Marker Y"; + markerY->m_id = "sensor.markery"; + markerY->m_type = FLOAT; + markerY->m_units = "dBm"; + markerY->m_getState = ":CALCulate:MARKer1:Y?"; + info.m_sensors.append(markerY); + } + + devices.append(info); + } + emit deviceList(devices); +} + +DeviceDiscoverer::ControlInfo *VISADevice::VISAControl::clone() const +{ + return new VISAControl(*this); +} + +QByteArray VISADevice::VISAControl::serialize() const +{ + SimpleSerializer s(1); + + s.writeBlob(1, ControlInfo::serialize()); + s.writeString(2, m_getState); + s.writeString(3, m_setState); + + return s.final(); +} + +bool VISADevice::VISAControl::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + QByteArray blob; + + d.readBlob(1, &blob); + ControlInfo::deserialize(blob); + d.readString(2, &m_getState); + d.readString(3, &m_setState); + return true; + } + else + { + return false; + } +} + +DeviceDiscoverer::SensorInfo *VISADevice::VISASensor::clone() const +{ + return new VISASensor(*this); +} + +QByteArray VISADevice::VISASensor::serialize() const +{ + SimpleSerializer s(1); + + s.writeBlob(1, SensorInfo::serialize()); + s.writeString(2, m_getState); + return s.final(); +} + +bool VISADevice::VISASensor::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + QByteArray blob; + + d.readBlob(1, &blob); + SensorInfo::deserialize(blob); + d.readString(2, &m_getState); + return true; + } + else + { + return false; + } +} diff --git a/android/app/src/main/cpp/util/iot/visa.h b/android/app/src/main/cpp/util/iot/visa.h new file mode 100644 index 0000000..02f2cd3 --- /dev/null +++ b/android/app/src/main/cpp/util/iot/visa.h @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_IOT_VISA_H +#define INCLUDE_IOT_VISA_H + +#include "util/iot/device.h" +#include "util/visa.h" + +class SDRBASE_API VISADevice : public Device { + Q_OBJECT +public: + + struct SDRBASE_API VISAControl : public DeviceDiscoverer::ControlInfo { + QString m_getState; + QString m_setState; + + virtual DeviceDiscoverer::ControlInfo *clone() const override; + virtual QByteArray serialize() const override; + virtual bool deserialize(const QByteArray& data) override; + }; + + struct SDRBASE_API VISASensor : public DeviceDiscoverer::SensorInfo { + QString m_getState; + + virtual DeviceDiscoverer::SensorInfo *clone() const override; + virtual QByteArray serialize() const override; + virtual bool deserialize(const QByteArray& data) override; + }; + + VISADevice(const QHash settings, const QString &deviceId, + const QStringList &controls, const QStringList &sensors, + DeviceDiscoverer::DeviceInfo *info=nullptr); + ~VISADevice(); + virtual void getState() override; + using Device::setState; + virtual void setState(const QString &controlId, bool state) override; + virtual void setState(const QString &controlId, int state) override; + virtual void setState(const QString &controlId, float state) override; + virtual void setState(const QString &controlId, const QString &state) override; + virtual QString getProtocol() const override { return "VISA"; } + virtual QString getDeviceId() const override { return m_deviceId; } + +private: + QString m_deviceId; // VISA resource (E.g. USB0::0xcafe...) + VISA m_visa; + ViSession m_session; + QStringList m_controls; // Control IDs for getState + QStringList m_sensors; // Sensor IDs for getState + bool m_debugIO; + + bool open(); + bool convertToBool(const QString &string, bool &ok); + void convert(QHash &status, const QString &id, DeviceDiscoverer::Type type, const QString &state); +}; + +class SDRBASE_API VISADeviceDiscoverer : public DeviceDiscoverer { + Q_OBJECT +public: + + VISADeviceDiscoverer(const QString &resourceFilter = ""); + ~VISADeviceDiscoverer(); + virtual void getDevices() override; + +private: + VISA m_visa; + ViSession m_session; + QString m_resourceFilter; +}; + +#endif /* INCLUDE_IOT_VISA_H */ diff --git a/android/app/src/main/cpp/util/kiwisdrlist.cpp b/android/app/src/main/cpp/util/kiwisdrlist.cpp new file mode 100644 index 0000000..60f8b35 --- /dev/null +++ b/android/app/src/main/cpp/util/kiwisdrlist.cpp @@ -0,0 +1,212 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "kiwisdrlist.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "util/corsproxy.h" + +KiwiSDRList::KiwiSDRList() +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect(m_networkManager, &QNetworkAccessManager::finished, this, &KiwiSDRList::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("kiwisdr"))) { + qDebug() << "Failed to create cache/kiwisdr"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("kiwisdr")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); + + connect(&m_timer, &QTimer::timeout, this, &KiwiSDRList::update); +} + +KiwiSDRList::~KiwiSDRList() +{ + QObject::disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &KiwiSDRList::handleReply); + delete m_networkManager; +} + +void KiwiSDRList::getData() +{ +#ifdef __EMSCRIPTEN__ + // kiwisdr.com doesn't support https, but it's needed for Emscripten - our CORS proxy handles it + QUrl url = CORSProxy::adjustHost(QUrl("https://kiwisdr.com/.public/")); +#else + QUrl url = CORSProxy::adjustHost(QUrl("http://kiwisdr.com/.public/")); +#endif + m_networkManager->get(QNetworkRequest(url)); +} + +void KiwiSDRList::getDataPeriodically(int periodInMins) +{ + m_timer.setInterval(periodInMins*60*1000); + m_timer.start(); + update(); +} + +void KiwiSDRList::update() +{ + getData(); +} + +void KiwiSDRList::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QString url = reply->url().toEncoded().constData(); + QByteArray bytes = reply->readAll(); + + handleHTML(url, bytes); + } + else + { + qDebug() << "KiwiSDRList::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "KiwiSDRList::handleReply: reply is null"; + } +} + +void KiwiSDRList::handleHTML(const QString& url, const QByteArray& bytes) +{ + (void) url; + + QList sdrs; + QString html(bytes); + + // Strip nested divs, as the following div regexp can't handle them + QRegularExpression divName("
(.*?)<\\/div>", QRegularExpression::DotMatchesEverythingOption); + html.replace(divName, "\\1"); + + QRegularExpression div("
(.*?)<\\/div>", QRegularExpression::DotMatchesEverythingOption); + QRegularExpressionMatchIterator divItr = div.globalMatch(html); + + if (divItr.hasNext()) + { + while (divItr.hasNext()) + { + QRegularExpressionMatch divMatch = divItr.next(); + QString divText = divMatch.captured(1); + QRegularExpression urlRe("a href='(.*?)'"); + QRegularExpressionMatch urlMatch = urlRe.match(divText); + + if (urlMatch.hasMatch()) + { + KiwiSDR sdr; + + sdr.m_url = urlMatch.captured(1); + + QRegularExpression element(""); + QRegularExpressionMatchIterator elementItr = element.globalMatch(divText); + while(elementItr.hasNext()) + { + QRegularExpressionMatch elementMatch = elementItr.next(); + QString key = elementMatch.captured(1); + QString value = elementMatch.captured(2); + + if (key == "name") + { + sdr.m_name = value; + } + else if (key == "sdr_hw") + { + sdr.m_sdrHW = value; + } + else if (key == "bands") + { + QRegularExpression freqRe("([\\d]+)-([\\d]+)"); + QRegularExpressionMatch freqMatch = freqRe.match(value); + + if (freqMatch.hasMatch()) + { + sdr.m_lowFrequency = freqMatch.captured(1).toInt(); + sdr.m_highFrequency = freqMatch.captured(2).toInt(); + } + } + else if (key == "users") + { + sdr.m_users = value.toInt(); + } + else if (key == "users_max") + { + sdr.m_usersMax = value.toInt(); + } + else if (key == "gps") + { + QRegularExpression gpsRe("([\\d.+-]+), ([\\d.+-]+)"); + QRegularExpressionMatch gpsMatch = gpsRe.match(value); + + if (gpsMatch.hasMatch()) + { + sdr.m_latitude = gpsMatch.captured(1).toFloat(); + sdr.m_longitude = gpsMatch.captured(2).toFloat(); + } + } + else if (key == "asl") + { + sdr.m_altitude = value.toInt(); + } + else if (key == "loc") + { + sdr.m_location = value; + } + else if (key == "antenna") + { + sdr.m_antenna = value; + } + else if (key == "ant_connected") + { + sdr.m_antennaConnected = value == "1"; + } + else if (key == "snr") + { + sdr.m_snr = value; + } + } + + sdrs.append(sdr); + } + else + { + qDebug() << "KiwiSDRPublic::handleHTML: No URL found in:\n" << divText; + } + } + } + else + { + qDebug() << "KiwiSDRPublic::handleHTML: No cl-info found in:\n" << html; + } + + emit dataUpdated(sdrs); +} diff --git a/android/app/src/main/cpp/util/kiwisdrlist.h b/android/app/src/main/cpp/util/kiwisdrlist.h new file mode 100644 index 0000000..ba0c562 --- /dev/null +++ b/android/app/src/main/cpp/util/kiwisdrlist.h @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_KIWISDRLIST_H +#define INCLUDE_KIWISDRLIST_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// Gets a list of public Kiwi SDRs from http://kiwisdr.com/.public/ +class SDRBASE_API KiwiSDRList : public QObject +{ + Q_OBJECT + +public: + + struct KiwiSDR { + QString m_url; + QString m_status; // Only seems to be 'active' + QString m_offline; // Only seems to be 'no' + QString m_name; + QString m_sdrHW; + qint64 m_lowFrequency; + qint64 m_highFrequency; + int m_users; + int m_usersMax; + float m_latitude; + float m_longitude; + int m_altitude; // Above sea level (Not sure if ft or m) + QString m_location; + QString m_antenna; + bool m_antennaConnected; + QString m_snr; + }; + + KiwiSDRList(); + ~KiwiSDRList(); + + void getData(); + void getDataPeriodically(int periodInMins=4); + +public slots: + void handleReply(QNetworkReply* reply); + void update(); + +signals: + void dataUpdated(const QList& sdrs); // Emitted when data are available. + +private: + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + QTimer m_timer; // Timer for periodic updates + + void handleHTML(const QString& url, const QByteArray& bytes); + +}; + +#endif /* INCLUDE_KIWISDRLIST_H */ diff --git a/android/app/src/main/cpp/util/lfsr.cpp b/android/app/src/main/cpp/util/lfsr.cpp new file mode 100644 index 0000000..e122950 --- /dev/null +++ b/android/app/src/main/cpp/util/lfsr.cpp @@ -0,0 +1,130 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "lfsr.h" +#include "popcount.h" + +// Shift LFSR one bit +int LFSR::shift() +{ + int bit; + + bit = (popcount(m_sr & m_polynomial) & 1) ^ 1; + m_sr = (m_sr << 1) | bit; + return bit; +} + +// Scramble a single bit +int LFSR::scramble(int bit_in) +{ + int bit_out; + + bit_out = (popcount(m_sr & m_polynomial) & 1) ^ bit_in; + m_sr = (m_sr << 1) | bit_out; + return bit_out; +} + +// Scramble data using LFSR - LSB first +void LFSR::scramble(uint8_t *data, int length) +{ + uint8_t byte_in, byte_out; + int bit_in, bit_out; + + for(int i = 0; i < length; i++) + { + byte_in = data[i]; + byte_out = 0; + for (int j = 0; j < 8; j++) + { + bit_in = (byte_in >> j) & 1; + bit_out = (popcount(m_sr & m_polynomial) & 1) ^ bit_in; + m_sr = (m_sr << 1) | bit_out; + byte_out = byte_out | (bit_out << j); + } + data[i] = byte_out; + } +} + +// Descramble data using LFSR - LSB first +void LFSR::descramble(uint8_t *data, int length) +{ + uint8_t byte_in, byte_out; + int bit_in, bit_out; + + for(int i = 0; i < length; i++) + { + byte_in = data[i]; + byte_out = 0; + for (int j = 0; j < 8; j++) + { + bit_in = (byte_in >> j) & 1; + bit_out = (popcount(m_sr & m_polynomial) & 1) ^ bit_in; + m_sr = (m_sr << 1) | bit_in; + byte_out = byte_out | (bit_out << j); + } + data[i] = byte_out; + } +} + +// XOR data with rand_bits of LFSR - LSB first +void LFSR::randomize(uint8_t *data, int length) +{ + uint8_t byte_in, byte_out; + int bit_in, bit_out, bit; + + for(int i = 0; i < length; i++) + { + byte_in = data[i]; + byte_out = 0; + for (int j = 0; j < 8; j++) + { + // XOR input bit with specified bit from SR + bit_in = (byte_in >> j) & 1; + //bit_out = ((m_sr >> m_rand_bit) & 1) ^ bit_in; + bit_out = (popcount(m_sr & m_rand_bits) & 1) ^ bit_in; + byte_out = byte_out | (bit_out << j); + // Update LFSR + bit = popcount(m_sr & m_polynomial) & 1; + m_sr = (m_sr << 1) | bit; + } + data[i] = byte_out; + } +} + +// XOR data with rand_bits of LFSR - MSB first +void LFSR::randomizeMSB(const uint8_t *dataIn, uint8_t *dataOut, int length) +{ + uint8_t byte_in, byte_out; + int bit_in, bit_out, bit; + + for(int i = 0; i < length; i++) + { + byte_in = dataIn[i]; + byte_out = 0; + for (int j = 7; j >= 0; j--) + { + // XOR input bit with selected bits from SR + bit_in = (byte_in >> j) & 1; + bit_out = (popcount(m_sr & m_rand_bits) & 1) ^ bit_in; + byte_out = byte_out | (bit_out << j); + // Update LFSR + bit = popcount(m_sr & m_polynomial) & 1; + m_sr = (m_sr << 1) | bit; + } + dataOut[i] = byte_out; + } +} diff --git a/android/app/src/main/cpp/util/lfsr.h b/android/app/src/main/cpp/util/lfsr.h new file mode 100644 index 0000000..e920fa2 --- /dev/null +++ b/android/app/src/main/cpp/util/lfsr.h @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_LFSR_H +#define INCLUDE_LFSR_H + +#include + +#include "export.h" + +// Linear feedback shift register that can be used for scrambling or generating +// PN (Pseudo Noise) random sequence. +class SDRBASE_API LFSR +{ +public: + // Create and initialise LFSR with specified number of bits, polynomial and + // initial state (which must be non-zero, unless a multiplicative scrambler). + // The +1 is implicit in the polynomial so x^1 + 1 should be passed as 0x01 + LFSR(uint32_t polynomial, uint32_t init_value = ~0U, int rand_bits = 1) : + m_rand_bits(rand_bits), + m_polynomial(polynomial), + m_init_value(init_value) + { + init(); + } + + // Initialise LFSR state + void init() + { + m_sr = m_init_value; + } + + // Shift the LFSR one bit and return output of XOR + int shift(); + + // Multiplicative scramble a single bit using LFSR + int scramble(int bit_in); + + // Multiplicative scramble of data using LFSR - LSB first + void scramble(uint8_t *data, int length); + // Descramble data using LFSR - LSB first + void descramble(uint8_t *data, int length); + + // XOR data with rand_bits from LFSR generating pseudo noise (PN) sequence - LSB first + void randomize(uint8_t *data, int length); + // XOR data with rand_bits from LFSR generating pseudo noise (PN) sequence - MSB first + void randomizeMSB(const uint8_t *dataIn, uint8_t *dataOut, int length); + + // Get current shift-register value + uint32_t getSR() + { + return m_sr; + } + + // Set the polynomial + void setPolynomial(uint32_t polynomial) + { + m_polynomial = polynomial; + } + + // Get the polynomial + uint32_t getPolynomial() + { + return m_polynomial; + } + +private: + int m_rand_bits; // Which bits from the SR to use in randomize() + uint32_t m_polynomial; // Polynomial coefficients (+1 is implicit) + uint32_t m_init_value; // Value to initialise SR to when init() is called + uint32_t m_sr; // Shift register +}; + +// http://www.jrmiller.demon.co.uk/products/figs/man9k6.pdf +// In Matlab: comm.Scrambler(2, '1 + z^-12 + z^-17', 0) +// Call scramble() +class SDRBASE_API ScramblerG3RUG : public LFSR +{ +public: + ScramblerG3RUG() : LFSR(0x10800, 0x0) {} +}; + +// https://public.ccsds.org/Pubs/131x0b3e1.pdf +// x^8+x^7+x^5+x^3+1 +// In Matlab: comm.PNSequence('Polynomial', 'x^8+x^7+x^5+x^3+1', 'InitialConditions', [1 1 1 1 1 1 1 1]) +// randomize() shifts to right, so polynomial below is reversed with x^8 implicit +// Call randomize() +class SDRBASE_API RandomizeCCSDS : public LFSR +{ +public: + RandomizeCCSDS() : LFSR(0x95, 0xff, 1<<7) {} +}; + +// DVB-S +// https://www.etsi.org/deliver/etsi_en/300400_300499/300421/01.01.02_60/en_300421v010102p.pdf +// x^15+x^14+1 +// In Matlab: comm.PNSequence('Polynomial', 'z^15+z^1+1', 'InitialConditions', [1 0 0 1 0 1 0 1 0 0 0 0 0 0 0], 'Mask', [0 0 0 0 0 0 0 0 0 0 0 0 0 1 1]) +// Call randomizeMSB() +class SDRBASE_API RandomizeDVBS : public LFSR +{ +public: + RandomizeDVBS() : LFSR(0x6000, 0x00a9, 0x6000) {} +}; + +// 802.15.4 GFSK PHY +// In Matlab: comm.PNSequence('Polynomial', [1 0 0 0 1 0 0 0 0 1], 'InitialConditions', [0 1 1 1 1 1 1 1 1], 'Mask', [1 0 0 0 0 0 0 0 0]) +// Call randomize() +class SDRBASE_API Randomize_802_15_4_PN9 : public LFSR +{ +public: + Randomize_802_15_4_PN9() : LFSR(0x108, 0x1fe, 1) {} +}; + +#endif diff --git a/android/app/src/main/cpp/util/limitedcounter.h b/android/app/src/main/cpp/util/limitedcounter.h new file mode 100644 index 0000000..b63d8bf --- /dev/null +++ b/android/app/src/main/cpp/util/limitedcounter.h @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_LIMITEDCOUNTER_H_ +#define SDRBASE_UTIL_LIMITEDCOUNTER_H_ + +#include + +template +class LimitedCounter +{ +public: + LimitedCounter() : + m_counter(0) + {} + + LimitedCounter(StoreType value) : + m_counter(value < Limit ? value : Limit - 1) + {} + + void operator=(const LimitedCounter& rhs) { + m_counter = rhs.m_counter; + } + + LimitedCounter& operator++() { + m_counter++; + if (m_counter >= Limit) { + m_counter = 0; + } + return *this; + } + + LimitedCounter operator++(int) { + LimitedCounter temp = *this ; + m_counter++; + if (m_counter >= Limit) { + m_counter = 0; + } + return temp; + } + + LimitedCounter& operator+=(const uint32_t rhs) { + m_counter += rhs; + if (m_counter >= Limit) { + m_counter -= Limit; + } + return *this; + } + + StoreType value() const { return m_counter; } + + friend LimitedCounter operator-(const LimitedCounter lhs, const LimitedCounter& rhs) + { + LimitedCounter result; + + if (lhs.m_counter < rhs.m_counter) { + result.m_counter = Limit - lhs.m_counter + rhs.m_counter + 1; + } else { + result.m_counter = lhs.m_counter - rhs.m_counter; + } + + return result; + } + +private: + StoreType m_counter; +}; + + + +#endif /* SDRBASE_UTIL_LIMITEDCOUNTER_H_ */ diff --git a/android/app/src/main/cpp/util/maidenhead.cpp b/android/app/src/main/cpp/util/maidenhead.cpp new file mode 100644 index 0000000..8d60b5b --- /dev/null +++ b/android/app/src/main/cpp/util/maidenhead.cpp @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// Maidenhead locator utilities // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "maidenhead.h" + +// See: https://en.wikipedia.org/wiki/Maidenhead_Locator_System + +QString Maidenhead::toMaidenhead(float latitude, float longitude) +{ + longitude += 180.0; + latitude += 90.0; + int lon1 = floor(longitude/20); + longitude -= lon1*20; + int lat1 = floor(latitude/10); + latitude -= lat1*10; + int lon2 = floor(longitude/2); + longitude -= lon2*2; + int lat2 = floor(latitude/1); + latitude -= lat2; + int lon3 = round(longitude*12); + int lat3 = round(latitude*24); + return QString("%1%2%3%4%5%6").arg(QChar(lon1+'A')).arg(QChar(lat1+'A')) + .arg(QChar(lon2+'0')).arg(QChar(lat2+'0')) + .arg(QChar(lon3+'A')).arg(QChar(lat3+'A')); +} + +bool Maidenhead::fromMaidenhead(const QString& maidenhead, float& latitude, float& longitude) +{ + if (Maidenhead::isMaidenhead(maidenhead)) + { + int lon1 = maidenhead[0].toUpper().toLatin1() - 'A'; + int lat1 = maidenhead[1].toUpper().toLatin1() - 'A'; + int lon2 = maidenhead[2].toLatin1() - '0'; + int lat2 = maidenhead[3].toLatin1() - '0'; + int lon3 = 0; + int lat3 = 0; + int lon4 = 0; + int lat4 = 0; + if (maidenhead.length() >= 6) + { + lon3 = maidenhead[4].toUpper().toLatin1() - 'A'; + lat3 = maidenhead[5].toUpper().toLatin1() - 'A'; + } + if (maidenhead.length() == 8) + { + lon4 = maidenhead[6].toLatin1() - '0'; + lat4 = maidenhead[7].toLatin1() - '0'; + } + longitude = lon1 * 20 + lon2 * 2 + lon3 * 2.0/24.0 + lon4 * 2.0/24.0/10; // 20=360/18 + latitude = lat1 * 10 + lat2 * 1 + lat3 * 1.0/24.0 + lat4 * 1.0/24.0/10; // 10=180/18 + + longitude -= 180.0; + latitude -= 90.0; + + return true; + } + else + return false; +} + +bool Maidenhead::isMaidenhead(const QString& maidenhead) +{ + int length = maidenhead.length(); + if ((length != 4) && (length != 6) && (length != 8)) + return false; + QRegularExpression re(QRegularExpression::anchoredPattern("[A-Ra-r][A-Ra-r][0-9][0-9]([A-Xa-x][A-Xa-x]([0-9][0-9])?)?")); + return re.match(maidenhead).hasMatch(); +} diff --git a/android/app/src/main/cpp/util/maidenhead.h b/android/app/src/main/cpp/util/maidenhead.h new file mode 100644 index 0000000..5b975b5 --- /dev/null +++ b/android/app/src/main/cpp/util/maidenhead.h @@ -0,0 +1,40 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// Maidenhead locator utilities // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_MAIDENHEAD_H +#define INCLUDE_MAIDENHEAD_H + +#include + +#include "export.h" + +class SDRBASE_API Maidenhead { + +public: + + static QString toMaidenhead(float latitude, float longitude); + static bool fromMaidenhead(const QString& maidenhead, float& latitude, float& longitude); + static bool isMaidenhead(const QString& maidenhead); + +}; + +#endif // INCLUDE_MAIDENHEAD_H diff --git a/android/app/src/main/cpp/util/max2d.h b/android/app/src/main/cpp/util/max2d.h new file mode 100644 index 0000000..58b6ee3 --- /dev/null +++ b/android/app/src/main/cpp/util/max2d.h @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_MAX2D_H_ +#define SDRBASE_UTIL_MAX2D_H_ + +#include + +template +class Max2D +{ +public: + Max2D() : m_max(0), m_maxSize(0), m_width(0), m_size(0), m_maxIndex(0) {} + + ~Max2D() + { + if (m_max) { + delete[] m_max; + } + } + + void resize(unsigned int width, unsigned int size) + { + if (width > m_maxSize) + { + m_maxSize = width; + if (m_max) { + delete[] m_max; + } + m_max = new T[m_maxSize]; + } + + m_width = width; + m_size = size; + + std::fill(m_max, m_max+m_width, 0); + m_maxIndex = 0; + } + + bool storeAndGetMax(T& max, T v, unsigned int index) + { + if (m_size <= 1) + { + max = v; + return true; + } + + if (m_maxIndex == 0) + { + m_max[index] = v; + return false; + } + else if (m_maxIndex == m_size - 1) + { + m_max[index] = std::max(m_max[index], v); + max = m_max[index]; + return true; + } + else + { + m_max[index] = std::max(m_max[index], v); + return false; + } + } + + bool nextMax() + { + if (m_size <= 1) { + return true; + } + + if (m_maxIndex == m_size - 1) + { + m_maxIndex = 0; + std::fill(m_max, m_max+m_width, 0); + return true; + } + else + { + m_maxIndex++; + return false; + } + } + +private: + T *m_max; + unsigned int m_maxSize; + unsigned int m_width; + unsigned int m_size; + unsigned int m_maxIndex; +}; + +#endif /* SDRBASE_UTIL_MAX2D_H_ */ diff --git a/android/app/src/main/cpp/util/message.cpp b/android/app/src/main/cpp/util/message.cpp new file mode 100644 index 0000000..4e48724 --- /dev/null +++ b/android/app/src/main/cpp/util/message.cpp @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015, 2017, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "util/message.h" + +const char* Message::m_identifier = 0; + +Message::Message() : + m_destination(0) +{ +} + +Message::~Message() +{ +} + +const char* Message::getIdentifier() const +{ + return m_identifier; +} + +bool Message::matchIdentifier(const char* identifier) const +{ + return m_identifier == identifier; +} + +bool Message::match(const Message* message) +{ + return message->matchIdentifier(m_identifier); +} diff --git a/android/app/src/main/cpp/util/message.h b/android/app/src/main/cpp/util/message.h new file mode 100644 index 0000000..a57c4e2 --- /dev/null +++ b/android/app/src/main/cpp/util/message.h @@ -0,0 +1,61 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_MESSAGE_H +#define INCLUDE_MESSAGE_H + +#include +#include "export.h" + +class SDRBASE_API Message { +public: + Message(); + virtual ~Message(); + + virtual const char* getIdentifier() const; + virtual bool matchIdentifier(const char* identifier) const; + static bool match(const Message* message); + + void* getDestination() const { return m_destination; } + void setDestination(void *destination) { m_destination = destination; } + +protected: + // addressing + static const char* m_identifier; + void* m_destination; +}; + +#define MESSAGE_CLASS_DECLARATION \ + public: \ + const char* getIdentifier() const; \ + bool matchIdentifier(const char* identifier) const; \ + static bool match(const Message& message); \ + protected: \ + static const char* m_identifier; \ + private: + +#define MESSAGE_CLASS_DEFINITION(Name, BaseClass) \ + const char* Name::m_identifier = #Name; \ + const char* Name::getIdentifier() const { return m_identifier; } \ + bool Name::matchIdentifier(const char* identifier) const {\ + return (m_identifier == identifier) ? true : BaseClass::matchIdentifier(identifier); \ + } \ + bool Name::match(const Message& message) { return message.matchIdentifier(m_identifier); } + +#endif // INCLUDE_MESSAGE_H diff --git a/android/app/src/main/cpp/util/messagequeue.cpp b/android/app/src/main/cpp/util/messagequeue.cpp new file mode 100644 index 0000000..d4ac1df --- /dev/null +++ b/android/app/src/main/cpp/util/messagequeue.cpp @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015, 2019, 2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "util/messagequeue.h" +#include "util/message.h" + +MessageQueue::MessageQueue(QObject* parent) : + QObject(parent), + m_queue() +{ + setObjectName("MessageQueue"); +} + +MessageQueue::~MessageQueue() +{ + Message* message; + + while ((message = pop()) != 0) + { + qDebug() << "MessageQueue::~MessageQueue: message: " << message->getIdentifier() << " was still in queue"; + delete message; + } +} + +void MessageQueue::push(Message* message, bool emitSignal) +{ + if (message) + { + m_lock.lock(); + m_queue.append(message); + m_lock.unlock(); + } + + if (emitSignal) + { + emit messageEnqueued(); + } +} + +Message* MessageQueue::pop() +{ + QMutexLocker locker(&m_lock); + + if (m_queue.isEmpty()) + { + return 0; + } + else + { + return m_queue.takeFirst(); + } +} + +int MessageQueue::size() +{ + QMutexLocker locker(&m_lock); + + return m_queue.size(); +} + +void MessageQueue::clear() +{ + QMutexLocker locker(&m_lock); + + while (!m_queue.isEmpty()) { + delete m_queue.takeFirst(); + } +} diff --git a/android/app/src/main/cpp/util/messagequeue.h b/android/app/src/main/cpp/util/messagequeue.h new file mode 100644 index 0000000..82a6480 --- /dev/null +++ b/android/app/src/main/cpp/util/messagequeue.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_MESSAGEQUEUE_H +#define INCLUDE_MESSAGEQUEUE_H + +#include +#include +#include +#include "export.h" + +class Message; + +class SDRBASE_API MessageQueue : public QObject { + Q_OBJECT + +public: + MessageQueue(QObject* parent = NULL); + ~MessageQueue(); + + void push(Message* message, bool emitSignal = true); //!< Push message onto queue + Message* pop(); //!< Pop message from queue + + int size(); //!< Returns queue size + void clear(); //!< Empty queue + +signals: + void messageEnqueued(); + +private: + QRecursiveMutex m_lock; + QQueue m_queue; +}; + +#endif // INCLUDE_MESSAGEQUEUE_H diff --git a/android/app/src/main/cpp/util/mmsi.cpp b/android/app/src/main/cpp/util/mmsi.cpp new file mode 100644 index 0000000..584f93e --- /dev/null +++ b/android/app/src/main/cpp/util/mmsi.cpp @@ -0,0 +1,422 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "util/mmsi.h" +#include "util/osndb.h" + +// https://www.itu.int/en/ITU-R/terrestrial/fmd/Pages/mid.aspx +// Names used match up with names used for flags in ADS-B directory +QMap MMSI::m_mid = { + {201, "albania"}, + {202, "andorra"}, + {203, "austria"}, + {204, "portugal"}, + {205, "belgium"}, + {206, "belarus"}, + {207, "bulgaria"}, + {208, "vatican_city"}, + {209, "cyprus"}, + {210, "cyprus"}, + {211, "germany"}, + {212, "cyprus"}, + {213, "georgia"}, + {214, "moldova"}, + {215, "malta"}, + {216, "armenia"}, + {218, "germany"}, + {219, "denmark"}, + {220, "denmark"}, + {224, "spain"}, + {225, "spain"}, + {226, "france"}, + {227, "france"}, + {228, "france"}, + {229, "malta"}, + {230, "finland"}, + {231, "denmark"}, + {232, "united_kingdom"}, + {233, "united_kingdom"}, + {234, "united_kingdom"}, + {235, "united_kingdom"}, + {236, "united_kingdom"}, + {237, "greece"}, + {238, "croatia"}, + {239, "greece"}, + {240, "greece"}, + {241, "greece"}, + {242, "morocco"}, + {243, "hungary"}, + {244, "netherlands"}, + {245, "netherlands"}, + {246, "netherlands"}, + {247, "italy"}, + {248, "malta"}, + {249, "malta"}, + {250, "ireland"}, + {251, "iceland"}, + {252, "liechtenstein"}, + {253, "luxembourg"}, + {254, "monaco"}, + {255, "portugal"}, + {256, "malta"}, + {257, "norway"}, + {258, "norway"}, + {259, "norway"}, + {261, "poland"}, + {262, "montenegro"}, + {263, "portugal"}, + {264, "romania"}, + {265, "sweden"}, + {266, "sweden"}, + {267, "slovakia"}, + {268, "san_marino"}, + {269, "switzerland"}, + {270, "czech_republic"}, + {271, "turkey"}, + {272, "ukraine"}, + {273, "russia"}, + {274, "macedonia"}, + {275, "latvia"}, + {276, "estonia"}, + {277, "slovenia"}, + {279, "serbia"}, + {301, "united_kingdom"}, + {303, "united_states"}, + {304, "antigua_and_barbuda"}, + {305, "antigua_and_barbuda"}, + {306, "netherlands"}, + {307, "netherlands"}, + {308, "bahamas"}, + {309, "bahamas"}, + {310, "bermuda"}, + {311, "bahamas"}, + {312, "belize"}, + {314, "barbados"}, + {316, "canada"}, + {319, "cayman_isles"}, + {321, "costa_rica"}, + {323, "cuba"}, + {325, "dominica"}, + {327, "dominican_republic"}, + {329, "france"}, + {330, "grenada"}, + {331, "denmark"}, // greenland + {332, "guatemala"}, + {334, "honduras"}, + {336, "haiti"}, + {338, "united_states"}, + {339, "jamaica"}, + {341, "st_kitts_and_nevis"}, + {343, "st_lucia"}, + {345, "mexico"}, + {347, "france"}, // martinique + {348, "united_kingdom"}, // montserrat + {350, "nicaragua"}, + {351, "panama"}, + {352, "panama"}, + {353, "panama"}, + {354, "panama"}, + {355, "panama"}, + {356, "panama"}, + {357, "panama"}, + {358, "united_states"}, // puerto_rico + {359, "el_salvador"}, + {361, "france"}, + {362, "trinidad_and_tobago"}, + {364, "turks_and_caicos"}, + {366, "united_states"}, + {367, "united_states"}, + {368, "united_states"}, + {369, "united_states"}, + {370, "panama"}, + {371, "panama"}, + {372, "panama"}, + {373, "panama"}, + {374, "panama"}, + {375, "st_vincent"}, + {376, "st_vincent"}, + {377, "st_vincent"}, + {378, "virgin_isles"}, + {401, "afghanistan"}, + {403, "saudi_arabia"}, + {405, "bangladesh"}, + {408, "bahrain"}, + {410, "bhutan"}, + {412, "china"}, + {413, "china"}, + {414, "china"}, + {416, "taiwan"}, + {417, "sri_lanka"}, + {419, "india"}, + {422, "iran"}, + {423, "azerbaijan"}, + {425, "iraq"}, + {428, "israel"}, + {431, "japan"}, + {432, "japan"}, + {434, "turkmenistan"}, + {436, "kazakhstan"}, + {437, "uzbekistan"}, + {438, "jordan"}, + {440, "korea_south"}, + {441, "korea_south"}, + {443, "palestine"}, + {445, "korea_north"}, + {447, "kuwait"}, + {450, "lebanon"}, + {451, "kyrgyzstan"}, + {453, "china"}, // macao + {455, "maldives"}, + {457, "mongolia"}, + {459, "nepal"}, + {461, "oman"}, + {463, "pakistan"}, + {466, "qatar"}, + {468, "syria"}, + {470, "united_arab_emirates"}, + {471, "united_arab_emirates"}, + {472, "tajikistan"}, + {473, "yemen"}, + {474, "yemen"}, + {477, "hong_kong"}, + {478, "bosnia"}, + {501, "france"}, + {503, "australia"}, + {506, "myanmar"}, + {508, "brunei"}, + {510, "micronesia"}, + {511, "palau"}, + {512, "new_zealand"}, + {514, "cambodia"}, + {515, "cambodia"}, + {516, "australia"}, + {518, "cook_islands"}, + {520, "fiji"}, + {523, "australia"}, + {525, "indonesia"}, + {529, "kiribati"}, + {531, "laos"}, + {533, "malaysia"}, + {536, "united_states"}, + {538, "marshall islands"}, + {540, "france"}, + {542, "new_zealand"}, + {544, "nauru"}, + {546, "france"}, + {548, "philippines"}, + {550, "timorleste"}, + {553, "papua_new_guinea"}, + {555, "united_kingdom"}, + {557, "solomon_islands"}, + {559, "united_states"}, // american_samoa + {561, "samoa"}, + {563, "singapore"}, + {564, "singapore"}, + {565, "singapore"}, + {566, "singapore"}, + {567, "thailand"}, + {570, "tonga"}, + {572, "tuvalu"}, + {574, "vietnam"}, + {576, "vanuatu"}, + {577, "vanuatu"}, + {578, "france"}, + {601, "south_africa"}, + {603, "angola"}, + {605, "algeria"}, + {607, "france"}, + {608, "united_kingdom"}, // ascension_island + {609, "burundi"}, + {610, "benin"}, + {611, "botswana"}, + {612, "central_african_republic"}, + {613, "cameroun"}, // cameroon + {615, "congoroc"}, + {616, "comoros"}, + {617, "cape_verde"}, + {618, "france"}, + {619, "ivory_coast"}, + {620, "comoros"}, + {621, "djibouti"}, + {622, "egypt"}, + {624, "ethiopia"}, + {625, "eritrea"}, + {626, "gabon"}, + {627, "ghana"}, + {629, "gambia"}, + {630, "guinea_bissau"}, + {631, "equatorial_guinea"}, + {632, "guinea"}, + {633, "burkina_faso"}, + {634, "kenya"}, + {635, "france"}, + {636, "liberia"}, + {637, "liberia"}, + {638, "south_sudan"}, + {642, "libya"}, + {644, "lesotho"}, + {645, "mauritius"}, + {647, "madagascar"}, + {649, "mali"}, + {650, "mozambique"}, + {654, "mauritania"}, + {655, "malawi"}, + {656, "niger"}, + {657, "nigeria"}, + {659, "namibia"}, + {660, "france"}, // reunion + {661, "rwanda"}, + {662, "sudan"}, + {663, "senegal"}, + {664, "seychelles"}, + {665, "united_kingdom"}, // saint_helena + {666, "somalia"}, + {667, "sierra_leone"}, + {668, "sao_tome_principe"}, + {669, "swaziland"}, // eswatini + {670, "chad"}, + {671, "togo"}, + {672, "tunisia"}, + {674, "tanzania"}, + {675, "uganda"}, + {676, "congodrc"}, + {677, "tanzania"}, + {678, "zambia"}, + {679, "zimbabwe"}, + {701, "argentina"}, + {710, "brazil"}, + {720, "bolivia"}, + {725, "chile"}, + {730, "colombia"}, + {735, "ecuador"}, + {740, "falkland_isles"}, + {745, "france"}, // guiana + {750, "guyana"}, + {755, "paraguay"}, + {760, "peru"}, + {765, "suriname"}, + {770, "uruguay"}, + {775, "venezuela"}, +}; + +QString MMSI::getMID(const QString &mmsi) +{ + if (mmsi.startsWith("00") || mmsi.startsWith("99") || mmsi.startsWith("98")) { + return mmsi.mid(2, 3); + } else if (mmsi.startsWith("0") || mmsi.startsWith("8")) { + return mmsi.mid(1, 3); + } else if (mmsi.startsWith("111")) { + return mmsi.mid(3, 3); + } else { + return mmsi.left(3); + } +} + +QString MMSI::getCountry(const QString &mmsi) +{ + return m_mid[MMSI::getMID(mmsi).toInt()]; +} + +void MMSI::checkFlags() +{ + // Loop through all MIDs and check to see if we have a flag icon + for (auto id : m_mid.keys()) + { + QString country = m_mid.value(id); + QString path = QString(":/flags/%1.bmp").arg(country); + QResource res(path); + if (!res.isValid()) { + qDebug() << "MMSI::checkFlags: Resource invalid " << path; + } + } +} + +QIcon *MMSI::getFlagIcon(const QString &mmsi) +{ + QString country = getCountry(mmsi); + return AircraftInformation::getFlagIcon(country); +} + +QString MMSI::getFlagIconURL(const QString &mmsi) +{ + QString country = getCountry(mmsi); + return AircraftInformation::getFlagIconURL(country); +} + +QString MMSI::getCategory(const QString &mmsi) +{ + switch (mmsi[0].toLatin1()) + { + case '0': + if (mmsi.startsWith("00")) { + return "Coast"; + } else { + return "Group"; // Group of ships + } + case '1': + // Search and rescue + if (mmsi[6] == '1') { + return "SAR Aircraft"; + } else if (mmsi[6] == '5') { + return "SAR Helicopter"; + } else { + return "SAR"; + } + case '8': + return "Handheld"; + case '9': + if (mmsi.startsWith("970")) + { + return "SAR"; + } + else if (mmsi.startsWith("972")) + { + return "Man overboard"; + } + else if (mmsi.startsWith("974")) + { + return "EPIRB"; // Emergency Becaon + } + else if (mmsi.startsWith("979")) + { + return "AMRD"; // Autonomous + } + else if (mmsi.startsWith("98")) + { + return "Craft with parent ship"; + } + else if (mmsi.startsWith("99")) + { + if (mmsi[5] == '1') { + return "Physical AtoN"; + } else if (mmsi[5] == '6') { + return "Virtual AtoN"; + } else if (mmsi[5] == '8') { + return "Mobile AtoN"; + } else { + return "AtoN"; // Aid-to-navigation + } + } + break; + default: + return "Ship"; // Vessel better? + } + return "Unknown"; +} diff --git a/android/app/src/main/cpp/util/mmsi.h b/android/app/src/main/cpp/util/mmsi.h new file mode 100644 index 0000000..5a8c34a --- /dev/null +++ b/android/app/src/main/cpp/util/mmsi.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef UTIL_MMSI_H +#define UTIL_MMSI_H + +#include +#include + +#include "export.h" + +// Maritime mobile service identities (basically ship identifiers) +// MMSIs defined by ITU-R M.585 +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.585-9-202205-I!!PDF-E.pdf + +class SDRBASE_API MMSI { + +public: + + static QString getMID(const QString &mmsi); + static QString getCountry(const QString &mmsi); + static QString getCategory(const QString &mmsi); + static QIcon *getFlagIcon(const QString &mmsi); + static QString getFlagIconURL(const QString &mmsi); + +private: + + static QMap m_mid; + + static void checkFlags(); +}; + +#endif /* UTIL_MMSI_H */ diff --git a/android/app/src/main/cpp/util/morse.cpp b/android/app/src/main/cpp/util/morse.cpp new file mode 100644 index 0000000..579f015 --- /dev/null +++ b/android/app/src/main/cpp/util/morse.cpp @@ -0,0 +1,205 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020, 2022 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "util/morse.h" + +// ITU encoding with a few extras +const struct Morse::ASCIIToMorse Morse::m_asciiToMorse[] = { + {' ', " "}, + {'!', "-.-.--"}, // Non-ITU + {'\"', ".-..-."}, + {'#', ""}, + {'$', "...-..-"}, // Non-ITU + {'%', ""}, + {'&', ".-..."}, + {'\'', ".----."}, + {'(', "-.--."}, + {')', "-.--.-"}, + {'*', "_.._"}, + {'+', ".-.-."}, + {',', "--..--"}, + {'-', "-....-"}, + {'.', ".-.-.-"}, + {'/', "-..-."}, + {'0', "-----"}, + {'1', ".----"}, + {'2', "..---"}, + {'3', "...--"}, + {'4', "....-"}, + {'5', "....."}, + {'6', "-...."}, + {'7', "--..."}, + {'8', "---.."}, + {'9', "----."}, + {':', "---..."}, + {';', "-.-.-."}, // Non-ITU + {'<', ""}, + {'=', "-...-"}, + {'>', ""}, + {'?', "..--.."}, + {'@', ".--.-."}, + {'A', ".-"}, + {'B', "-..."}, + {'C', "-.-."}, + {'D', "-.."}, + {'E', "."}, + {'F', "..-."}, + {'G', "--."}, + {'H', "...."}, + {'I', ".."}, + {'J', ".---"}, + {'K', "-.-"}, + {'L', ".-.."}, + {'M', "--"}, + {'N', "-."}, + {'O', "---"}, + {'P', ".--."}, + {'Q', "--.-"}, + {'R', ".-."}, + {'S', "..."}, + {'T', "-"}, + {'U', "..-"}, + {'V', "...-"}, + {'W', ".--"}, + {'X', "-..-"}, + {'Y', "-.--"}, + {'Z', "--.."}, + {'[', ""}, + {'\\', ""}, + {']', ""}, + {'^', ""}, + {'_', "..--.-"}, // Non-ITU + {'`', ""}, + {'a', ".-"}, + {'b', "-..."}, + {'c', "-.-."}, + {'d', "-.."}, + {'e', "."}, + {'f', "..-."}, + {'g', "--."}, + {'h', "...."}, + {'i', ".."}, + {'j', ".---"}, + {'k', "-.-"}, + {'l', ".-.."}, + {'m', "--"}, + {'n', "-."}, + {'o', "---"}, + {'p', ".--."}, + {'q', "--.-"}, + {'r', ".-."}, + {'s', "..."}, + {'t', "-"}, + {'u', "..-"}, + {'v', "...-"}, + {'w', ".--"}, + {'x', "-..-"}, + {'y', "-.--"}, + {'z', "--.."}, + {'{', ""}, + {'|', ""}, + {'}', ""}, + {'~', ""} +}; + +// Convert ASCII character to Morse code sequence consisting of . and - characters +QString Morse::toMorse(char ascii) +{ + char firstChar = m_asciiToMorse[0].ascii; + if ((ascii >= firstChar) && (ascii < 127)) + return QString(m_asciiToMorse[ascii - firstChar].morse); + else + return QString(); +} + +// Convert string to Morse code sequence consisting of . and - characters separated by spaces +QString Morse::toMorse(const QString &string) +{ + QStringList list; + for (int i = 0; i < string.size(); i++) + { + if (i != 0) + list.append(" "); + list.append(toMorse(string.at(i).toLatin1())); + } + return list.join(""); +} + +// Converts Morse code sequence using ASCII . and - to Unicode bullet and minus sign +// which are horizontally aligned, so look nicer in GUIs +QString Morse::toUnicode(const QString &morse) +{ + QString s = morse; + return s.replace(QChar('.'), QChar(0x2022)).replace(QChar('-'), QChar(0x2212)); +} + +// Converts a string to a unicode Morse sequence with extra space characters between +// dots and dashes to improve readability in GUIs +QString Morse::toSpacedUnicode(const QString &morse) +{ + QString temp = toUnicode(morse); + for (int i = 0; i < temp.size(); i+=2) + temp.insert(i, ' '); + return temp; +} + +// Converts a string to a unicode Morse sequence +QString Morse::toUnicodeMorse(const QString &string) +{ + QString ascii = toMorse(string); + return ascii.replace(QChar('.'), QChar(0x2022)).replace(QChar('-'), QChar(0x2212)); +} + +// Converts a string to a unicode Morse sequence with spacing between dots and dashes +QString Morse::toSpacedUnicodeMorse(const QString &string) +{ + QString temp = toUnicodeMorse(string); + for (int i = 0; i < temp.size(); i+=2) + temp.insert(i, ' '); + return temp; +} + +#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) + +// Converts a Morse sequence to an ASCII character. -1 if no mapping found. +int Morse::toASCII(const QString &morse) +{ + for (unsigned int i = 0; i < COUNT_OF(m_asciiToMorse); i++) + { + if (morse == m_asciiToMorse[i].morse) + return m_asciiToMorse[i].ascii; + } + return -1; +} + +// Converts a sequence of Morse code to a string. Unknown Morse codes are ignored. +QString Morse::toString(const QString &morse) +{ + QString string(""); + QStringList groups = morse.split(" "); + for (int i = 0; i < groups.size(); i++) + { + int c = Morse::toASCII(groups[i]); + if ((c != -1) && (groups[i] != "")) + string.append(QChar(c)); + } + return string; +} diff --git a/android/app/src/main/cpp/util/morse.h b/android/app/src/main/cpp/util/morse.h new file mode 100644 index 0000000..364579d --- /dev/null +++ b/android/app/src/main/cpp/util/morse.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_MORSE_H +#define INCLUDE_MORSE_H + +#include + +#include "export.h" + +// Morse (ITU) code utils for converting between strings and Morse code and vice-versa +class SDRBASE_API Morse +{ +public: + static QString toMorse(char asciiChar); + static QString toMorse(const QString &string); + static QString toUnicode(const QString &morse); + static QString toSpacedUnicode(const QString &morse); + static QString toUnicodeMorse(const QString &string); + static QString toSpacedUnicodeMorse(const QString &string); + static int toASCII(const QString &morse); + static QString toString(const QString &morse); + +private: + struct ASCIIToMorse { + char ascii; + const char *morse; + }; + + const static ASCIIToMorse m_asciiToMorse[]; +}; + +#endif // INCLUDE_MORSE_H diff --git a/android/app/src/main/cpp/util/movingaverage.h b/android/app/src/main/cpp/util/movingaverage.h new file mode 100644 index 0000000..c1849b3 --- /dev/null +++ b/android/app/src/main/cpp/util/movingaverage.h @@ -0,0 +1,137 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Vort // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// http://stackoverflow.com/questions/10990618/calculate-rolling-moving-average-in-c // +// // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// + +#ifndef _UTIL_MOVINGAVERAGE_H_ +#define _UTIL_MOVINGAVERAGE_H_ + +#include + +template +class MovingAverageUtil +{ + public: + MovingAverageUtil() + : m_num_samples(0), m_index(0), m_total(0) + { } + + void reset() + { + m_num_samples = 0; + m_index = 0; + m_total = 0; + } + + void operator()(T sample) + { + if (m_num_samples < N) // fill up + { + m_samples[m_num_samples++] = sample; + m_total += sample; + } + else // roll + { + T& oldest = m_samples[m_index]; + m_total += sample - oldest; + oldest = sample; + m_index = (m_index + 1) % N; + } + } + + double asDouble() const { return ((double)m_total) / N; } + float asFloat() const { return ((float)m_total) / N; } + operator T() const { return m_total / N; } + T instantAverage() const { return m_total / (m_num_samples == 0 ? 1 : m_num_samples); } + + private: + T m_samples[N]; + int m_num_samples; + unsigned int m_index; + Total m_total; +}; + + +template +class MovingAverageUtilVar +{ + public: + MovingAverageUtilVar(unsigned int size) + : m_num_samples(0), m_index(0), m_total(0) + { + m_samples.resize(size); + m_samplesSizeInvF = 1.0f / size; + m_samplesSizeInvD = 1.0 / size; + } + + void reset() + { + m_num_samples = 0; + m_index = 0; + m_total = 0; + } + + void resize(unsigned int size) + { + reset(); + m_samples.resize(size); + m_samplesSizeInvF = 1.0f / size; + m_samplesSizeInvD = 1.0 / size; + } + + unsigned int size() const + { + return m_samples.size(); + } + + void operator()(T sample) + { + if (m_num_samples < m_samples.size()) // fill up + { + m_samples[m_num_samples++] = sample; + m_total += sample; + } + else // roll + { + T& oldest = m_samples[m_index]; + m_total += sample - oldest; + oldest = sample; + m_index++; + if (m_index == m_samples.size()) + m_index = 0; + } + } + + double asDouble() const { return m_total * m_samplesSizeInvD; } + float asFloat() const { return m_total * m_samplesSizeInvF; } + operator T() const { return m_total / m_samples.size(); } + T instantAverage() const { return m_total / (m_num_samples == 0 ? 1 : m_num_samples); } + + private: + std::vector m_samples; + unsigned int m_num_samples; + unsigned int m_index; + Total m_total; + float m_samplesSizeInvF; + double m_samplesSizeInvD; +}; + + +#endif /* _UTIL_MOVINGAVERAGE_H_ */ diff --git a/android/app/src/main/cpp/util/movingaverage2d.h b/android/app/src/main/cpp/util/movingaverage2d.h new file mode 100644 index 0000000..8358c59 --- /dev/null +++ b/android/app/src/main/cpp/util/movingaverage2d.h @@ -0,0 +1,122 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _UTIL_MOVINGAVERAGE2D_H_ +#define _UTIL_MOVINGAVERAGE2D_H_ + +#include + +template +class MovingAverage2D +{ +public: + MovingAverage2D() : m_data(0), m_sum(0), m_dataSize(0), m_sumSize(0), m_width(0), m_depth(0), m_avgIndex(0) {} + + ~MovingAverage2D() + { + if (m_data) { + delete[] m_data; + } + + if (m_sum) { + delete[] m_sum; + } + } + + void resize(unsigned int width, unsigned int depth) + { + if (width*depth > m_dataSize) + { + m_dataSize = width*depth; + if (m_data) { + delete[] m_data; + } + m_data = new T[m_dataSize]; + } + + if (width > m_sumSize) + { + m_sumSize = width; + if (m_sum) { + delete[] m_sum; + } + m_sum = new T[m_sumSize]; + } + + m_width = width; + m_depth = depth; + + std::fill(m_data, m_data+(m_width*m_depth), 0.0); + std::fill(m_sum, m_sum+m_width, 0.0); + + m_avgIndex = 0; + } + + T storeAndGetAvg(T v, unsigned int index) + { + if (m_depth <= 1) + { + return v; + } + else if (index < m_width) + { + T first = m_data[m_avgIndex*m_width+index]; + m_sum[index] += (v - first); + m_data[m_avgIndex*m_width+index] = v; + return m_sum[index] / m_depth; + } + else + { + return 0; + } + } + + T storeAndGetSum(T v, unsigned int index) + { + if (m_depth == 1) + { + return v; + } + else if (index < m_width) + { + T first = m_data[m_avgIndex*m_width+index]; + m_sum[index] += (v - first); + m_data[m_avgIndex*m_width+index] = v; + return m_sum[index]; + } + else + { + return 0; + } + } + + void nextAverage() { + m_avgIndex = m_avgIndex == m_depth-1 ? 0 : m_avgIndex+1; + } + +private: + T *m_data; + T *m_sum; + unsigned int m_dataSize; + unsigned int m_sumSize; + unsigned int m_width; + unsigned int m_depth; + unsigned int m_avgIndex; +}; + + +#endif diff --git a/android/app/src/main/cpp/util/movingmaximum.h b/android/app/src/main/cpp/util/movingmaximum.h new file mode 100644 index 0000000..38a07ab --- /dev/null +++ b/android/app/src/main/cpp/util/movingmaximum.h @@ -0,0 +1,98 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_MOVINGMAXIMUM_H +#define INCLUDE_UTIL_MOVINGMAXIMUM_H + +#include +#include + +// Calculates moving maximum over a number of samples +template +class MovingMaximum +{ +public: + + MovingMaximum() : + m_samples(nullptr), + m_size(0) + { + reset(); + } + + ~MovingMaximum() + { + delete[] m_samples; + } + + void reset() + { + m_count = 0; + m_index = 0; + m_max = NAN; + } + + void setSize(int size) + { + delete[] m_samples; + m_samples = new T[size](); + m_size = size; + reset(); + } + + void operator()(T sample) + { + if (m_count < m_size) + { + m_samples[m_count++] = sample; + if (m_count == 1) { + m_max = sample; + } else { + m_max = std::max(m_max, sample); + } + } + else + { + T oldest = m_samples[m_index]; + m_samples[m_index] = sample; + m_index = (m_index + 1) % m_size; + m_max = std::max(m_max, sample); + if (oldest >= m_max) + { + // Find new maximum, that will be lower than the oldest sample + m_max = m_samples[0]; + for (unsigned int i = 1; i < m_size; i++) { + m_max = std::max(m_max, m_samples[i]); + } + } + } + } + + T getMaximum() const { + return m_max; + } + +private: + T *m_samples; + unsigned int m_size; // Max number of samples + unsigned int m_count; // Number of samples used + unsigned int m_index; // Current index + T m_max; +}; + +#endif /* INCLUDE_UTIL_MOVINGMAXIMUM_H */ + diff --git a/android/app/src/main/cpp/util/nasaglobalimagery.cpp b/android/app/src/main/cpp/util/nasaglobalimagery.cpp new file mode 100644 index 0000000..9385151 --- /dev/null +++ b/android/app/src/main/cpp/util/nasaglobalimagery.cpp @@ -0,0 +1,326 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "nasaglobalimagery.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +NASAGlobalImagery::NASAGlobalImagery() +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect(m_networkManager, &QNetworkAccessManager::finished, this, &NASAGlobalImagery::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("nasaglobalimagery"))) { + qDebug() << "Failed to create cache/nasaglobalimagery"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("nasaglobalimagery")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); +} + +NASAGlobalImagery::~NASAGlobalImagery() +{ + QObject::disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &NASAGlobalImagery::handleReply); + delete m_networkManager; +} + +void NASAGlobalImagery::getData() +{ + QUrl url(QString("https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/1.0.0/WMTSCapabilities.xml")); + m_networkManager->get(QNetworkRequest(url)); +} + +void NASAGlobalImagery::getMetaData() +{ + QUrl url(QString("https://worldview.earthdata.nasa.gov/config/wv.json")); + m_networkManager->get(QNetworkRequest(url)); +} + +void NASAGlobalImagery::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QString url = reply->url().toEncoded().constData(); + QByteArray bytes = reply->readAll(); + + if (url.endsWith(".xml")) { + handleXML(bytes); + } else if (url.endsWith(".svg")) { + handleSVG(url, bytes); + } else if (url.endsWith(".json")) { + handleJSON(bytes); + } else if (url.endsWith(".html")) { + handleHTML(url, bytes); + } else { + qDebug() << "NASAGlobalImagery::handleReply: unexpected URL: " << url; + } + } + else + { + qDebug() << "NASAGlobalImagery::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "NASAGlobalImagery::handleReply: reply is null"; + } +} + +void NASAGlobalImagery::handleXML(const QByteArray& bytes) +{ + QXmlStreamReader xmlReader(bytes); + QList dataSets; + + while (!xmlReader.atEnd() && !xmlReader.hasError()) + { + while (xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("Capabilities")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("Contents")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("Layer")) + { + QString identifier; + QString colorMap; + QList legends; + QString tileMatrixSet; + QStringList urls; + QString format; + QString defaultDateTime; + QStringList dates; + + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("Identifier")) + { + identifier = xmlReader.readElementText(); + } + else if (xmlReader.name() == QLatin1String("TileMatrixSetLink")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("TileMatrixSet")) + { + tileMatrixSet = xmlReader.readElementText(); + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else if (xmlReader.name() == QLatin1String("Style")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("LegendURL")) + { + Legend legend; + + legend.m_url = xmlReader.attributes().value("xlink:href").toString(); + legend.m_width = (int)xmlReader.attributes().value("width").toFloat(); + legend.m_height = (int)xmlReader.attributes().value("height").toFloat(); + //qDebug() << legend.m_url << legend.m_width << legend.m_height; + legends.append(legend); + xmlReader.skipCurrentElement(); + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else if (xmlReader.name() == QLatin1String("Metadata")) + { + colorMap = xmlReader.attributes().value("xlink:href").toString(); + xmlReader.skipCurrentElement(); + } + else if (xmlReader.name() == QLatin1String("Format")) + { + format = xmlReader.readElementText(); + } + else if (xmlReader.name() == QLatin1String("ResourceURL")) + { + QString url = xmlReader.attributes().value("template").toString(); + if (!url.isEmpty()) { + urls.append(url); + } + xmlReader.skipCurrentElement(); + } + else if (xmlReader.name() == QLatin1String("Dimension")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("Default")) + { + defaultDateTime = xmlReader.readElementText(); + } + else if (xmlReader.name() == QLatin1String("Value")) + { + dates.append(xmlReader.readElementText()); + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else + { + xmlReader.skipCurrentElement(); + } + } + + // Some layers are application/vnd.mapbox-vector-tile + if (!identifier.isEmpty() && !tileMatrixSet.isEmpty() && ((format == "image/png") || (format == "image/jpeg"))) + { + DataSet dataSet; + dataSet.m_identifier = identifier; + dataSet.m_legends = legends; + dataSet.m_tileMatrixSet = tileMatrixSet; + dataSet.m_format = format; + dataSet.m_defaultDateTime = defaultDateTime; + dataSet.m_dates = dates; + dataSets.append(dataSet); + + //qDebug() << "Adding layer" << identifier << colorMap << legends << tileMatrixSet; + } + + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else if (xmlReader.name() == QLatin1String("ServiceMetadataURL")) + { + // Empty? + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + // Ignore "Premature end of document." here even if ok + if (!xmlReader.atEnd() && xmlReader.hasError()) + { + qDebug() << "NASAGlobalImagery::handleReply: Error parsing XML: " << xmlReader.errorString(); + } + + emit dataUpdated(dataSets); +} + +void NASAGlobalImagery::downloadLegend(const Legend& legend) +{ + QUrl url(legend.m_url); + m_networkManager->get(QNetworkRequest(url)); +} + +void NASAGlobalImagery::downloadHTML(const QString& urlString) +{ + QUrl url(urlString); + m_networkManager->get(QNetworkRequest(url)); +} + +void NASAGlobalImagery::handleSVG(const QString& url, const QByteArray& bytes) +{ + emit legendAvailable(url, bytes); +} + +void NASAGlobalImagery::handleJSON(const QByteArray& bytes) +{ + MetaData metaData; + + QJsonDocument document = QJsonDocument::fromJson(bytes); + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("layers"))) + { + for (const auto& oRef : obj.value(QStringLiteral("layers")).toObject()) + { + Layer layer; + QJsonObject o = oRef.toObject(); + + if (o.contains(QStringLiteral("id"))) { + layer.m_identifier = o.value(QStringLiteral("id")).toString(); + } + if (o.contains(QStringLiteral("title"))) { + layer.m_title = o.value(QStringLiteral("title")).toString(); + } + if (o.contains(QStringLiteral("subtitle"))) { + layer.m_subtitle = o.value(QStringLiteral("subtitle")).toString(); + } + if (o.contains(QStringLiteral("description"))) { + layer.m_descriptionURL = "https://worldview.earthdata.nasa.gov/config/metadata/layers/" + o.value(QStringLiteral("description")).toString() + ".html"; + } + if (o.contains(QStringLiteral("startDate"))) { + layer.m_startDate = QDateTime::fromString(o.value(QStringLiteral("startDate")).toString(), Qt::ISODate); + } + if (o.contains(QStringLiteral("endDate"))) { + layer.m_endDate = QDateTime::fromString(o.value(QStringLiteral("endDate")).toString(), Qt::ISODate); + } + if (o.contains(QStringLiteral("ongoing"))) { + layer.m_ongoing = o.value(QStringLiteral("ongoing")).toBool(); + } + if (o.contains(QStringLiteral("layerPeriod"))) { + layer.m_layerPeriod = o.value(QStringLiteral("layerPeriod")).toString(); + } + if (o.contains(QStringLiteral("layergroup"))) { + layer.m_layerGroup = o.value(QStringLiteral("layergroup")).toString(); + } + if (!layer.m_identifier.isEmpty()) { + metaData.m_layers.insert(layer.m_identifier, layer); + } + } + } + } + + emit metaDataUpdated(metaData); +} + +void NASAGlobalImagery::handleHTML(const QString& url, const QByteArray& bytes) +{ + emit htmlAvailable(url, bytes); +} diff --git a/android/app/src/main/cpp/util/nasaglobalimagery.h b/android/app/src/main/cpp/util/nasaglobalimagery.h new file mode 100644 index 0000000..9408c5b --- /dev/null +++ b/android/app/src/main/cpp/util/nasaglobalimagery.h @@ -0,0 +1,98 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_NASAGLOBALIMAGERY_H +#define INCLUDE_NASAGLOBALIMAGERY_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// NASA GIBS (Global Imagery Browse Server) API (https://nasa-gibs.github.io/gibs-api-docs/) +// Gets details of available data sets for use on maps +// Also supports cached download of .svg legends +class SDRBASE_API NASAGlobalImagery : public QObject +{ + Q_OBJECT + +public: + + struct Legend { + QString m_url; // Typically to .svg file + int m_width; + int m_height; + }; + + struct DataSet { + QString m_identifier; + QList m_legends; + QString m_tileMatrixSet; + QString m_format; + QString m_defaultDateTime; + QStringList m_dates; + }; + + struct Layer { + QString m_identifier; + QString m_title; + QString m_subtitle; + QString m_descriptionURL; + QDateTime m_startDate; + QDateTime m_endDate; + bool m_ongoing; + QString m_layerPeriod; + QString m_layerGroup; + }; + + struct MetaData { + QHash m_layers; + }; + + NASAGlobalImagery(); + ~NASAGlobalImagery(); + + void getData(); + void getMetaData(); + void downloadLegend(const Legend& legend); + void downloadHTML(const QString& url); + +public slots: + void handleReply(QNetworkReply* reply); + +signals: + void dataUpdated(const QList& dataSets); // Emitted when paths to new data are available. + void metaDataUpdated(const MetaData& metaData); + void legendAvailable(const QString& url, const QByteArray data); + void htmlAvailable(const QString& url, const QByteArray data); + +private: + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + + void handleXML(const QByteArray& bytes); + void handleSVG(const QString& url, const QByteArray& bytes); + void handleJSON(const QByteArray& bytes); + void handleHTML(const QString& url, const QByteArray& bytes); + +}; + +#endif /* INCLUDE_NASAGLOBALIMAGERY_H */ diff --git a/android/app/src/main/cpp/util/navtex.cpp b/android/app/src/main/cpp/util/navtex.cpp new file mode 100644 index 0000000..85afaee --- /dev/null +++ b/android/app/src/main/cpp/util/navtex.cpp @@ -0,0 +1,727 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "navtex.h" + +// From https://en.wikipedia.org/wiki/List_of_Navtex_stations +const QList NavtexTransmitter::m_navtexTransmitters = { + {1, "Svalbard", 78.056944, 13.609722, {Schedule('A', 518000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {1, "Bodo", 67.266667, 14.383333, {NavtexTransmitter::Schedule('B', 518000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(12, 10), QTime(16, 10), QTime(20, 10)})}}, + {1, "Vardo", 70.370889, 31.097389, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)})}}, + {1, "Torshavn", 62.014944, -6.800056, {NavtexTransmitter::Schedule('D', 518000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(12, 30), QTime(16, 30), QTime(20, 30)})}}, + {1, "Niton", 50.586297, -1.254756, {NavtexTransmitter::Schedule('E', 518000, {QTime(0, 40), QTime(4, 40), QTime(8, 40), QTime(12, 40), QTime(16, 40), QTime(20, 40)}), + NavtexTransmitter::Schedule('K', 518000, {QTime(1, 40), QTime(5, 40), QTime(6, 40), QTime(13, 40), QTime(17, 40), QTime(21, 40)}), + NavtexTransmitter::Schedule('I', 490000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)})}}, // Have seen this broadcast at 9:10 + {1, "Talinn", 59.4644, 24.357294, {NavtexTransmitter::Schedule('F', 518000, {QTime(3, 20), QTime(7, 20), QTime(11, 20), QTime(15, 20), QTime(19, 20), QTime(23, 20)})}}, + {1, "Cullercoats", 55.0732, -1.463233, {NavtexTransmitter::Schedule('G', 518000, {QTime(1, 0), QTime(5, 0), QTime(9, 0), QTime(13, 0), QTime(17, 0), QTime(21, 0)}), + NavtexTransmitter::Schedule('U', 490000, {QTime(3, 20), QTime(7, 20), QTime(11, 20), QTime(15, 20), QTime(19, 20), QTime(23, 20)})}}, + {1, "Bjuroklubb", 64.461639, 21.591833, {NavtexTransmitter::Schedule('H', 518000, {QTime(1, 10), QTime(5, 10), QTime(9, 10), QTime(13, 10), QTime(17, 10), QTime(21, 10)})}}, + {1, "Grimeton", 57.103056, 12.385556, {NavtexTransmitter::Schedule('I', 518000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)})}}, + {1, "Gislovshammer", 55.488917, 14.314222, {NavtexTransmitter::Schedule('J', 518000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(17, 30), QTime(21, 30)})}}, + {1, "Rogaland", 58.658817, 5.603778, {NavtexTransmitter::Schedule('L', 518000, {QTime(1, 50), QTime(5, 50), QTime(9, 50), QTime(13, 50), QTime(17, 50), QTime(21, 50)})}}, + {1, "Jeloy", 59.435833, 10.589444, {NavtexTransmitter::Schedule('M', 518000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(18, 0), QTime(22, 0)})}}, + {1, "Orlandet", 63.661194, 9.5455, {NavtexTransmitter::Schedule('N', 518000, {QTime(2, 10), QTime(6, 10), QTime(10, 10), QTime(14, 10), QTime(18, 10), QTime(22, 10)})}}, + {1, "Portpatrick", 54.844044, -5.124478, {NavtexTransmitter::Schedule('O', 518000, {QTime(2, 20), QTime(6, 20), QTime(10, 20), QTime(14, 20), QTime(18, 20), QTime(22, 20)}), + NavtexTransmitter::Schedule('C', 490000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(16, 20), QTime(20, 20)})}}, + {1, "Netherlands Coastguard", 52.095128, 4.257975, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {1, "Malin Head", 55.363278, -7.33925, {NavtexTransmitter::Schedule('Q', 518000, {QTime(2, 40), QTime(6, 40), QTime(10, 40), QTime(14, 40), QTime(18, 40), QTime(22, 40)})}}, + {1, "Saudanes", 66.18625, -18.951867, {NavtexTransmitter::Schedule('R', 518000, {QTime(2, 50), QTime(6, 50), QTime(10, 50), QTime(14, 50), QTime(18, 50), QTime(22, 50)}), + NavtexTransmitter::Schedule('E', 490000, {QTime(0, 40), QTime(4, 40), QTime(8, 40), QTime(16, 40), QTime(20, 40)})}}, + {1, "Hamburg", 53.673333, 9.808611, {NavtexTransmitter::Schedule('S', 518000, {QTime(3, 0), QTime(7, 0), QTime(12, 0), QTime(15, 0), QTime(19, 0), QTime(23, 0)}), + NavtexTransmitter::Schedule('L', 490000, {QTime(1, 50), QTime(5, 50), QTime(9, 50), QTime(17, 50), QTime(21, 50)})}}, // Transmitter is at Pinneberg (used on wiki), but messages give location as Hamburg + {1, "Oostende", 51.182278, 2.806539, {NavtexTransmitter::Schedule('T', 518000, {QTime(3, 10), QTime(7, 10), QTime(11, 10), QTime(15, 10), QTime(19, 10), QTime(23, 10)}), + NavtexTransmitter::Schedule('V', 518000, {QTime(3, 30), QTime(7, 30), QTime(11, 30), QTime(15, 30), QTime(19, 30), QTime(23, 30)}), + NavtexTransmitter::Schedule('B', 490000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(16, 10), QTime(20, 10)})}}, + {1, "Valentia", 51.929756, -10.349028, {NavtexTransmitter::Schedule('W', 518000, {QTime(3, 40), QTime(7, 40), QTime(12, 40), QTime(15, 40), QTime(19, 40), QTime(23, 40)})}}, + {1, "Grindavik", 63.833208, -22.450786, {NavtexTransmitter::Schedule('X', 518000, {QTime(3, 50), QTime(7, 50), QTime(12, 50), QTime(15, 50), QTime(19, 50), QTime(23, 50)}), + NavtexTransmitter::Schedule('K', 490000, {QTime(1, 40), QTime(5, 40), QTime(9, 40), QTime(17, 40), QTime(21, 40)})}}, + + {2, "Cross Corsen", 48.476031, -5.053697, {NavtexTransmitter::Schedule('A', 518000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)}), + NavtexTransmitter::Schedule('E', 490000, {QTime(0, 40), QTime(4, 40), QTime(8, 40), QTime(12, 40), QTime(16, 40), QTime(20, 40)})}}, + {2, "Coruna", 43.367028, -8.451861, {NavtexTransmitter::Schedule('D', 518000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(12, 30), QTime(16, 30), QTime(20, 30)}), + NavtexTransmitter::Schedule('W', 490000, {QTime(3, 40), QTime(7, 40), QTime(11, 40), QTime(15, 40), QTime(17, 40), QTime(23, 40)})}}, + {2, "Horta", 38.529872, -28.628922, {NavtexTransmitter::Schedule('F', 518000, {QTime(0, 50), QTime(4, 50), QTime(8, 50), QTime(12, 50), QTime(16, 50), QTime(20, 50)}), + NavtexTransmitter::Schedule('J', 490000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(15, 30), QTime(21, 30)})}}, + {2, "Tarifa", 36.042, -5.556606, {NavtexTransmitter::Schedule('G', 518000, {QTime(1, 0), QTime(5, 0), QTime(9, 0), QTime(13, 0), QTime(17, 0), QTime(21, 0)})}}, + {2, "Las Palmas", 27.758522, -15.605361, {NavtexTransmitter::Schedule('I', 518000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)}), + NavtexTransmitter::Schedule('A', 490000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {2, "Casablanca", 33.6, -7.633333, {NavtexTransmitter::Schedule('M', 518000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(18, 0), QTime(22, 0)})}}, + {2, "Porto Santo", 33.066278, -16.355417, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {2, "Monsanto", 38.731611, -9.190611, {NavtexTransmitter::Schedule('R', 518000, {QTime(2, 50), QTime(6, 50), QTime(10, 50), QTime(14, 50), QTime(18, 50), QTime(22, 50)}), + NavtexTransmitter::Schedule('G', 490000, {QTime(1, 0), QTime(5, 0), QTime(9, 0), QTime(13, 0), QTime(15, 0), QTime(21, 0)})}}, + {2, "Ribeira de Vinha", 16.853228, -25.003197, {NavtexTransmitter::Schedule('U', 518000, {QTime(3, 20), QTime(7, 20), QTime(11, 20), QTime(15, 20), QTime(19, 20), QTime(23, 20)})}}, + {2, "Cabo La Nao", 38.723258, 0.161367, {NavtexTransmitter::Schedule('X', 518000, {QTime(3, 50), QTime(7, 520), QTime(11, 50), QTime(15, 50), QTime(19, 50), QTime(23, 50)}), + NavtexTransmitter::Schedule('M', 490000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(16, 0), QTime(22, 0)})}}, + {2, "Sao Vicente", 16.853228, -25.003197, {NavtexTransmitter::Schedule('P', 490000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(16, 30), QTime(22, 30)})}}, +// {2, "Niton", 50.586297, -1.254756, {NavtexTransmitter::Schedule('T', 490000, {QTime(3, 10), QTime(7, 10), QTime(11, 10), QTime(15, 10), QTime(17, 10), QTime(23, 10)})}}, + {2, "Tarifa", 36.042, -5.556606, {NavtexTransmitter::Schedule('T', 490000, {QTime(3, 10), QTime(7, 10), QTime(11, 10), QTime(15, 10), QTime(17, 10), QTime(23, 10)})}}, + + {3, "Novorossijsk", 44.599111, 37.951442, {NavtexTransmitter::Schedule('A', 518000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {3, "Algier", 36.733333, 3.18, {NavtexTransmitter::Schedule('B', 518000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(12, 10), QTime(16, 10), QTime(20, 10)})}}, + {3, "Odessa", 46.377611, 30.748222, {NavtexTransmitter::Schedule('C', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {3, "Istanbul", 41.066667, 28.95, {NavtexTransmitter::Schedule('D', 518000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(12, 30), QTime(16, 30), QTime(20, 30)}), + NavtexTransmitter::Schedule('B', 490000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(16, 10), QTime(20, 10)}), + NavtexTransmitter::Schedule('M', 4209500, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(16, 0), QTime(22, 0)})}}, + {3, "Samsun", 41.386667, 36.188333, {NavtexTransmitter::Schedule('E', 518000, {QTime(0, 40), QTime(4, 40), QTime(8, 40), QTime(12, 40), QTime(16, 40), QTime(20, 40)}), + NavtexTransmitter::Schedule('A', 490000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {3, "Antalya", 36.1525, 32.44, {NavtexTransmitter::Schedule('F', 518000, {QTime(0, 50), QTime(4, 50), QTime(8, 50), QTime(12, 50), QTime(16, 50), QTime(20, 50)}), + NavtexTransmitter::Schedule('D', 490000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(16, 30), QTime(20, 30)})}}, + {3, "Iraklio", 35.322861, 25.748986, {NavtexTransmitter::Schedule('H', 518000, {QTime(1, 10), QTime(5, 10), QTime(9, 10), QTime(13, 10), QTime(17, 10), QTime(21, 10)}), + NavtexTransmitter::Schedule('Q', 490000, {QTime(2, 40), QTime(6, 40), QTime(10, 40), QTime(14, 40), QTime(16, 40), QTime(22, 40)}), + NavtexTransmitter::Schedule('S', 4209500, {QTime(3, 0), QTime(7, 0), QTime(11, 0), QTime(15, 0), QTime(19, 0), QTime(3, 20)})}}, + {3, "Izmir", 38.275833, 26.2675, {NavtexTransmitter::Schedule('I', 518000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)}), + NavtexTransmitter::Schedule('C', 490000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(16, 20), QTime(20, 20)})}}, + {3, "Varna", 43.068056, 27.786111, {NavtexTransmitter::Schedule('J', 518000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(17, 30), QTime(21, 30)})}}, + {3, "Kerkyra", 39.607222, 19.890833, {NavtexTransmitter::Schedule('K', 518000, {QTime(1, 40), QTime(5, 40), QTime(9, 40), QTime(13, 40), QTime(17, 40), QTime(21, 40)}), + NavtexTransmitter::Schedule('P', 490000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(16, 30), QTime(22, 30)})}}, + {3, "Limnos", 39.906389, 25.181389, {NavtexTransmitter::Schedule('L', 518000, {QTime(1, 50), QTime(5, 50), QTime(9, 50), QTime(13, 50), QTime(17, 50), QTime(21, 50)}), + NavtexTransmitter::Schedule('R', 490000, {QTime(2, 50), QTime(6, 50), QTime(10, 50), QTime(14, 50), QTime(16, 50), QTime(22, 50)})}}, + {3, "Cyprus", 35.048278, 33.283628, {NavtexTransmitter::Schedule('M', 518000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(18, 0), QTime(22, 0)})}}, + {3, "Alexandria", 31.198089, 29.864494, {NavtexTransmitter::Schedule('N', 518000, {QTime(2, 10), QTime(6, 10), QTime(10, 10), QTime(14, 10), QTime(18, 10), QTime(22, 10)})}}, + {3, "Malta", 35.815211, 14.526911, {NavtexTransmitter::Schedule('O', 518000, {QTime(2, 20), QTime(6, 20), QTime(10, 20), QTime(14, 20), QTime(18, 20), QTime(22, 20)})}}, + {3, "Haifa", 32.827806, 34.969306, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {3, "Split", 43.181861, 16.422333, {NavtexTransmitter::Schedule('Q', 518000, {QTime(2, 40), QTime(6, 40), QTime(10, 40), QTime(14, 40), QTime(18, 40), QTime(22, 40)})}}, + {3, "La Maddalena", 41.222778, 9.398889, {NavtexTransmitter::Schedule('R', 518000, {QTime(2, 50), QTime(6, 50), QTime(10, 50), QTime(14, 50), QTime(18, 50), QTime(22, 50)}), + NavtexTransmitter::Schedule('I', 490000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)})}}, + {3, "Kelibia", 36.801819, 11.037372, {NavtexTransmitter::Schedule('T', 518000, {QTime(3, 10), QTime(7, 10), QTime(12, 10), QTime(15, 10), QTime(19, 10), QTime(23, 10)})}}, + {3, "Mondolfo", 43.747778, 13.141667, {NavtexTransmitter::Schedule('U', 518000, {QTime(3, 20), QTime(7, 20), QTime(12, 20), QTime(15, 20), QTime(19, 20), QTime(23, 20)}), + NavtexTransmitter::Schedule('E', 490000, {QTime(0, 40), QTime(4, 40), QTime(8, 40), QTime(12, 40), QTime(16, 40), QTime(20, 40)})}}, + {3, "Sellia Marina", 38.873056, 16.719722, {NavtexTransmitter::Schedule('V', 518000, {QTime(3, 30), QTime(7, 30), QTime(12, 30), QTime(15, 30), QTime(19, 30), QTime(23, 30)}), + NavtexTransmitter::Schedule('W', 490000, {QTime(3, 40), QTime(7, 40), QTime(11, 40), QTime(15, 40), QTime(17, 40), QTime(23, 40)})}}, + {3, "Cross La Garde", 43.104306, 5.991389, {NavtexTransmitter::Schedule('W', 518000, {QTime(3, 40), QTime(7, 40), QTime(12, 40), QTime(15, 40), QTime(19, 40), QTime(23, 40)}), + NavtexTransmitter::Schedule('S', 490000, {QTime(3, 0), QTime(7, 0), QTime(11, 0), QTime(15, 0), QTime(19, 0), QTime(3, 20)})}}, + {3, "Cabo de la Nao", 38.723258, 0.161367, {NavtexTransmitter::Schedule('X', 518000, {QTime(3, 50), QTime(7, 50), QTime(12, 50), QTime(15, 50), QTime(19, 50), QTime(23, 50)}), + NavtexTransmitter::Schedule('M', 490000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(16, 0), QTime(22, 0)})}}, + + {4, "Miami", 25.626225, -80.383411, {NavtexTransmitter::Schedule('A', 518000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {4, "Bermuda Harbour", 32.380389, -64.682778, {NavtexTransmitter::Schedule('B', 518000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(12, 10), QTime(16, 10), QTime(20, 10)})}}, + {4, "Riviere-au-Renard", 50.195, -66.109889, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)}), + NavtexTransmitter::Schedule('D', 490000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(16, 30), QTime(20, 30)})}}, + {4, "Boston", 41.709833, -70.498353, {NavtexTransmitter::Schedule('F', 518000, {QTime(0, 50), QTime(4, 20), QTime(8, 50), QTime(12, 50), QTime(16, 50), QTime(20, 50)})}}, + {4, "New Orleans", 29.884625, -89.945611, {NavtexTransmitter::Schedule('G', 518000, {QTime(1, 0), QTime(5, 0), QTime(9, 0), QTime(13, 0), QTime(17, 0), QTime(21, 0)}), + NavtexTransmitter::Schedule('G', 4209500, {QTime(3, 0), QTime(7, 0), QTime(11, 0), QTime(15, 0), QTime(19, 0), QTime(23, 0)})}}, + {4, "Wiarton", 44.937111, -81.233467, {NavtexTransmitter::Schedule('H', 518000, {QTime(1, 10), QTime(5, 10), QTime(9, 10), QTime(13, 10), QTime(17, 10), QTime(21, 10)})}}, + {4, "Curacao", 12.173197, -68.864919, {NavtexTransmitter::Schedule('H', 518000, {QTime(1, 10), QTime(5, 10), QTime(9, 10), QTime(13, 10), QTime(17, 10), QTime(21, 10)})}}, // Duplicate Id + {4, "Portsmouth", 36.726342, -76.007894, {NavtexTransmitter::Schedule('N', 518000, {QTime(2, 10), QTime(6, 10), QTime(10, 10), QTime(14, 10), QTime(18, 10), QTime(22, 10)})}}, + {4, "St. John's", 47.611111, -52.666944, {NavtexTransmitter::Schedule('O', 518000, {QTime(2, 20), QTime(6, 20), QTime(10, 20), QTime(14, 20), QTime(18, 20), QTime(22, 20)})}}, + {4, "Thunder Bay", 48.563514, -88.656311, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {4, "Sydney", 46.185556, -59.893611, {NavtexTransmitter::Schedule('Q', 518000, {QTime(2, 40), QTime(6, 40), QTime(10, 40), QTime(14, 40), QTime(18, 40), QTime(22, 40)}), + NavtexTransmitter::Schedule('J', 490000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(15, 30), QTime(21, 30)})}}, + + {4, "Isabela", 18.466683, -67.071819, {NavtexTransmitter::Schedule('R', 518000, {QTime(2, 50), QTime(6, 50), QTime(10, 50), QTime(14, 50), QTime(18, 50), QTime(22, 50)})}}, + {4, "Iqaluit", 63.731389, -68.543167, {NavtexTransmitter::Schedule('T', 518000, {QTime(3, 10), QTime(7, 10), QTime(11, 10), QTime(15, 10), QTime(19, 10), QTime(23, 10)}), + NavtexTransmitter::Schedule('S', 490000, {QTime(3, 0), QTime(7, 0), QTime(11, 0), QTime(15, 0), QTime(19, 0), QTime(3, 20)})}}, + {4, "Saint John", 43.744256, -66.121786, {NavtexTransmitter::Schedule('U', 518000, {QTime(3, 20), QTime(7, 20), QTime(11, 20), QTime(15, 20), QTime(19, 20), QTime(23, 20)}), + NavtexTransmitter::Schedule('V', 490000, {QTime(3, 30), QTime(7, 30), QTime(11, 30), QTime(15, 30), QTime(19, 30), QTime(23, 30)})}}, + {4, "Kook Island", 64.067017, -52.012611, {NavtexTransmitter::Schedule('W', 518000, {QTime(3, 40), QTime(7, 40), QTime(12, 40), QTime(15, 40), QTime(19, 40), QTime(23, 40)})}}, + {4, "Labrador", 53.708611, -57.021667, {NavtexTransmitter::Schedule('X', 518000, {QTime(3, 50), QTime(7, 50), QTime(12, 50), QTime(15, 50), QTime(19, 50), QTime(23, 50)})}}, + + {6, "La Paloma", -34.666667, -54.15, {NavtexTransmitter::Schedule('F', 518000, {QTime(0, 50), QTime(4, 50), QTime(8, 50), QTime(12, 50), QTime(16, 50), QTime(20, 50)})}}, + {6, "Ushuaia", -54.8, -68.3, {NavtexTransmitter::Schedule('M', 518000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(18, 0), QTime(22, 0)})}}, + {6, "Rio Gallegos", -51.616667, -69.216667, {NavtexTransmitter::Schedule('N', 518000, {QTime(2, 10), QTime(6, 10), QTime(10, 10), QTime(14, 10), QTime(18, 10), QTime(22, 10)})}}, + {6, "Comodoro Rivadavia", -45.85, -67.416667, {NavtexTransmitter::Schedule('O', 518000, {QTime(2, 20), QTime(6, 20), QTime(10, 20), QTime(14, 20), QTime(18, 20), QTime(22, 20)})}}, + {6, "Bahía Blanca", -38.716667, -62.1, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {6, "Mar del Plata", -38.05, -57.533333, {NavtexTransmitter::Schedule('Q', 518000, {QTime(2, 40), QTime(6, 40), QTime(10, 40), QTime(14, 40), QTime(18, 40), QTime(22, 40)})}}, + {6, "Buenos Aires", -34.6, -58.366667, {NavtexTransmitter::Schedule('R', 518000, {QTime(2, 50), QTime(6, 50), QTime(10, 50), QTime(14, 50), QTime(18, 50), QTime(22, 50)})}}, + + {7, "Walvis Bay", -23.05665, 14.624333, {NavtexTransmitter::Schedule('B', 518000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(12, 10), QTime(16, 10), QTime(20, 10)})}}, + {7, "Cape Town", -33.685128, 18.712961, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)})}}, + {7, "Port Elizabeth", -34.036722, 25.555833, {NavtexTransmitter::Schedule('I', 518000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)})}}, + {7, "Durban", -29.804833, 30.815633, {NavtexTransmitter::Schedule('O', 518000, {QTime(2, 20), QTime(6, 20), QTime(10, 20), QTime(14, 20), QTime(18, 20), QTime(22, 20)})}}, + + {8, "Mauritius", -20.167089, 57.478161, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)})}}, + {8, "Bombay", 19.083239, 72.834033, {NavtexTransmitter::Schedule('G', 518000, {QTime(1, 0), QTime(5, 0), QTime(9, 0), QTime(13, 0), QTime(17, 0), QTime(21, 0)})}}, + {8, "Madras", 13.082778, 80.287222, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + + {9, "Bushehr", 28.962225, 50.822794, {NavtexTransmitter::Schedule('A', 518000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {9, "Hamala", 26.157167, 50.47665, {NavtexTransmitter::Schedule('B', 518000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(12, 10), QTime(16, 10), QTime(20, 10)})}}, + {9, "Bandar Abbas", 27.161022, 56.225378, {NavtexTransmitter::Schedule('F', 518000, {QTime(0, 50), QTime(4, 50), QTime(8, 50), QTime(12, 50), QTime(16, 50), QTime(20, 50)})}}, + {9, "Jeddah", 21.342222, 39.155833, {NavtexTransmitter::Schedule('H', 518000, {QTime(1, 10), QTime(5, 10), QTime(9, 10), QTime(13, 10), QTime(17, 10), QTime(21, 10)})}}, + {9, "Muscat", 23.6, 58.5, {NavtexTransmitter::Schedule('M', 518000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(18, 0), QTime(22, 0)})}}, + {9, "Karachi", 24.851944, 67.0425, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {9, "Quseir", 26.110889, 34.280083, {NavtexTransmitter::Schedule('V', 518000, {QTime(3, 30), QTime(7, 30), QTime(11, 30), QTime(15, 30), QTime(19, 30), QTime(23, 30)})}}, + {9, "Serapeum ", 30.470311, 32.36675, {NavtexTransmitter::Schedule('X', 518000, {QTime(3, 50), QTime(7, 50), QTime(12, 50), QTime(15, 50), QTime(19, 50), QTime(23, 50)})}}, + + {11, "Jayapura", -2.516667, 140.716667, {NavtexTransmitter::Schedule('A', 518000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {11, "Ambon", -3.7, 128.2, {NavtexTransmitter::Schedule('B', 518000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(12, 10), QTime(16, 10), QTime(20, 10)})}}, + {11, "Singapore", 1.333333, 103.7, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)})}}, + {11, "Makassar", -5.1, 119.433333, {NavtexTransmitter::Schedule('D', 518000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(12, 30), QTime(16, 30), QTime(20, 30)})}}, + {11, "Jakarta", -6.116667, 106.866667, {NavtexTransmitter::Schedule('E', 518000, {QTime(0, 40), QTime(4, 40), QTime(8, 40), QTime(12, 40), QTime(16, 40), QTime(20, 40)})}}, + {11, "Bangkok", 13.024444, 100.019733, {NavtexTransmitter::Schedule('F', 518000, {QTime(0, 50), QTime(4, 50), QTime(8, 50), QTime(12, 50), QTime(16, 50), QTime(20, 50)})}}, + {11, "Naha", 26.15, 127.766667, {NavtexTransmitter::Schedule('G', 518000, {QTime(1, 0), QTime(5, 0), QTime(9, 0), QTime(13, 0), QTime(17, 0), QTime(21, 0)})}}, + {11, "Moji", 33.95, 130.966667, {NavtexTransmitter::Schedule('H', 518000, {QTime(1, 10), QTime(5, 10), QTime(9, 10), QTime(13, 10), QTime(17, 10), QTime(21, 10)})}}, + {11, "Puerto Princesa", 9.733333, 118.716667, {NavtexTransmitter::Schedule('I', 518000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)})}}, + {11, "Yokohama", 35.433333, 139.633333, {NavtexTransmitter::Schedule('I', 518000, {QTime(1, 20), QTime(5, 20), QTime(9, 20), QTime(13, 20), QTime(17, 20), QTime(21, 20)})}}, // Duplicate Id + {11, "Manila", 14.583333, 121.05, {NavtexTransmitter::Schedule('J', 518000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(17, 30), QTime(21, 30)})}}, + {11, "Otaru", 43.2, 141, {NavtexTransmitter::Schedule('J', 518000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(17, 30), QTime(21, 30)})}}, // Duplicate Id + {11, "Davao City", 7.066667, 125.6, {NavtexTransmitter::Schedule('K', 518000, {QTime(1, 40), QTime(5, 40), QTime(9, 40), QTime(13, 40), QTime(17, 40), QTime(21, 40)})}}, + {11, "Kushiro", 42.983333, 144.383333, {NavtexTransmitter::Schedule('K', 518000, {QTime(1, 40), QTime(5, 40), QTime(9, 40), QTime(13, 40), QTime(17, 40), QTime(21, 40)})}}, // Duplicate Id + {11, "Hongkong", 22.209167, 114.256111, {NavtexTransmitter::Schedule('L', 518000, {QTime(1, 50), QTime(5, 50), QTime(9, 50), QTime(13, 50), QTime(17, 50), QTime(21, 50)})}}, + {11, "Sanya", 18.232222, 109.495833, {NavtexTransmitter::Schedule('M', 518000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(18, 0), QTime(22, 0)})}}, + {11, "Guangzhou", 23.15, 113.483333, {NavtexTransmitter::Schedule('N', 518000, {QTime(2, 10), QTime(6, 10), QTime(10, 10), QTime(14, 10), QTime(18, 10), QTime(22, 10)})}}, + {11, "Fuzhou", 26.028544, 119.305444, {NavtexTransmitter::Schedule('O', 518000, {QTime(2, 20), QTime(6, 20), QTime(10, 20), QTime(14, 20), QTime(18, 20), QTime(22, 20)})}}, + {11, "Da Nang", 16.083333, 108.233333, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, + {11, "Chilung", 25.15, 121.733333, {NavtexTransmitter::Schedule('P', 518000, {QTime(2, 30), QTime(6, 30), QTime(10, 30), QTime(14, 30), QTime(18, 30), QTime(22, 30)})}}, // Duplicate Id + {11, "Shanghai", 31.108889, 121.544167, {NavtexTransmitter::Schedule('Q', 518000, {QTime(2, 40), QTime(6, 40), QTime(10, 40), QTime(14, 40), QTime(18, 40), QTime(22, 40)})}}, + {11, "Dalian", 38.845244, 121.518056, {NavtexTransmitter::Schedule('R', 518000, {QTime(2, 50), QTime(6, 50), QTime(10, 50), QTime(14, 50), QTime(18, 50), QTime(22, 50)})}}, + {11, "Sandakan", 5.895886, 118.00305, {NavtexTransmitter::Schedule('S', 518000, {QTime(3, 0), QTime(7, 0), QTime(12, 0), QTime(15, 0), QTime(19, 0), QTime(23, 0)})}}, + {11, "Miri", 4.438, 114.020889, {NavtexTransmitter::Schedule('T', 518000, {QTime(3, 10), QTime(7, 10), QTime(11, 10), QTime(15, 10), QTime(19, 10), QTime(23, 10)})}}, + {11, "Penang", 5.425, 100.403056, {NavtexTransmitter::Schedule('U', 518000, {QTime(3, 20), QTime(7, 20), QTime(11, 20), QTime(15, 20), QTime(19, 20), QTime(23, 20)})}}, + {11, "Guam", 13.47445, 144.844389, {NavtexTransmitter::Schedule('V', 518000, {QTime(3, 30), QTime(7, 30), QTime(11, 30), QTime(15, 30), QTime(19, 30), QTime(23, 30)})}}, + {11, "Jukbyeon", 37.05, 129.416667, {NavtexTransmitter::Schedule('V', 518000, {QTime(3, 30), QTime(7, 30), QTime(11, 30), QTime(15, 30), QTime(19, 30), QTime(23, 30)}), // Duplicate Id + NavtexTransmitter::Schedule('J', 490000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(15, 30), QTime(21, 30)})}}, + + {11, "Byeonsan", 35.6, 126.483333, {NavtexTransmitter::Schedule('W', 518000, {QTime(3, 40), QTime(7, 40), QTime(12, 40), QTime(15, 40), QTime(19, 40), QTime(23, 40)}), + NavtexTransmitter::Schedule('K', 490000, {QTime(1, 40), QTime(5, 40), QTime(9, 40), QTime(17, 40), QTime(21, 40)})}}, + {11, "Ho-Chi-Minh City", 10.703317, 106.729139, {NavtexTransmitter::Schedule('X', 518000, {QTime(3, 50), QTime(7, 50), QTime(12, 50), QTime(15, 50), QTime(19, 50), QTime(23, 50)})}}, + + {12, "San Francisco", 37.925739, -122.734056, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)})}}, + {12, "Prince Rupert", 54.298519, -130.417669, {NavtexTransmitter::Schedule('D', 518000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(12, 30), QTime(16, 30), QTime(20, 30)})}}, + {12, "Tofino", 48.925478, -125.540306, {NavtexTransmitter::Schedule('H', 518000, {QTime(1, 10), QTime(5, 10), QTime(9, 10), QTime(13, 10), QTime(17, 10), QTime(21, 10)})}}, + {12, "Kodiak", 57.781606, -152.537583, {NavtexTransmitter::Schedule('J', 518000, {QTime(1, 30), QTime(5, 30), QTime(9, 30), QTime(13, 30), QTime(17, 30), QTime(21, 30)}), + NavtexTransmitter::Schedule('X', 518000, {QTime(3, 50), QTime(7, 50), QTime(12, 50), QTime(15, 50), QTime(19, 50), QTime(23, 50)})}}, + {12, "Ayora", -0.75, -90.316667, {NavtexTransmitter::Schedule('L', 518000, {QTime(1, 50), QTime(5, 50), QTime(9, 50), QTime(13, 50), QTime(17, 50), QTime(21, 50)}), + NavtexTransmitter::Schedule('A', 490000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {12, "Guayaquil", -2.283333, -80.016667, {NavtexTransmitter::Schedule('M', 518000, {QTime(2, 0), QTime(6, 0), QTime(10, 0), QTime(14, 0), QTime(18, 0), QTime(22, 0)})}}, + {12, "Honolulu", 21.437019, -158.143239, {NavtexTransmitter::Schedule('O', 518000, {QTime(2, 20), QTime(6, 20), QTime(10, 20), QTime(14, 20), QTime(18, 20), QTime(22, 20)})}}, + {12, "Cambria", 35.524297, -121.061922, {NavtexTransmitter::Schedule('Q', 518000, {QTime(2, 40), QTime(6, 40), QTime(10, 40), QTime(14, 40), QTime(18, 40), QTime(22, 40)})}}, + {12, "Astoria", 46.203989, -123.955639, {NavtexTransmitter::Schedule('W', 518000, {QTime(3, 40), QTime(7, 40), QTime(12, 40), QTime(15, 40), QTime(19, 40), QTime(23, 40)})}}, + + {13, "Vladivostok", 43.381472, 131.899861, {NavtexTransmitter::Schedule('A', 518000, {QTime(0, 0), QTime(4, 0), QTime(8, 0), QTime(12, 0), QTime(16, 0), QTime(20, 0)})}}, + {13, "Kholmsk", 47.023556, 142.045056, {NavtexTransmitter::Schedule('B', 518000, {QTime(0, 10), QTime(4, 10), QTime(8, 10), QTime(12, 10), QTime(16, 10), QTime(20, 10)})}}, + {13, "Murmansk", 68.865803, 33.070761, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)})}}, + {13, "Petropavlosk", 53.247778, 158.419472, {NavtexTransmitter::Schedule('C', 518000, {QTime(0, 20), QTime(4, 20), QTime(8, 20), QTime(12, 20), QTime(16, 20), QTime(20, 20)})}}, // Duplicate + {13, "Magadan", 59.683333, 150.15, {NavtexTransmitter::Schedule('D', 518000, {QTime(0, 30), QTime(4, 30), QTime(8, 30), QTime(12, 30), QTime(16, 30), QTime(20, 30)})}}, + {13, "Archangelsk", 64.556278, 40.550028, {NavtexTransmitter::Schedule('F', 518000, {QTime(0, 50), QTime(4, 50), QTime(8, 50), QTime(12, 50), QTime(16, 50), QTime(20, 50)})}}, + {13, "Okhotsk", 59.366667, 143.2, {NavtexTransmitter::Schedule('G', 518000, {QTime(1, 0), QTime(5, 0), QTime(9, 0), QTime(13, 0), QTime(17, 0), QTime(21, 0)})}}, + {13, "Astrakhan", 46.296694, 47.997778, {NavtexTransmitter::Schedule('W', 518000, {QTime(3, 40), QTime(7, 40), QTime(12, 40), QTime(15, 40), QTime(19, 40), QTime(23, 40)})}}, + + {15, "Antofagasta", -23.491333, -70.424778, {NavtexTransmitter::Schedule('A', 518000, {QTime(4, 0), QTime(12, 0), QTime(20, 0)})}}, + {15, "Valparaíso", -32.802222, -71.485, {NavtexTransmitter::Schedule('B', 518000, {QTime(4, 10), QTime(12, 10), QTime(20, 10)})}}, + {15, "Talcahuano", -36.715056, -73.108, {NavtexTransmitter::Schedule('C', 518000, {QTime(4, 20), QTime(12, 20), QTime(20, 20)})}}, + {15, "Puerto Montt", -41.489983, -72.957744, {NavtexTransmitter::Schedule('D', 518000, {QTime(4, 30), QTime(12, 30), QTime(20, 30)})}}, + {15, "Punta Arenas", -52.948111, -71.056944, {NavtexTransmitter::Schedule('E', 518000, {QTime(4, 40), QTime(12, 40), QTime(20, 40)})}}, + {15, "Easter Island", -27.15, -109.416667, {NavtexTransmitter::Schedule('F', 518000, {QTime(4, 50), QTime(12, 50), QTime(20, 50)})}}, + + {16, "Paita", -5.083333, -81.116667, {NavtexTransmitter::Schedule('S', 518000, {QTime(3, 0), QTime(7, 0), QTime(12, 0), QTime(15, 0), QTime(19, 0), QTime(23, 0)})}}, + {16, "Callao", -12.5, -77.15, {NavtexTransmitter::Schedule('U', 518000, {QTime(3, 20), QTime(7, 20), QTime(11, 20), QTime(15, 20), QTime(19, 20), QTime(23, 20)})}}, + {16, "Mollendo", -17.016667, -72.016667, {NavtexTransmitter::Schedule('W', 518000, {QTime(3, 40), QTime(7, 40), QTime(12, 40), QTime(15, 40), QTime(19, 40), QTime(23, 40)})}}, + +}; + +const QMap NavtexMessage::m_types = { + {"A", "Navigational warning"}, + {"B", "Meteorological warning"}, + {"C", "Ice reports"}, + {"D", "Search and rescue"}, + {"E", "Meteorological forecasts"}, + {"F", "Pilot service messages"}, + {"G", "AIS"}, + {"H", "LORAN"}, + {"J", "SATNAV"}, + {"K", "Navaid messages"}, + {"L", "Navigational warning"}, + {"T", "Test transmissions"}, + {"X", "Special services"}, + {"Y", "Special services"}, + {"Z", "No message"} +}; + +const NavtexTransmitter* NavtexTransmitter::getTransmitter(QTime time, int area, qint64 frequency) +{ + for (const auto& transmitter : NavtexTransmitter::m_navtexTransmitters) + { + if (transmitter.m_area == area) + { + for (const auto& schedule : transmitter.m_schedules) + { + if (schedule.m_frequency == frequency) + { + for (const auto& txStartTime : schedule.m_times) + { + // Transmitters have 10 minute windows for transmission + int secs = txStartTime.secsTo(time); + if ((secs >= 0) && (secs < 10*60)) { + return &transmitter; + } + } + } + } + } + } + return nullptr; +} + +NavtexMessage::NavtexMessage(QDateTime dateTime, const QString& stationId, const QString& typeId, const QString& id, const QString& message) : + m_stationId(stationId), + m_typeId(typeId), + m_id(id), + m_message(message), + m_dateTime(dateTime), + m_valid(true) +{ +} + +NavtexMessage::NavtexMessage(const QString& text) +{ + m_dateTime = QDateTime::currentDateTime(); + QRegularExpression re("[Z*][C*][Z*][C*][ *]([A-Z])([A-Z])(\\d\\d)((.|\n|\r)*)[N*][N*][N*][N*]"); + + QRegularExpressionMatch match = re.match(text); + if (match.hasMatch()) + { + m_stationId = match.captured(1); + m_typeId = match.captured(2); + m_id = match.captured(3); + m_message = match.captured(4).trimmed(); + m_valid = true; + } + else + { + m_message = text; + m_valid = false; + } +} + +QString NavtexMessage::getStation(int area, qint64 frequency) const +{ + for (const auto& transmitter : NavtexTransmitter::m_navtexTransmitters) + { + if (transmitter.m_area == area) + { + for (const auto& schedule : transmitter.m_schedules) + { + if ((schedule.m_id == m_stationId) && (schedule.m_frequency == frequency)) { + return transmitter.m_station; + } + } + } + + } + return ""; +} + +QString NavtexMessage::getType() const +{ + if (m_valid && m_types.contains(m_typeId)) { + return m_types.value(m_typeId); + } + return ""; +} + +void SitorBDecoder::init() +{ + m_state = PHASING; + m_idx = 0; + m_figureSet = false; + m_errors = 0; +} + +// In: +// Received 7-bit CCIR476 sequence +// Returns: +// Decoded ASCII character +// ETX end of text +// '*' both chars invalid +// -1 no character available yet +signed char SitorBDecoder::decode(signed char c) +{ + signed char ret = -1; + + //qDebug() << "In: " << printable(ccir476Decode(c)); + + switch (m_state) + { + case PHASING: + // Wait until we get a valid non-phasing character + if ((c != PHASING_1) && (c != PHASING_2) && (ccir476Decode(c) != -1)) + { + m_buf[m_idx++] = c; + m_state = FILL_RX; + } + break; + + case FILL_DX: + // Fill up buffer + m_buf[m_idx++] = c; + if (m_idx == BUFFER_SIZE) + { + m_state = RX; + m_idx = 0; + } + else + { + m_state = FILL_RX; + } + break; + + case FILL_RX: + // Should be phasing 1 + if (c != PHASING_1) { + m_errors++; + } + m_state = FILL_DX; + break; + + case RX: + { + // Try to decode a character + signed char dx = ccir476Decode(m_buf[m_idx]); + signed char rx = ccir476Decode(c); + signed char a; + + // Idle alpha (phasing 1) in both dx and rx means end of signal + if ((dx == '<') && (rx == '<')) + { + a = 0x2; // ETX - End of text + } + else if (dx != -1) + { + a = dx; // First received character has no detectable error + if ((dx != rx) && !((dx == '<') && (rx == '>')) && !((dx == '>') && (rx == '<'))) { + m_errors++; + } + } + else if (rx != -1) + { + a = rx; // Second received character has no detectable error + m_errors++; + } + else + { + a = '*'; // Both received characters have errors + m_errors += 2; + } + if (a == 0xf) { + m_figureSet = false; + } else if (a == 0xe) { + m_figureSet = true; + } else { + ret = a; + } + m_state = DX; + } + break; + + case DX: + // Save received character in buffer + m_buf[m_idx] = c; + m_idx = (m_idx + 1) % BUFFER_SIZE; + m_state = RX; + break; + } + + return ret; +} + +QString SitorBDecoder::printable(signed char c) +{ + if (c == -1) { + return "Unknown"; + } else if (c == 0x2) { + return "End of transmission"; + } else if (c == 0xf) { + return "Letter"; + } else if (c == 0xe) { + return "Figure"; + } else if (c == 0x5) { + return "Cross"; + } else if (c == 0x7) { + return "Bell"; + } else { + return QString("%1").arg((char)c); + } +} + +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.476-5-199510-I!!PDF-E.pdf - Table 1 + +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.625-4-201203-I!!PDF-E.pdf + +const signed char SitorBDecoder::m_ccir476LetterSetDecode[128] = { + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 0x0d, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 'T', + -1, + -1, + -1, + 0x0a, + -1, + ' ', + 'V', + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 'B', + -1, + -1, + -1, + -1, + -1, + 0x0f, + 'X', + -1, + -1, + -1, + -1, + '>', + -1, + 'E', + 0x0e, + -1, + -1, + 'U', + 'Q', + -1, + 'K', + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 'O', + -1, + -1, + -1, + 'H', + -1, + 'N', + 'M', + -1, + -1, + -1, + -1, + 'L', + -1, + 'R', + 'G', + -1, + -1, + 'I', + 'P', + -1, + 'C', + -1, + -1, + -1, + -1, + -1, + -1, + 'Z', + -1, + 'D', + -1, + -1, + -1, + 'S', + 'Y', + -1, + 'F', + -1, + -1, + -1, + -1, + 'A', + 'W', + -1, + 'J', + -1, + -1, + -1, + '<', + -1, + -1, + -1, + -1, + -1, + -1, + -1, +}; + +const signed char SitorBDecoder::m_ccir476FigureSetDecode[128] = { + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 0x0d, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + '5', + -1, + -1, + -1, + 0x0a, + -1, + ' ', + '=', + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + '?', + -1, + -1, + -1, + -1, + -1, + 0x0f, + '/', + -1, + -1, + -1, + -1, + '>', + -1, + '3', + 0x0e, + -1, + -1, + '7', + '1', + -1, + '(', + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + '9', + -1, + -1, + -1, + -93, + -1, + ',', + '.', + -1, + -1, + -1, + -1, + ')', + -1, + '4', + '&', + -1, + -1, + '8', + '0', + -1, + ':', + -1, + -1, + -1, + -1, + -1, + -1, + '+', + -1, + 0x05, + -1, + -1, + -1, + '\'', + '6', + -1, + '!', + -1, + -1, + -1, + -1, + '-', + '2', + -1, + 0x07, + -1, + -1, + -1, + '<', + -1, + -1, + -1, + -1, + -1, + -1, + -1, +}; + +signed char SitorBDecoder::ccir476Decode(signed char c) +{ + if (m_figureSet) { + return m_ccir476FigureSetDecode[(int)c]; + } else { + return m_ccir476LetterSetDecode[(int)c]; + } +} + diff --git a/android/app/src/main/cpp/util/navtex.h b/android/app/src/main/cpp/util/navtex.h new file mode 100644 index 0000000..ae243e3 --- /dev/null +++ b/android/app/src/main/cpp/util/navtex.h @@ -0,0 +1,111 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_NAVTEX_H +#define INCLUDE_UTIL_NAVTEX_H + +#include +#include +#include + +#include "export.h" + +class SDRBASE_API NavtexTransmitter { + +public: + + struct Schedule { + char m_id; + qint64 m_frequency; + QList m_times; + Schedule(char id, qint64 frequency) : + m_id(id), + m_frequency(frequency) + { + } + Schedule(char id, qint64 frequency, QList times) : + m_id(id), + m_frequency(frequency), + m_times(times) + { + } + }; + + int m_area; + QString m_station; + float m_latitude; + float m_longitude; + QList m_schedules; + + static const QList m_navtexTransmitters; + static const NavtexTransmitter* getTransmitter(QTime time, int area, qint64 frequency); +}; + +class SDRBASE_API NavtexMessage { + +public: + + QString m_stationId; + QString m_typeId; + QString m_id; + QString m_message; + QDateTime m_dateTime; + bool m_valid; + + static const QMap m_types; + + NavtexMessage(const QString& text); + NavtexMessage(QDateTime dataTime, const QString& stationId, const QString& typeId, const QString& id, const QString& message); + QString getStation(int area, qint64 frequency) const; + QString getType() const; + +}; + +class SDRBASE_API SitorBDecoder { + +public: + + void init(); + signed char decode(signed char c); + int getErrors() const { return m_errors; } + static QString printable(signed char c); + +private: + static const signed char PHASING_1 = 0x78; + static const signed char PHASING_2 = 0x33; + static const int BUFFER_SIZE = 3; + signed char m_buf[3]; + bool m_figureSet; + enum State { + PHASING, + FILL_DX, + FILL_RX, + DX, + RX + } m_state; + int m_idx; + int m_errors; + + static const signed char m_ccir476LetterSetDecode[128]; + static const signed char m_ccir476FigureSetDecode[128]; + + signed char ccir476Decode(signed char c); + +}; + +#endif // INCLUDE_UTIL_NAVTEX_H + diff --git a/android/app/src/main/cpp/util/openaip.cpp b/android/app/src/main/cpp/util/openaip.cpp new file mode 100644 index 0000000..197411c --- /dev/null +++ b/android/app/src/main/cpp/util/openaip.cpp @@ -0,0 +1,730 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "openaip.h" + +QList Airspace::readXML(const QString &filename) +{ + QList airspaces; + QFile file(filename); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QXmlStreamReader xmlReader(&file); + + while(!xmlReader.atEnd() && !xmlReader.hasError()) + { + if (xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("ASP")) + { + Airspace *airspace = new Airspace(); + + airspace->m_category = xmlReader.attributes().value("CATEGORY").toString(); + + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("COUNTRY")) + { + airspace->m_country = xmlReader.readElementText(); + } + else if (xmlReader.name() == QLatin1String("NAME")) + { + airspace->m_name = xmlReader.readElementText(); + } + else if (xmlReader.name() == QLatin1String("ALTLIMIT_TOP")) + { + while(xmlReader.readNextStartElement()) + { + airspace->m_top.m_reference = xmlReader.attributes().value("REFERENCE").toString(); + airspace->m_top.m_altUnit = xmlReader.attributes().value("UNIT").toString(); + if (xmlReader.name() == QLatin1String("ALT")) + { + airspace->m_top.m_alt = xmlReader.readElementText().toInt(); + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else if (xmlReader.name() == QLatin1String("ALTLIMIT_BOTTOM")) + { + while(xmlReader.readNextStartElement()) + { + airspace->m_bottom.m_reference = xmlReader.attributes().value("REFERENCE").toString(); + airspace->m_bottom.m_altUnit = xmlReader.attributes().value("UNIT").toString(); + if (xmlReader.name() == QLatin1String("ALT")) + { + airspace->m_bottom.m_alt = xmlReader.readElementText().toInt(); + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else if (xmlReader.name() == QLatin1String("GEOMETRY")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("POLYGON")) + { + QString pointsString = xmlReader.readElementText(); + QStringList points = pointsString.split(","); + for (const auto& ps : points) + { + QStringList split = ps.trimmed().split(" "); + if (split.size() == 2) + { + QPointF pf(split[0].toDouble(), split[1].toDouble()); + airspace->m_polygon.append(pf); + } + else + { + qDebug() << "Airspace::readXML - Unexpected polygon point format: " << ps; + } + } + } + else + { + xmlReader.skipCurrentElement(); + } + } + } + else + { + xmlReader.skipCurrentElement(); + } + } + + airspace->calculatePosition(); + //qDebug() << "Adding airspace: " << airspace->m_name << " " << airspace->m_category; + airspaces.append(airspace); + } + } + } + + file.close(); + } + else + { + // Don't warn, as many countries don't have files + //qDebug() << "Airspace::readXML: Could not open " << filename << " for reading."; + } + return airspaces; +} + +QList NavAid::readXML(const QString &filename) +{ + int uniqueId = 1; + QList navAidInfo; + QFile file(filename); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QXmlStreamReader xmlReader(&file); + + while(!xmlReader.atEnd() && !xmlReader.hasError()) + { + if (xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("NAVAID")) + { + QStringView typeRef = xmlReader.attributes().value("TYPE"); + if ((typeRef == QLatin1String("NDB")) + || (typeRef == QLatin1String("DME")) + || (typeRef == QLatin1String("VOR")) + || (typeRef == QLatin1String("VOR-DME")) + || (typeRef == QLatin1String("VORTAC")) + || (typeRef == QLatin1String("DVOR")) + || (typeRef == QLatin1String("DVOR-DME")) + || (typeRef == QLatin1String("DVORTAC"))) + { + QString type = typeRef.toString(); + QString name; + QString id; + float lat = 0.0f; + float lon = 0.0f; + float elevation = 0.0f; + float frequency = 0.0f; + QString channel; + int range = 25; + float declination = 0.0f; + bool alignedTrueNorth = false; + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("NAME")) + { + name = xmlReader.readElementText(); + } + else if (xmlReader.name() == QLatin1String("ID")) + { + id = xmlReader.readElementText(); + } + else if (xmlReader.name() == QLatin1String("GEOLOCATION")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("LAT")) { + lat = xmlReader.readElementText().toFloat(); + } else if (xmlReader.name() == QLatin1String("LON")) { + lon = xmlReader.readElementText().toFloat(); + } else if (xmlReader.name() == QLatin1String("ELEV")) { + elevation = xmlReader.readElementText().toFloat(); + } else { + xmlReader.skipCurrentElement(); + } + } + } + else if (xmlReader.name() == QLatin1String("RADIO")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("FREQUENCY")) + { + if (type == "NDB") { + frequency = xmlReader.readElementText().toFloat(); + } else { + frequency = xmlReader.readElementText().toFloat() * 1000.0; + } + } else if (xmlReader.name() == QLatin1String("CHANNEL")) { + channel = xmlReader.readElementText(); + } else { + xmlReader.skipCurrentElement(); + } + } + } + else if (xmlReader.name() == QLatin1String("PARAMS")) + { + while(xmlReader.readNextStartElement()) + { + if (xmlReader.name() == QLatin1String("RANGE")) { + range = xmlReader.readElementText().toInt(); + } else if (xmlReader.name() == QLatin1String("DECLINATION")) { + declination = xmlReader.readElementText().toFloat(); + } else if (xmlReader.name() == QLatin1String("ALIGNEDTOTRUENORTH")) { + alignedTrueNorth = xmlReader.readElementText() == "TRUE"; + } else { + xmlReader.skipCurrentElement(); + } + } + } + else + { + xmlReader.skipCurrentElement(); + } + } + NavAid *navAid = new NavAid(); + navAid->m_id = uniqueId++; + navAid->m_ident = id; + // Check idents conform to our filtering rules + if (navAid->m_ident.size() < 2) { + qDebug() << "NavAid::readXML: Ident less than 2 characters: " << navAid->m_ident; + } else if (navAid->m_ident.size() > 3) { + qDebug() << "NavAid::readXML: Ident greater than 3 characters: " << navAid->m_ident; + } + navAid->m_type = type; + navAid->m_name = name; + navAid->m_frequencykHz = frequency; + navAid->m_channel = channel; + navAid->m_latitude = lat; + navAid->m_longitude = lon; + navAid->m_elevation = elevation; + navAid->m_range = range; + navAid->m_magneticDeclination = declination; + navAid->m_alignedTrueNorth = alignedTrueNorth; + navAidInfo.append(navAid); + } + } + } + } + + file.close(); + } + else + { + // Don't warn, as many countries don't have files + //qDebug() << "NavAid::readNavAidsXML: Could not open " << filename << " for reading."; + } + return navAidInfo; +} + +const QStringList OpenAIP::m_countryCodes = { + "ad", + "ae", + "af", + "ag", + "ai", + "al", + "am", + "an", + "ao", + "aq", + "ar", + "as", + "at", + "au", + "aw", + "ax", + "az", + "ba", + "bb", + "bd", + "be", + "bf", + "bg", + "bh", + "bi", + "bj", + "bl", + "bm", + "bn", + "bo", + "bq", + "br", + "bs", + "bt", + "bv", + "bw", + "by", + "bz", + "ca", + "cc", + "cd", + "cf", + "cg", + "ch", + "ci", + "ck", + "cl", + "cm", + "cn", + "co", + "cr", + "cu", + "cv", + "cw", + "cx", + "cy", + "cz", + "de", + "dj", + "dk", + "dm", + "do", + "dz", + "ec", + "ee", + "eg", + "eh", + "er", + "es", + "et", + "fi", + "fj", + "fk", + "fm", + "fo", + "fr", + "ga", + "gb", + "ge", + "gf", + "gg", + "gh", + "gi", + "gl", + "gm", + "gn", + "gp", + "gq", + "gr", + "gs", + "gt", + "gu", + "gw", + "gy", + "hk", + "hm", + "hn", + "hr", + "hu", + "id", + "ie", + "il", + "im", + "in", + "io", + "iq", + "ir", + "is", + "it", + "je", + "jm", + "jo", + "jp", + "ke", + "kg", + "kh", + "ki", + "km", + "kn", + "kp", + "kr", + "kw", + "ky", + "kz", + "la", + "lb", + "lc", + "li", + "lk", + "lr", + "ls", + "lt", + "lu", + "lv", + "ly", + "ma", + "mc", + "md", + "me", + "mf", + "mg", + "mh", + "mk", + "ml", + "mm", + "mn", + "mo", + "mp", + "mq", + "mr", + "ms", + "mt", + "mu", + "mv", + "mw", + "mx", + "my", + "mz", + "na", + "nc", + "ne", + "nf", + "ng", + "ni", + "nl", + "no", + "np", + "nr", + "nu", + "nz", + "om", + "pa", + "pe", + "pf", + "pg", + "ph", + "pk", + "pl", + "pm", + "pn", + "pr", + "ps", + "pt", + "pw", + "py", + "qa", + "re", + "ro", + "rs", + "ru", + "rw", + "sa", + "sb", + "sc", + "sd", + "se", + "sg", + "sh", + "si", + "sj", + "sk", + "sl", + "sm", + "sn", + "so", + "sr", + "ss", + "st", + "sv", + "sx", + "sy", + "sz", + "tc", + "td", + "tf", + "tg", + "th", + "tj", + "tk", + "tl", + "tm", + "tn", + "to", + "tr", + "tt", + "tv", + "tw", + "tz", + "ua", + "ug", + "um", + "us", + "uy", + "uz", + "va", + "vc", + "ve", + "vg", + "vi", + "vn", + "vu", + "wf", + "ws", + "ye", + "yt", + "za", + "zm", + "zw" +}; + +QSharedPointer> OpenAIP::m_airspaces; +QSharedPointer> OpenAIP::m_navAids; + +QDateTime OpenAIP::m_airspacesModifiedDateTime; +QDateTime OpenAIP::m_navAidsModifiedDateTime; + +OpenAIP::OpenAIP(QObject *parent) : + QObject(parent) +{ + connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished); +} + +OpenAIP::~OpenAIP() +{ + disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished); +} + +QString OpenAIP::getDataDir() +{ + // Get directory to store app data in + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + // First dir is writable + return locations[0]; +} + +QString OpenAIP::getAirspaceFilename(int i) +{ + return getAirspaceFilename(m_countryCodes[i]); +} + +QString OpenAIP::getAirspaceFilename(const QString& countryCode) +{ + return getDataDir() + "/" + countryCode + "_asp.xml"; +} + +QString OpenAIP::getAirspaceURL(int i) +{ + if (i < m_countryCodes.size()) { + return QString(OPENAIP_AIRSPACE_URL).arg(m_countryCodes[i]); + } else { + return QString(); + } +} + +void OpenAIP::downloadAirspaces() +{ + m_countryIndex = 0; + downloadAirspace(); +} + +void OpenAIP::downloadAirspace() +{ + QString filename = getAirspaceFilename(m_countryIndex); + QString urlString = getAirspaceURL(m_countryIndex); + QUrl dbURL(urlString); + qDebug() << "OpenAIP::downloadAirspace: Downloading " << urlString; + emit downloadingURL(urlString); + m_dlm.download(dbURL, filename); +} + +QString OpenAIP::getNavAidsFilename(int i) +{ + return getNavAidsFilename(m_countryCodes[i]); +} + +QString OpenAIP::getNavAidsFilename(const QString& countryCode) +{ + return getDataDir() + "/" + countryCode + "_nav.xml"; +} + +QString OpenAIP::getNavAidsURL(int i) +{ + if (i < m_countryCodes.size()) { + return QString(OPENAIP_NAVAIDS_URL).arg(m_countryCodes[i]); + } else { + return QString(); + } +} + +void OpenAIP::downloadNavAids() +{ + m_countryIndex = 0; + downloadNavAid(); +} + +void OpenAIP::downloadNavAid() +{ + QString filename = getNavAidsFilename(m_countryIndex); + QString urlString = getNavAidsURL(m_countryIndex); + QUrl dbURL(urlString); + qDebug() << "OpenAIP::downloadNavAid: Downloading " << urlString; + emit downloadingURL(urlString); + m_dlm.download(dbURL, filename); +} + +void OpenAIP::downloadFinished(const QString& filename, bool success) +{ + // Not all countries have corresponding files, so we should expect some errors + if (!success) { + qDebug() << "OpenAIP::downloadFinished: Failed: " << filename; + } + + if (filename == getNavAidsFilename(m_countryIndex)) + { + m_countryIndex++; + if (m_countryIndex < m_countryCodes.size()) { + downloadNavAid(); + } else { + emit downloadNavAidsFinished(); + } + } + else if (filename == getAirspaceFilename(m_countryIndex)) + { + m_countryIndex++; + if (m_countryIndex < m_countryCodes.size()) { + downloadAirspace(); + } else { + emit downloadAirspaceFinished(); + } + } + else + { + qDebug() << "OpenAIP::downloadFinished: Unexpected filename: " << filename; + emit downloadError(QString("Unexpected filename: %1").arg(filename)); + } +} + +// Read airspaces for all countries +QList *OpenAIP::readAirspaces() +{ + QList *airspaces = new QList(); + for (const auto& countryCode : m_countryCodes) { + airspaces->append(readAirspaces(countryCode)); + } + return airspaces; +} + +// Read airspaces for a single country +QList OpenAIP::readAirspaces(const QString& countryCode) +{ + return Airspace::readXML(getAirspaceFilename(countryCode)); +} + +// Read NavAids for all countries +QList *OpenAIP::readNavAids() +{ + QList *navAids = new QList(); + for (const auto& countryCode : m_countryCodes) { + navAids->append(readNavAids(countryCode)); + } + return navAids; +} + +// Read NavAids for a single country +QList OpenAIP::readNavAids(const QString& countryCode) +{ + return NavAid::readXML(getNavAidsFilename(countryCode)); +} + +QSharedPointer> OpenAIP::getAirspaces() +{ + QDateTime filesDateTime = getAirspacesModifiedDateTime(); + + if (!m_airspaces || (filesDateTime > m_airspacesModifiedDateTime)) + { + // Using shared pointer, so old object, if it exists, will be deleted, when no longer user + m_airspaces = QSharedPointer>(readAirspaces()); + m_airspacesModifiedDateTime = filesDateTime; + } + return m_airspaces; +} + +QSharedPointer> OpenAIP::getNavAids() +{ + QDateTime filesDateTime = getNavAidsModifiedDateTime(); + + if (!m_navAids || (filesDateTime > m_navAidsModifiedDateTime)) + { + // Using shared pointer, so old object, if it exists, will be deleted, when no longer user + m_navAids = QSharedPointer>(readNavAids()); + m_navAidsModifiedDateTime = filesDateTime; + } + return m_navAids; +} + +// Gets the date and time the airspaces files were most recently modified +QDateTime OpenAIP::getAirspacesModifiedDateTime() +{ + QDateTime dateTime; + for (const auto& countryCode : m_countryCodes) + { + QFileInfo fileInfo(getAirspaceFilename(countryCode)); + QDateTime fileModifiedDateTime = fileInfo.lastModified(); + if (fileModifiedDateTime > dateTime) { + dateTime = fileModifiedDateTime; + } + } + return dateTime; +} + +// Gets the date and time the navaid files were most recently modified +QDateTime OpenAIP::getNavAidsModifiedDateTime() +{ + QDateTime dateTime; + for (const auto& countryCode : m_countryCodes) + { + QFileInfo fileInfo(getNavAidsFilename(countryCode)); + QDateTime fileModifiedDateTime = fileInfo.lastModified(); + if (fileModifiedDateTime > dateTime) { + dateTime = fileModifiedDateTime; + } + } + return dateTime; +} + diff --git a/android/app/src/main/cpp/util/openaip.h b/android/app/src/main/cpp/util/openaip.h new file mode 100644 index 0000000..2f820b4 --- /dev/null +++ b/android/app/src/main/cpp/util/openaip.h @@ -0,0 +1,203 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_OPENAIP_H +#define INCLUDE_OPENAIP_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "export.h" + +#include "util/units.h" +#include "util/httpdownloadmanager.h" + +// Server is moving, use new URL and .xml extension instead of .aip +//#define OPENAIP_AIRSPACE_URL "https://www.openaip.net/customer_export_akfshb9237tgwiuvb4tgiwbf/%1_asp.aip" +//#define OPENAIP_NAVAIDS_URL "https://www.openaip.net/customer_export_akfshb9237tgwiuvb4tgiwbf/%1_nav.aip" +#define OPENAIP_AIRSPACE_URL "https://storage.googleapis.com/29f98e10-a489-4c82-ae5e-489dbcd4912f/%1_asp.xml" +#define OPENAIP_NAVAIDS_URL "https://storage.googleapis.com/29f98e10-a489-4c82-ae5e-489dbcd4912f/%1_nav.xml" + +struct SDRBASE_API Airspace { + + struct AltLimit { + QString m_reference; // STD, MSL + int m_alt; // Altitude + QString m_altUnit; // FL (Flight level) or F (Feet) + }; + + QString m_category; // A-G, GLIDING, DANGER, PROHIBITED, TMZ + QString m_country; // GB + QString m_name; // BIGGIN HILL ATZ 129.405 - TODO: Extract frequency so we can tune to it + AltLimit m_top; // Top of airspace + AltLimit m_bottom; // Bottom of airspace + QVector m_polygon; + QPointF m_center; // Center of polygon + QPointF m_position; // Position for text (not at the center, otherwise it will clash with airport) + + void calculatePosition() + { + qreal minX, maxX; + qreal minY, maxY; + if (m_polygon.size() > 0) + { + minX = maxX = m_polygon[0].x(); + minY = maxY = m_polygon[0].y(); + for (int i = 1; i < m_polygon.size(); i++) + { + qreal x = m_polygon[i].x(); + qreal y = m_polygon[i].y(); + minX = std::min(minX, x); + maxX = std::max(maxX, x); + minY = std::min(minY, y); + maxY = std::max(maxY, y); + } + m_center.setX(minX + (maxX - minX) / 2.0); + m_center.setY(minY + (maxY - minY) * 2.0); + m_position.setX(minX + (maxX - minX) / 2.0); + m_position.setY(minY + (maxY - minY) * 3.0 / 4.0); + } + } + + QString getAlt(const AltLimit *altlimit) const + { + // Format on UK charts + if (altlimit->m_alt == 0) { + return "SFC"; // Surface + } if (altlimit->m_altUnit == "FL") { + return QString("FL%1").arg(altlimit->m_alt); + } else if (altlimit->m_altUnit == "F") { + return QString("%1'").arg(altlimit->m_alt); + } else { + return QString("%1 %2").arg(altlimit->m_alt).arg(altlimit->m_altUnit); + } + } + + double heightInMetres(const AltLimit *altlimit) const + { + if (altlimit->m_altUnit == "FL") { + return Units::feetToMetres(altlimit->m_alt * 100); + } else if (altlimit->m_altUnit == "F") { + return Units::feetToMetres(altlimit->m_alt); + } else { + return altlimit->m_alt; + } + } + + double topHeightInMetres() const + { + return heightInMetres(&m_top); + } + + double bottomHeightInMetres() const + { + return heightInMetres(&m_bottom); + } + + // Read OpenAIP XML file + static QList readXML(const QString &filename); + +}; + +struct SDRBASE_API NavAid { + int m_id; // Unique ID needed by VOR feature - Don't use value from database as that's 96-bit + QString m_ident; // 2 or 3 character ident + QString m_type; // NDB, VOR, VOR-DME or VORTAC + QString m_name; + float m_latitude; + float m_longitude; + float m_elevation; + float m_frequencykHz; + QString m_channel; + int m_range; // Nautical miles + float m_magneticDeclination; + bool m_alignedTrueNorth; // Is the VOR aligned to true North, rather than magnetic (may be the case at high latitudes) + + int getRangeMetres() const + { + return Units::nauticalMilesToIntegerMetres((float)m_range); + } + + // OpenAIP XML file + static QList readXML(const QString &filename); +}; + +class SDRBASE_API OpenAIP : public QObject { + Q_OBJECT + +public: + OpenAIP(QObject* parent = nullptr); + ~OpenAIP(); + + static const QStringList m_countryCodes; + + void downloadAirspaces(); + void downloadNavAids(); + + static QSharedPointer> getAirspaces(); + static QSharedPointer> getNavAids(); + +private: + HttpDownloadManager m_dlm; + int m_countryIndex; + + static QSharedPointer> m_airspaces; + static QSharedPointer> m_navAids; + + static QDateTime m_airspacesModifiedDateTime; + static QDateTime m_navAidsModifiedDateTime; + + static QList *readAirspaces(); + static QList readAirspaces(const QString& countryCode); + static QList *readNavAids(); + static QList readNavAids(const QString& countryCode); + + static QString getDataDir(); + static QString getAirspaceFilename(int i); + static QString getAirspaceFilename(const QString& countryCode); + static QString getAirspaceURL(int i); + static QString getNavAidsFilename(int i); + static QString getNavAidsFilename(const QString& countryCode); + static QString getNavAidsURL(int i); + static QDateTime getAirspacesModifiedDateTime(); + static QDateTime getNavAidsModifiedDateTime(); + + void downloadAirspace(); + void downloadNavAid(); + +public slots: + void downloadFinished(const QString& filename, bool success); + +signals: + void downloadingURL(const QString& url); + void downloadError(const QString& error); + void downloadAirspaceFinished(); + void downloadNavAidsFinished(); + +}; + +#endif // INCLUDE_OPENAIP_H + diff --git a/android/app/src/main/cpp/util/osndb.cpp b/android/app/src/main/cpp/util/osndb.cpp new file mode 100644 index 0000000..e4a1214 --- /dev/null +++ b/android/app/src/main/cpp/util/osndb.cpp @@ -0,0 +1,733 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#if (QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) +#include +#else +#include +#endif + +#include "util/osndb.h" +#include "util/corsproxy.h" + +QHash AircraftInformation::m_airlineIcons; +QHash AircraftInformation::m_airlineMissingIcons; +QHash AircraftInformation::m_sideviewIcons; +QHash AircraftInformation::m_flagIcons; +QHash *AircraftInformation::m_prefixMap; +QHash *AircraftInformation::m_militaryMap; +QMutex AircraftInformation::m_mutex; + +QSharedPointer> OsnDB::m_aircraftInformation; +QSharedPointer> OsnDB::m_aircraftInformationByReg; +QSharedPointer> OsnDB::m_aircraftRouteInformation; +QDateTime OsnDB::m_modifiedDateTime; + + +OsnDB::OsnDB(QObject *parent) : + QObject(parent) +{ + connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OsnDB::downloadFinished); +} + +OsnDB::~OsnDB() +{ + disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OsnDB::downloadFinished); +} + +void OsnDB::downloadAircraftInformation() +{ + QString filename = OsnDB::getZipFilename(); + QString urlString = OSNDB_URL; + QUrl dbURL(CORSProxy::adjustHost(urlString)); + qDebug() << "OsnDB::downloadAircraftInformation: Downloading " << urlString; + emit downloadingURL(urlString); + QNetworkReply *reply = m_dlm.download(dbURL, filename); + connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) { + emit downloadProgress(bytesRead, totalBytes); + }); +} + +void OsnDB::downloadFinished(const QString& filename, bool success) +{ + if (!success) + { + qWarning() << "OsnDB::downloadFinished: Failed to download: " << filename; + emit downloadError(QString("Failed to download: %1").arg(filename)); + } + else if (filename == OsnDB::getZipFilename()) + { + // Extract .csv file from .zip file + QZipReader reader(filename); + if (reader.extractAll(getDataDir())) + { + emit downloadAircraftInformationFinished(); + } + else + { + qWarning() << "OsnDB::downloadFinished - Failed to extract files from " << filename; + emit downloadError(QString("Failed to extract files from ").arg(filename)); + } + } + else + { + qDebug() << "OsnDB::downloadFinished: Unexpected filename: " << filename; + emit downloadError(QString("Unexpected filename: %1").arg(filename)); + } +} + +QSharedPointer> OsnDB::getAircraftInformation() +{ + QFileInfo fastFileInfo(getFastDBFilename()); + QFileInfo fullFileInfo(getOSNDBFilename()); + QDateTime fastModifiedDateTime = fastFileInfo.lastModified(); + QDateTime fullModifiedDateTime = fullFileInfo.lastModified(); + + // Update fast database, if full database is newer + if (fullModifiedDateTime > fastModifiedDateTime) + { + qDebug() << "AircraftInformation::getAircraftInformation: Creating fast database"; + m_aircraftInformation = QSharedPointer>(OsnDB::readOSNDB(getOSNDBFilename())); + if (m_aircraftInformation) + { + OsnDB::writeFastDB(OsnDB::getFastDBFilename(), m_aircraftInformation.get()); + fastModifiedDateTime = fastFileInfo.lastModified(); + m_modifiedDateTime = fastModifiedDateTime; + m_aircraftInformationByReg = QSharedPointer> (OsnDB::registrationHash(m_aircraftInformation.get())); + } + } + + if (!m_aircraftInformation || (fastModifiedDateTime > m_modifiedDateTime)) + { + m_aircraftInformation = QSharedPointer>(OsnDB::readFastDB(getFastDBFilename())); + if (m_aircraftInformation) + { + m_modifiedDateTime = fastModifiedDateTime; + m_aircraftInformationByReg = QSharedPointer> (OsnDB::registrationHash(m_aircraftInformation.get())); + } + } + + return m_aircraftInformation; +} + +QSharedPointer> OsnDB::getAircraftInformationByReg() +{ + getAircraftInformation(); + return m_aircraftInformationByReg; +} + +QSharedPointer> OsnDB::getAircraftRouteInformation() +{ + if (!m_aircraftRouteInformation) { + m_aircraftRouteInformation = QSharedPointer>(OsnDB::readRouteDB(getRouteDBFilename())); + } + return m_aircraftRouteInformation; +} + +QHash *OsnDB::readOSNDB(const QString &filename) +{ + int cnt = 0; + QHash *aircraftInfo = nullptr; + + // Column numbers used for the data as of 2020/10/28 + int icaoCol = 0; + int registrationCol = 1; + int manufacturerNameCol = 3; + int modelCol = 4; + int typeCodeCol = 5; + int ownerCol = 13; + int operatorCol = 9; + int operatorICAOCol = 11; + int registeredCol = 15; + + qDebug() << "AircraftInformation::readOSNDB: " << filename; + + FILE *file; + QByteArray utfFilename = filename.toUtf8(); + if ((file = fopen(utfFilename.constData(), "r")) != NULL) + { + char row[2048]; + + if (fgets(row, sizeof(row), file)) + { + aircraftInfo = new QHash(); + aircraftInfo->reserve(500000); + // Read header + int idx = 0; + char *p = strtok(row, ","); + while (p != NULL) + { + if (!strcmp(p, "icao24")) + icaoCol = idx; + else if (!strcmp(p, "registration")) + registrationCol = idx; + else if (!strcmp(p, "manufacturername")) + manufacturerNameCol = idx; + else if (!strcmp(p, "model")) + modelCol = idx; + else if (!strcmp(p, "typecode")) + typeCodeCol = idx; + else if (!strcmp(p, "owner")) + ownerCol = idx; + else if (!strcmp(p, "operator")) + operatorCol = idx; + else if (!strcmp(p, "operatoricao")) + operatorICAOCol = idx; + else if (!strcmp(p, "registered")) + registeredCol = idx; + p = strtok(NULL, ","); + idx++; + } + // Read data + while (fgets(row, sizeof(row), file)) + { + int icao = 0; + char *icaoString = NULL; + char *registration = NULL; + size_t registrationLen = 0; + char *manufacturerName = NULL; + size_t manufacturerNameLen = 0; + char *model = NULL; + size_t modelLen = 0; + char *typeCode = NULL; + size_t typeCodeLen = 0; + char *owner = NULL; + size_t ownerLen = 0; + char *operatorName = NULL; + size_t operatorNameLen = 0; + char *operatorICAO = NULL; + size_t operatorICAOLen = 0; + char *registered = NULL; + size_t registeredLen = 0; + + p = strtok(row, ","); + idx = 0; + while (p != NULL) + { + // Read strings, stripping quotes + if (idx == icaoCol) + { + icaoString = p+1; + icaoString[strlen(icaoString)-1] = '\0'; + icao = strtol(icaoString, NULL, 16); + // Ignore entries with uppercase - might be better to try and merge? + // See: https://opensky-network.org/forum/bug-reports/652-are-the-aircraft-database-dumps-working + for (int i = 0; icaoString[i] != '\0'; i++) + { + char c = icaoString[i]; + if ((c >= 'A') && (c <= 'F')) + { + icao = 0; + break; + } + } + } + else if (idx == registrationCol) + { + registration = p+1; + registrationLen = strlen(registration)-1; + registration[registrationLen] = '\0'; + } + else if (idx == manufacturerNameCol) + { + manufacturerName = p+1; + manufacturerNameLen = strlen(manufacturerName)-1; + manufacturerName[manufacturerNameLen] = '\0'; + } + else if (idx == modelCol) + { + model = p+1; + modelLen = strlen(model)-1; + model[modelLen] = '\0'; + } + else if (idx == typeCodeCol) + { + typeCode = p+1; + typeCodeLen = strlen(typeCode)-1; + typeCode[typeCodeLen] = '\0'; + } + else if (idx == ownerCol) + { + owner = p+1; + ownerLen = strlen(owner)-1; + owner[ownerLen] = '\0'; + } + else if (idx == operatorCol) + { + operatorName = p+1; + operatorNameLen = strlen(operatorName)-1; + operatorName[operatorNameLen] = '\0'; + } + else if (idx == operatorICAOCol) + { + operatorICAO = p+1; + operatorICAOLen = strlen(operatorICAO)-1; + operatorICAO[operatorICAOLen] = '\0'; + } + else if (idx == registeredCol) + { + registered = p+1; + registeredLen = strlen(registered)-1; + registered[registeredLen] = '\0'; + } + p = strtok(NULL, ","); + idx++; + } + + // Only create the entry if we have some interesting data + if ((icao != 0) && (registrationLen > 0 || modelLen > 0 || ownerLen > 0 || operatorNameLen > 0 || operatorICAOLen > 0)) + { + QString modelQ = QString(model); + // Tidy up the model names + if (modelQ.endsWith(" (Boeing)")) + modelQ = modelQ.left(modelQ.size() - 9); + else if (modelQ.startsWith("BOEING ")) + modelQ = modelQ.right(modelQ.size() - 7); + else if (modelQ.startsWith("Boeing ")) + modelQ = modelQ.right(modelQ.size() - 7); + else if (modelQ.startsWith("AIRBUS ")) + modelQ = modelQ.right(modelQ.size() - 7); + else if (modelQ.startsWith("Airbus ")) + modelQ = modelQ.right(modelQ.size() - 7); + else if (modelQ.endsWith(" (Cessna)")) + modelQ = modelQ.left(modelQ.size() - 9); + + AircraftInformation *aircraft = new AircraftInformation(); + aircraft->m_icao = icao; + aircraft->m_registration = QString(registration); + aircraft->m_manufacturerName = QString(manufacturerName); + aircraft->m_model = modelQ; + aircraft->m_type = QString(typeCode); + aircraft->m_owner = QString(owner); + aircraft->m_operator = QString(operatorName); + aircraft->m_operatorICAO = QString(operatorICAO); + aircraft->m_registered = QString(registered); + aircraftInfo->insert(icao, aircraft); + cnt++; + } + + } + } + fclose(file); + } + else + qDebug() << "AircraftInformation::readOSNDB: Failed to open " << filename; + + qDebug() << "AircraftInformation::readOSNDB: Read " << cnt << " aircraft"; + + return aircraftInfo; +} + +QHash *OsnDB::registrationHash(const QHash *in) +{ + QHash *out = new QHash(); + QHashIterator i(*in); + while (i.hasNext()) + { + i.next(); + AircraftInformation *info = i.value(); + out->insert(info->m_registration, info); + } + return out; +} + +bool OsnDB::writeFastDB(const QString &filename, const QHash *aircraftInfo) +{ + QFile file(filename); + if (file.open(QIODevice::WriteOnly)) + { + file.write("icao24,registration,manufacturername,model,typecode,owner,operator,operatoricao,registered\n"); + QHash::const_iterator i = aircraftInfo->begin(); + while (i != aircraftInfo->end()) + { + AircraftInformation *info = i.value(); + file.write(QString("%1").arg(info->m_icao, 1, 16).toUtf8()); + file.write(","); + file.write(info->m_registration.toUtf8()); + file.write(","); + file.write(info->m_manufacturerName.toUtf8()); + file.write(","); + file.write(info->m_model.toUtf8()); + file.write(","); + file.write(info->m_type.toUtf8()); + file.write(","); + file.write(info->m_owner.toUtf8()); + file.write(","); + file.write(info->m_operator.toUtf8()); + file.write(","); + file.write(info->m_operatorICAO.toUtf8()); + file.write(","); + file.write(info->m_registered.toUtf8()); + file.write("\n"); + ++i; + } + file.close(); + return true; + } + else + { + qCritical() << "AircraftInformation::writeFastDB failed to open " << filename << " for writing: " << file.errorString(); + return false; + } +} + +QHash *OsnDB::readFastDB(const QString &filename) +{ + int cnt = 0; + QHash *aircraftInfo = nullptr; + + qDebug() << "AircraftInformation::readFastDB: " << filename; + + FILE *file; + QByteArray utfFilename = filename.toUtf8(); + if ((file = fopen(utfFilename.constData(), "r")) != NULL) + { + char row[2048]; + + if (fgets(row, sizeof(row), file)) + { + // Check header + if (!strcmp(row, "icao24,registration,manufacturername,model,typecode,owner,operator,operatoricao,registered\n")) + { + aircraftInfo = new QHash(); + aircraftInfo->reserve(500000); + // Read data + while (fgets(row, sizeof(row), file)) + { + char *p = row; + AircraftInformation *aircraft = new AircraftInformation(); + char *icaoString = csvNext(&p); + int icao = strtol(icaoString, NULL, 16); + aircraft->m_icao = icao; + aircraft->m_registration = QString(csvNext(&p)); + aircraft->m_manufacturerName = QString(csvNext(&p)); + aircraft->m_model = QString(csvNext(&p)); + aircraft->m_type = QString(csvNext(&p)); + aircraft->m_owner = QString(csvNext(&p)); + aircraft->m_operator = QString(csvNext(&p)); + aircraft->m_operatorICAO = QString(csvNext(&p)); + aircraft->m_registered = QString(csvNext(&p)); + aircraftInfo->insert(icao, aircraft); + cnt++; + } + } + else + qDebug() << "AircraftInformation::readFastDB: Unexpected header" << row; + } + else + qDebug() << "AircraftInformation::readFastDB: Empty file"; + fclose(file); + } + else + qDebug() << "AircraftInformation::readFastDB: Failed to open " << filename; + + qDebug() << "AircraftInformation::readFastDB - read " << cnt << " aircraft"; + + return aircraftInfo; +} + +QHash *OsnDB::readRouteDB(const QString &filename) +{ + int cnt = 0; + QHash *routeInfo = nullptr; + + qDebug() << "AircraftInformation::readRouteDB: " << filename; + + FILE *file; + QByteArray utfFilename = filename.toUtf8(); + if ((file = fopen(utfFilename.constData(), "r")) != NULL) + { + char row[2048]; + + if (fgets(row, sizeof(row), file)) + { + // Check header + if (!strcmp(row, "callsign,dep,arr,stops\n")) + { + routeInfo = new QHash(); + routeInfo->reserve(600000); + // Read data + while (fgets(row, sizeof(row), file)) + { + char *p = row; + AircraftRouteInformation *route = new AircraftRouteInformation(); + route->m_callsign = QString(csvNext(&p)); + route->m_dep = QString(csvNext(&p)); + route->m_arr = QString(csvNext(&p)); + route->m_stops = QString(csvNext(&p)); + routeInfo->insert(route->m_callsign, route); + cnt++; + } + } + else + { + qDebug() << "AircraftInformation::readRouteDB: Unexpected header" << row; + } + } + else + { + qDebug() << "AircraftInformation::readRouteDB: Empty file"; + } + fclose(file); + } + else + { + qDebug() << "AircraftInformation::readRouteDB: Failed to open " << filename; + } + + qDebug() << "AircraftInformation::readRouteDB - read " << cnt << " routes"; + + return routeInfo; +} + +QString AircraftInformation::getFlag() const +{ + QString flag; + if (m_prefixMap) + { + int idx = m_registration.indexOf('-'); + if (idx >= 0) + { + QString prefix; + + // Some countries use AA-A - try these first as first letters are common + prefix = m_registration.left(idx + 2); + if (m_prefixMap->contains(prefix)) + { + flag = m_prefixMap->value(prefix); + } + else + { + // Try letters before '-' + prefix = m_registration.left(idx); + // Both China and Taiwan use B prefix. China regs are <= 4 digits, with Taiwan 5 + if (prefix == "B") + { + if (m_registration.size() >= 7) { + flag = "taiwan"; + } else { + flag = "china"; + } + } + else + { + if (m_prefixMap->contains(prefix)) { + flag = m_prefixMap->value(prefix); + } + } + } + } + else + { + // No '-' Could be one of a few countries or military. + // See: https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes + if (m_registration.startsWith("N")) { + flag = m_prefixMap->value("N"); // US + } else if (m_registration.startsWith("JA")) { + flag = m_prefixMap->value("JA"); // Japan + } else if (m_registration.startsWith("HL")) { + flag = m_prefixMap->value("HL"); // Korea + } else if (m_registration.startsWith("YV")) { + flag = m_prefixMap->value("YV"); // Venezuela + } else if ((m_militaryMap != nullptr) && (m_militaryMap->contains(m_operator))) { + flag = m_militaryMap->value(m_operator); + } + } + } + return flag; +} + +QString AircraftInformation::getAirlineIconPath(const QString &operatorICAO) +{ + QString endPath = QString("/airlinelogos/%1.png").arg(operatorICAO); + // Try in user directory first, so they can customise + QString userIconPath = OsnDB::getDataDir() + endPath; + QFile file(userIconPath); + if (file.exists()) + { + return userIconPath; + } + else + { + // Try in resources + QString resourceIconPath = ":" + endPath; + QResource resource(resourceIconPath); + if (resource.isValid()) + { + return resourceIconPath; + } + } + return QString(); +} + +QIcon *AircraftInformation::getAirlineIcon(const QString &operatorICAO) +{ + if (m_airlineIcons.contains(operatorICAO)) + { + return m_airlineIcons.value(operatorICAO); + } + else + { + QIcon *icon = nullptr; + QString path = getAirlineIconPath(operatorICAO); + if (!path.isEmpty()) + { + icon = new QIcon(path); + m_airlineIcons.insert(operatorICAO, icon); + } + else + { + if (!m_airlineMissingIcons.contains(operatorICAO)) + { + qDebug() << "AircraftInformation: No airline logo for " << operatorICAO; + m_airlineMissingIcons.insert(operatorICAO, true); + } + } + return icon; + } +} + +QString AircraftInformation::getSideviewIconPath(const QString ®istration, const QString &operatorICAO, const QString &modelICAO) +{ + QString p1 = QString(":/sideviews/%1.png").arg(registration); + QResource r1(p1); + if (r1.isValid()) { + return p1; + } + + QString opModel = operatorICAO + modelICAO; + QString p2 = QString(":/sideviews/%1.png").arg(opModel); + QResource r2(p2); + if (r2.isValid()) { + return p2; + } + + QString p3 = QString(":/sideviews/%1.png").arg(modelICAO); + QResource r3(p3); + if (r3.isValid()) { + return p3; + } + + return QString(); +} + +QIcon *AircraftInformation::getSideviewIcon(const QString ®istration, const QString &operatorICAO, const QString &modelICAO) +{ + QIcon *icon = nullptr; + + if (m_sideviewIcons.contains(registration)) { + return m_sideviewIcons.value(registration); + } + QString p1 = QString(":/sideviews/%1.png").arg(registration); + QResource r1(p1); + if (r1.isValid()) + { + icon = new QIcon(p1); + m_sideviewIcons.insert(registration, icon); + return icon; + } + + QString opModel = operatorICAO + modelICAO; + if (m_sideviewIcons.contains(opModel)) { + return m_sideviewIcons.value(opModel); + } + QString p2 = QString(":/sideviews/%1.png").arg(opModel); + QResource r2(p2); + if (r2.isValid()) + { + icon = new QIcon(p2); + m_sideviewIcons.insert(opModel, icon); + return icon; + } + + if (m_sideviewIcons.contains(modelICAO)) { + return m_sideviewIcons.value(modelICAO); + } + QString p3 = QString(":/sideviews/%1.png").arg(modelICAO); + QResource r3(p3); + if (r3.isValid()) + { + icon = new QIcon(p3); + m_sideviewIcons.insert(modelICAO, icon); + return icon; + } + + qDebug() << "AircraftInformation: No sideview for " << opModel; + return nullptr; +} + +QString AircraftInformation::getFlagIconPath(const QString &country) +{ + QString endPath = QString("/flags/%1.bmp").arg(country); + // Try in user directory first, so they can customise + QString userIconPath = OsnDB::getDataDir() + endPath; + QFile file(userIconPath); + if (file.exists()) + { + return userIconPath; + } + else + { + // Try in resources + QString resourceIconPath = ":" + endPath; + QResource resource(resourceIconPath); + if (resource.isValid()) + { + return resourceIconPath; + } + } + return QString(); +} + +QString AircraftInformation::getFlagIconURL(const QString &country) +{ + return resourcePathToURL(getFlagIconPath(country)); +} + +QString AircraftInformation::resourcePathToURL(const QString &path) +{ + if (path.startsWith(':')) { + return "qrc://" + path.mid(1); + } + return path; +} + +QIcon *AircraftInformation::getFlagIcon(const QString &country) +{ + if (m_flagIcons.contains(country)) + { + return m_flagIcons.value(country); + } + else + { + QIcon *icon = nullptr; + QString path = getFlagIconPath(country); + if (!path.isEmpty()) + { + icon = new QIcon(path); + m_flagIcons.insert(country, icon); + } + return icon; + } +} + diff --git a/android/app/src/main/cpp/util/osndb.h b/android/app/src/main/cpp/util/osndb.h new file mode 100644 index 0000000..2919090 --- /dev/null +++ b/android/app/src/main/cpp/util/osndb.h @@ -0,0 +1,183 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_OSNDB_H +#define INCLUDE_OSNDB_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/csv.h" +#include "util/httpdownloadmanager.h" +#include "export.h" + +//#define OSNDB_URL "https://s3.opensky-network.org/data-samples/metadata/aircraftDatabase.zip" +#define OSNDB_URL "https://sdrangel.org/downloads/aircraftDatabase.zip" + +struct SDRBASE_API AircraftRouteInformation { + QString m_callsign; // Aircraft callsign + QString m_dep; // Departure airport ICAO + QString m_arr; // Arrival airport ICAO + QString m_stops; // Airport ICAO of stops on route '-' separated list +}; + +struct SDRBASE_API AircraftInformation { + + int m_icao; + QString m_registration; + QString m_manufacturerName; + QString m_model; + QString m_type; + QString m_owner; + QString m_operator; + QString m_operatorICAO; + QString m_registered; + + static void init() + { + QMutexLocker locker(&m_mutex); + if (!m_prefixMap) + { + // Read registration prefix to country map + m_prefixMap = CSV::hash(":/flags/regprefixmap.csv"); + // Read operator air force to military map + m_militaryMap = CSV::hash(":/flags/militarymap.csv"); + } + } + + + // Get flag based on registration + QString getFlag() const; + + static QString getAirlineIconPath(const QString &operatorICAO); + + // Try to find an airline logo based on ICAO + static QIcon *getAirlineIcon(const QString &operatorICAO); + + static QIcon *getSideviewIcon(const QString ®istration, const QString &operatorICAO, const QString &modelICAO); + static QString getSideviewIconPath(const QString ®istration, const QString &operatorICAO, const QString &modelICAO); + + static QString getFlagIconPath(const QString &country); + static QString getFlagIconURL(const QString &country); + + // Try to find an flag logo based on a country + static QIcon *getFlagIcon(const QString &country); + + static QString resourcePathToURL(const QString &path); + +private: + + static QHash m_airlineIcons; // Hashed on airline ICAO + static QHash m_airlineMissingIcons; // Hash containing which ICAOs we don't have icons for + static QHash m_sideviewIcons; + static QHash m_flagIcons; // Hashed on country + static QHash *m_prefixMap; // Registration to country (flag name) + static QHash *m_militaryMap; // Operator airforce to military (flag name) + static QMutex m_mutex; + +}; + +class SDRBASE_API OsnDB : public QObject { + Q_OBJECT + +public: + + OsnDB(QObject* parent=nullptr); + ~OsnDB(); + + void downloadAircraftInformation(); + + static QSharedPointer> getAircraftInformation(); + static QSharedPointer> getAircraftInformationByReg(); + static QSharedPointer> getAircraftRouteInformation(); + + static QString getZipFilename() + { + return getDataDir() + "/aircraftDatabase.zip"; + } + + static QString getOSNDBFilename() + { + return getDataDir() + "/aircraftDatabase.csv"; + } + + static QString getDataDir() + { + // Get directory to store app data in (aircraft & airport databases and user-definable icons) + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + // First dir is writable + return locations[0]; + } + +private: + HttpDownloadManager m_dlm; + + static QSharedPointer> m_aircraftInformation; + static QSharedPointer> m_aircraftInformationByReg; + static QSharedPointer> m_aircraftRouteInformation; + static QDateTime m_modifiedDateTime; + + // Write a reduced size and validated version of the DB, so it loads quicker + static bool writeFastDB(const QString &filename, const QHash *aircraftInfo); + + // Read smaller CSV file with no validation. Takes about 0.5s instead of 2s. + static QHash *readFastDB(const QString &filename); + + static QHash *readRouteDB(const QString &filename); + + // Read OpenSky Network CSV file + // This is large and contains lots of data we don't want, so we convert to + // a smaller version to speed up loading time + // Note that we use C file functions rather than QT, as these are ~30% faster + // and the QT version seemed to occasionally crash + static QHash *readOSNDB(const QString &filename); + + static QString getFastDBFilename() + { + return getDataDir() + "/aircraftDatabaseFast.csv"; + } + + static QString getRouteDBFilename() + { + return getDataDir() + "/aircraftRouteDatabase.csv"; + } + + // Create hash table using registration as key + static QHash *registrationHash(const QHash *in); + +private slots: + void downloadFinished(const QString& filename, bool success); + +signals: + void downloadingURL(const QString& url); + void downloadError(const QString& error); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadAircraftInformationFinished(); + +}; + +#endif + diff --git a/android/app/src/main/cpp/util/ourairportsdb.cpp b/android/app/src/main/cpp/util/ourairportsdb.cpp new file mode 100644 index 0000000..7a05e0a --- /dev/null +++ b/android/app/src/main/cpp/util/ourairportsdb.cpp @@ -0,0 +1,420 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Daniele Forsi // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "util/ourairportsdb.h" + +QMutex OurAirportsDB::m_mutex; +QSharedPointer> OurAirportsDB::m_airportsById; +QSharedPointer> OurAirportsDB::m_airportsByIdent; +QDateTime OurAirportsDB::m_modifiedDateTime; + +AirportInformation::~AirportInformation() +{ + qDeleteAll(m_frequencies); +} + +QString AirportInformation::getImageName() const +{ + switch (m_type) + { + case AirportType::Large: + return "airport_large.png"; + case AirportType::Medium: + return "airport_medium.png"; + case AirportType::Heliport: + return "heliport.png"; + default: + return "airport_small.png"; + } +} + +OurAirportsDB::OurAirportsDB(QObject *parent) : + QObject(parent) +{ + connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OurAirportsDB::downloadFinished); +} + +OurAirportsDB::~OurAirportsDB() +{ + disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OurAirportsDB::downloadFinished); +} + +void OurAirportsDB::downloadAirportInformation() +{ + // Download airport database + QString urlString = AIRPORTS_URL; + QUrl dbURL(urlString); + qDebug() << "OurAirportsDB::downloadAirportInformation: Downloading " << urlString; + emit downloadingURL(urlString); + QNetworkReply *reply = m_dlm.download(dbURL, OurAirportsDB::getAirportDBFilename()); + connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) { + emit downloadProgress(bytesRead, totalBytes); + }); +} + +void OurAirportsDB::downloadFinished(const QString& filename, bool success) +{ + if (!success) + { + qWarning() << "OurAirportsDB::downloadFinished: Failed to download: " << filename; + emit downloadError(QString("Failed to download: %1").arg(filename)); + } + else if (filename == OurAirportsDB::getAirportDBFilename()) + { + // Now download airport frequencies + QString urlString = AIRPORT_FREQUENCIES_URL; + QUrl dbURL(urlString); + qDebug() << "OurAirportsDB::downloadFinished: Downloading " << urlString; + emit downloadingURL(urlString); + QNetworkReply *reply = m_dlm.download(dbURL, OurAirportsDB::getAirportFrequenciesDBFilename()); + connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) { + emit downloadProgress(bytesRead, totalBytes); + }); + } + else if (filename == OurAirportsDB::getAirportFrequenciesDBFilename()) + { + emit downloadAirportInformationFinished(); + } + else + { + qDebug() << "OurAirportsDB::downloadFinished: Unexpected filename: " << filename; + emit downloadError(QString("Unexpected filename: %1").arg(filename)); + } +} + +QSharedPointer> OurAirportsDB::getAirportsById() +{ + QMutexLocker locker(&m_mutex); + + readDB(); + return m_airportsById; +} + +QSharedPointer> OurAirportsDB::getAirportsByIdent() +{ + QMutexLocker locker(&m_mutex); + + readDB(); + return m_airportsByIdent; +} + +void OurAirportsDB::readDB() +{ + QFileInfo airportDBFileInfo(getAirportDBFilename()); + QDateTime airportDBModifiedDateTime = airportDBFileInfo.lastModified(); + + if (!m_airportsById || (airportDBModifiedDateTime > m_modifiedDateTime)) + { + // Using shared pointer, so old object, if it exists, will be deleted, when no longer user + m_airportsById = QSharedPointer>(OurAirportsDB::readAirportsDB(getAirportDBFilename())); + if (m_airportsById != nullptr) + { + OurAirportsDB::readFrequenciesDB(getAirportFrequenciesDBFilename(), m_airportsById.get()); + m_airportsByIdent = QSharedPointer>(identHash(m_airportsById.get())); + } + + m_modifiedDateTime = airportDBModifiedDateTime; + } +} + +QString OurAirportsDB::getDataDir() +{ + // Get directory to store app data in + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + // First dir is writable + return locations[0]; +} + +QString OurAirportsDB::getAirportDBFilename() +{ + return getDataDir() + "/airportDatabase.csv"; +} + +QString OurAirportsDB::getAirportFrequenciesDBFilename() +{ + return getDataDir() + "/airportFrequenciesDatabase.csv"; +} + +QString OurAirportsDB::trimQuotes(const QString s) +{ + if (s.startsWith('\"') && s.endsWith('\"')) { + return s.mid(1, s.size() - 2); + } else { + return s; + } +} + +// Read OurAirport's airport CSV file +// See comments for readOSNDB +QHash *OurAirportsDB::readAirportsDB(const QString &filename) +{ + int cnt = 0; + QHash *airportInfo = nullptr; + + // Column numbers used for the data as of 2020/10/28 + int idCol = 0; + int identCol = 1; + int typeCol = 2; + int nameCol = 3; + int latitudeCol = 4; + int longitudeCol = 5; + int elevationCol = 6; + + qDebug() << "OurAirportsDB::readAirportsDB: " << filename; + + FILE *file; + QByteArray utfFilename = filename.toUtf8(); + QLocale cLocale(QLocale::C); + if ((file = fopen(utfFilename.constData(), "r")) != NULL) + { + char row[2048]; + + if (fgets(row, sizeof(row), file)) + { + airportInfo = new QHash(); + airportInfo->reserve(70000); + + // Read header + int idx = 0; + char *p = strtok(row, ","); + while (p != NULL) + { + if (!strcmp(p, "id")) + idCol = idx; + else if (!strcmp(p, "ident")) + identCol = idx; + else if (!strcmp(p, "type")) + typeCol = idx; + else if (!strcmp(p, "name")) + nameCol = idx; + else if (!strcmp(p, "latitude_deg")) + latitudeCol = idx; + else if (!strcmp(p, "longitude_deg")) + longitudeCol = idx; + else if (!strcmp(p, "elevation_ft")) + elevationCol = idx; + p = strtok(NULL, ","); + idx++; + } + // Read data + while (fgets(row, sizeof(row), file)) + { + int id = 0; + char *idString = NULL; + char *ident = NULL; + size_t identLen = 0; + char *type = NULL; + size_t typeLen = 0; + char *name = NULL; + size_t nameLen = 0; + float latitude = 0.0f; + char *latitudeString = NULL; + size_t latitudeLen = 0; + float longitude = 0.0f; + char *longitudeString = NULL; + size_t longitudeLen = 0; + float elevation = 0.0f; + char *elevationString = NULL; + size_t elevationLen = 0; + + p = strtok(row, ","); + idx = 0; + while (p != NULL) + { + // Read strings, stripping quotes + if (idx == idCol) + { + idString = p; + idString[strlen(idString)] = '\0'; + id = strtol(idString, NULL, 10); + } + else if (idx == identCol) + { + ident = p+1; + identLen = strlen(ident)-1; + ident[identLen] = '\0'; + } + else if (idx == typeCol) + { + type = p+1; + typeLen = strlen(type)-1; + type[typeLen] = '\0'; + } + else if (idx == nameCol) + { + name = p+1; + nameLen = strlen(name)-1; + name[nameLen] = '\0'; + } + else if (idx == latitudeCol) + { + latitudeString = p; + latitudeLen = strlen(latitudeString)-1; + latitudeString[latitudeLen] = '\0'; + latitude = cLocale.toFloat(latitudeString); + } + else if (idx == longitudeCol) + { + longitudeString = p; + longitudeLen = strlen(longitudeString)-1; + longitudeString[longitudeLen] = '\0'; + longitude = cLocale.toFloat(longitudeString); + } + else if (idx == elevationCol) + { + elevationString = p; + elevationLen = strlen(elevationString)-1; + elevationString[elevationLen] = '\0'; + elevation = cLocale.toFloat(elevationString); + } + p = strtok(NULL, ","); + idx++; + } + + // Only create the entry if we have some interesting data + if (((latitude != 0.0f) || (longitude != 0.0f)) && (type && strcmp(type, "closed"))) + { + AirportInformation *airport = new AirportInformation(); + airport->m_id = id; + airport->m_ident = QString(ident); + if (!strcmp(type, "small_airport")) { + airport->m_type = AirportInformation::AirportType::Small; + } else if (!strcmp(type, "medium_airport")) { + airport->m_type = AirportInformation::AirportType::Medium; + } else if (!strcmp(type, "large_airport")) { + airport->m_type = AirportInformation::AirportType::Large; + } else if (!strcmp(type, "heliport")) { + airport->m_type = AirportInformation::AirportType::Heliport; + } + airport->m_name = QString(name); + airport->m_latitude = latitude; + airport->m_longitude = longitude; + airport->m_elevation = elevation; + airportInfo->insert(id, airport); + cnt++; + } + } + } + fclose(file); + } + else + qDebug() << "OurAirportsDB::readAirportsDB: Failed to open " << filename; + + qDebug() << "OurAirportsDB::readAirportsDB: Read " << cnt << " airports"; + + return airportInfo; +} + +// Create hash table using ICAO identifier as key +QHash *OurAirportsDB::identHash(QHash *in) +{ + QHash *out = new QHash(); + QHashIterator i(*in); + while (i.hasNext()) + { + i.next(); + AirportInformation *info = i.value(); + out->insert(info->m_ident, info); + } + return out; +} + +// Read OurAirport's airport frequencies CSV file +bool OurAirportsDB::readFrequenciesDB(const QString &filename, QHash *airportInfo) +{ + int cnt = 0; + + // Column numbers used for the data as of 2020/10/28 + int airportRefCol = 1; + int typeCol = 3; + int descriptionCol = 4; + int frequencyCol = 5; + + qDebug() << "OurAirportsDB::readFrequenciesDB: " << filename; + + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) + { + QList colNames; + int idx; + + // Read header + if (!file.atEnd()) + { + QByteArray row = file.readLine().trimmed(); + colNames = row.split(','); + // Work out which columns the data is in, based on the headers + idx = colNames.indexOf("airport_ref"); + if (idx >= 0) + airportRefCol = idx; + idx = colNames.indexOf("type"); + if (idx >= 0) + typeCol = idx; + idx = colNames.indexOf("description"); + if (idx >= 0) + descriptionCol = idx; + idx = colNames.indexOf("frequency_mhz"); + if (idx >= 0) + frequencyCol = idx; + } + // Read data + while (!file.atEnd()) + { + QByteArray row = file.readLine(); + QList cols = row.split(','); + + bool ok = false; + int airportRef = cols[airportRefCol].toInt(&ok, 10); + if (ok) + { + if (airportInfo->contains(airportRef)) + { + QString type = trimQuotes(cols[typeCol]); + QString description = trimQuotes(cols[descriptionCol]); + float frequency = cols[frequencyCol].toFloat(); + + AirportInformation::FrequencyInformation *frequencyInfo = new AirportInformation::FrequencyInformation(); + frequencyInfo->m_type = type; + frequencyInfo->m_description = description; + frequencyInfo->m_frequency = frequency; + airportInfo->value(airportRef)->m_frequencies.append(frequencyInfo); + cnt++; + } + } + } + file.close(); + } + else + { + qDebug() << "Failed to open " << filename << " " << file.errorString(); + return false; + } + + qDebug() << "OurAirportsDB::readFrequenciesDB: - read " << cnt << " airports"; + + return true; +} + diff --git a/android/app/src/main/cpp/util/ourairportsdb.h b/android/app/src/main/cpp/util/ourairportsdb.h new file mode 100644 index 0000000..c24179a --- /dev/null +++ b/android/app/src/main/cpp/util/ourairportsdb.h @@ -0,0 +1,118 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_OURAIRPORTSDB_H +#define INCLUDE_UTIL_OURAIRPORTSDB_H + +#include +#include +#include +#include + +#include +#include + +#include "util/httpdownloadmanager.h" +#include "export.h" + +#define AIRPORTS_URL "https://davidmegginson.github.io/ourairports-data/airports.csv" +#define AIRPORT_FREQUENCIES_URL "https://davidmegginson.github.io/ourairports-data/airport-frequencies.csv" + +class SDRBASE_API AirportInformation { + +public: + enum AirportType { + Small, + Medium, + Large, + Heliport + }; + + struct FrequencyInformation { + QString m_type; + QString m_description; + float m_frequency; // In MHz + }; + + int m_id; + QString m_ident; + AirportType m_type; + QString m_name; + float m_latitude; + float m_longitude; + float m_elevation; + QVector m_frequencies; + + ~AirportInformation(); + QString getImageName() const; + +}; + +class SDRBASE_API OurAirportsDB : public QObject { + Q_OBJECT + +public: + + OurAirportsDB(QObject *parent=nullptr); + ~OurAirportsDB(); + + void downloadAirportInformation(); + + static QSharedPointer> getAirportsById(); + static QSharedPointer> getAirportsByIdent(); + +private: + HttpDownloadManager m_dlm; + + static QSharedPointer> m_airportsById; + static QSharedPointer> m_airportsByIdent; + static QDateTime m_modifiedDateTime; + + static QMutex m_mutex; + + static QString getDataDir(); + + static void readDB(); + + // Read OurAirport's airport CSV file + // See comments for readOSNDB + static QHash *readAirportsDB(const QString &filename); + + // Create hash table using ICAO identifier as key + static QHash *identHash(QHash *in); + + // Read OurAirport's airport frequencies CSV file + static bool readFrequenciesDB(const QString &filename, QHash *airportInfo); + + static QString trimQuotes(const QString s); + + static QString getAirportDBFilename(); + static QString getAirportFrequenciesDBFilename(); + +private slots: + void downloadFinished(const QString& filename, bool success); + +signals: + void downloadingURL(const QString& url); + void downloadError(const QString& error); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadAirportInformationFinished(); + +}; + +#endif + diff --git a/android/app/src/main/cpp/util/peakfinder.cpp b/android/app/src/main/cpp/util/peakfinder.cpp new file mode 100644 index 0000000..9a7ebfe --- /dev/null +++ b/android/app/src/main/cpp/util/peakfinder.cpp @@ -0,0 +1,60 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// Find peaks in a series of real values // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "peakfinder.h" + +PeakFinder::PeakFinder() : + m_prevValue(0.0), + m_index(0) +{} + +PeakFinder::~PeakFinder() +{} + +void PeakFinder::init(Real value) +{ + m_prevValue = value; + m_peaks.clear(); + m_index = 0; +} + +void PeakFinder::push(Real value, bool last) +{ + Real diff = value - m_prevValue; + + if (diff < 0) { + m_peaks.push_back({m_prevValue, m_index}); + } else if (last) { + m_peaks.push_back({value, m_index}); + } + + m_prevValue = value; + m_index++; +} + +void PeakFinder::sortPeaks() +{ + std::sort(m_peaks.rbegin(), m_peaks.rend()); // descending order of values +} diff --git a/android/app/src/main/cpp/util/peakfinder.h b/android/app/src/main/cpp/util/peakfinder.h new file mode 100644 index 0000000..41d2073 --- /dev/null +++ b/android/app/src/main/cpp/util/peakfinder.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// // +// Find peaks in a series of real values // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_PEAKFINDER_H_ +#define SDRBASE_UTIL_PEAKFINDER_H_ + +#include +#include + +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API PeakFinder { +public: + PeakFinder(); + ~PeakFinder(); + + void init(Real value); + void push(Real value, bool last=false); + void sortPeaks(); + const std::vector>& getPeaks() { return m_peaks; } + +private: + Real m_prevValue; + int m_index; + std::vector> m_peaks; //!< index, value + +}; + + +#endif // SDRBASE_UTIL_PEAKFINDER_H_ diff --git a/android/app/src/main/cpp/util/planespotters.cpp b/android/app/src/main/cpp/util/planespotters.cpp new file mode 100644 index 0000000..704e2f9 --- /dev/null +++ b/android/app/src/main/cpp/util/planespotters.cpp @@ -0,0 +1,180 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "planespotters.h" + +#include +#include +#include +#include +#include +#include +#include + +PlaneSpotters::PlaneSpotters() +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &PlaneSpotters::handleReply + ); +} + +PlaneSpotters::~PlaneSpotters() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &PlaneSpotters::handleReply + ); + delete m_networkManager; +} + +void PlaneSpotters::getAircraftPhoto(const QString& icao) +{ + if (m_photos.contains(icao)) + { + emit aircraftPhoto(m_photos[icao]); + } + else + { + // Create a new photo hash table entry + PlaneSpottersPhoto *photo = new PlaneSpottersPhoto(); + photo->m_id = icao; + m_photos.insert(icao, photo); + + // Fetch from network + QUrl url(QString("https://api.planespotters.net/pub/photos/hex/%1").arg(icao)); + QNetworkRequest request(url); + request.setRawHeader("User-Agent", "SDRangel/1.0"); // Get 403 error without this + request.setOriginatingObject(photo); + m_networkManager->get(request); + } +} + +void PlaneSpotters::getAircraftPhotoByRegistration(const QString& registration) +{ + if (m_photos.contains(registration)) + { + emit aircraftPhoto(m_photos[registration]); + } + else + { + // Create a new photo hash table entry + PlaneSpottersPhoto *photo = new PlaneSpottersPhoto(); + photo->m_id = registration; + m_photos.insert(registration, photo); + + // Fetch from network + QUrl url(QString("https://api.planespotters.net/pub/photos/reg/%1").arg(registration)); + QNetworkRequest request(url); + request.setRawHeader("User-Agent", "SDRangel/1.0"); // Get 403 error without this + request.setOriginatingObject(photo); + m_networkManager->get(request); + } +} + +void PlaneSpotters::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + if (reply->url().path().startsWith("/pub/photos/hex") || reply->url().path().startsWith("/pub/photos/reg")) { + parseJson((PlaneSpottersPhoto *)reply->request().originatingObject(), reply->readAll()); + } else { + parsePhoto((PlaneSpottersPhoto *)reply->request().originatingObject(), reply->readAll()); + } + } + else + { + qDebug() << "PlaneSpotters::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "PlaneSpotters::handleReply: reply is null"; + } +} + +void PlaneSpotters::parsePhoto(PlaneSpottersPhoto *photo, QByteArray bytes) +{ + if (!photo->m_pixmap.loadFromData(bytes)) { + qDebug() << "PlaneSpotters::parsePhoto: Failed to loadFromData - " << bytes.size() << "bytes of data" ; + } + emit aircraftPhoto(photo); +} + +void PlaneSpotters::parseJson(PlaneSpottersPhoto *photo, QByteArray bytes) +{ + QJsonDocument document = QJsonDocument::fromJson(bytes); + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("photos"))) + { + QJsonArray photos = obj.value(QStringLiteral("photos")).toArray(); + if (photos.size() > 0) + { + QJsonObject photoObj = photos[0].toObject(); + + if (photoObj.contains(QStringLiteral("id"))) { + photo->m_link = photoObj.value(QStringLiteral("id")).toString(); + } + if (photoObj.contains(QStringLiteral("thumbnail"))) + { + QJsonObject thumbnailObj = photoObj.value(QStringLiteral("thumbnail")).toObject(); + photo->m_thumbnail.m_src = thumbnailObj.value(QStringLiteral("src")).toString(); + QJsonObject sizeObj = thumbnailObj.value(QStringLiteral("size")).toObject(); + photo->m_thumbnail.m_width = sizeObj.value(QStringLiteral("width")).toInt(); + photo->m_thumbnail.m_height = sizeObj.value(QStringLiteral("width")).toInt(); + } + if (photoObj.contains(QStringLiteral("link"))) { + photo->m_link = photoObj.value(QStringLiteral("link")).toString(); + } + if (photoObj.contains(QStringLiteral("photographer"))) { + photo->m_photographer = photoObj.value(QStringLiteral("photographer")).toString(); + } + + if (!photo->m_thumbnail.m_src.isEmpty()) + { + QUrl url(photo->m_thumbnail.m_src); + QNetworkRequest request(url); + request.setOriginatingObject(photo); + m_networkManager->get(request); + } + } + else + { + qDebug() << "PlaneSpotters::handleReply: data array is empty"; + } + } + else + { + qDebug() << "PlaneSpotters::handleReply: Object doesn't contain data: " << obj; + } + } + else + { + qDebug() << "PlaneSpotters::handleReply: Document is not an object: " << document; + } +} diff --git a/android/app/src/main/cpp/util/planespotters.h b/android/app/src/main/cpp/util/planespotters.h new file mode 100644 index 0000000..d5bc079 --- /dev/null +++ b/android/app/src/main/cpp/util/planespotters.h @@ -0,0 +1,82 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PLANESPOTTERS_H +#define INCLUDE_PLANESPOTTERS_H + +#include +#include +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +class SDRBASE_API PlaneSpottersPhoto : public QObject { + Q_OBJECT + + struct Thumbnail { + QString m_src; + int m_width; + int m_height; + }; + +public: + QString m_id; + Thumbnail m_thumbnail; + Thumbnail m_largeThumbnail; + QString m_link; + QString m_photographer; + QPixmap m_pixmap; +}; + +// PlaneSpotters API wrapper +// Allows downloading of images of aircraft from https://www.planespotters.net/ +// Note that API terms of use require us not to cache images on disk +class SDRBASE_API PlaneSpotters : public QObject +{ + Q_OBJECT + +public: + PlaneSpotters(); + ~PlaneSpotters(); + + + void getAircraftPhoto(const QString& icao); + void getAircraftPhotoByRegistration(const QString& registration); + +signals: + void aircraftPhoto(const PlaneSpottersPhoto *photo); // Called when photo is available. + +private: + void parseJson(PlaneSpottersPhoto *photo, QByteArray bytes); + void parsePhoto(PlaneSpottersPhoto *photo, QByteArray bytes); + + QNetworkAccessManager *m_networkManager; + QHash m_photos; + +public slots: + void handleReply(QNetworkReply* reply); + +}; + +#endif /* INCLUDE_PLANESPOTTERS_H */ diff --git a/android/app/src/main/cpp/util/png.cpp b/android/app/src/main/cpp/util/png.cpp new file mode 100644 index 0000000..f9aaadd --- /dev/null +++ b/android/app/src/main/cpp/util/png.cpp @@ -0,0 +1,311 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "png.h" + +#include +#include +#include +#include + +PNG::PNG() +{ +} + +PNG::PNG(QByteArray data) : + m_bytes(data), + m_width(0), + m_height(0) +{ + int idx = findChunk("IHDR"); + if (idx >= 0) + { + m_width = getInt(idx + 8); + m_height = getInt(idx + 12); + } + else + { + qDebug() << "PNG: No IHDR found"; + } +} + +void PNG::appendSignature() +{ + m_bytes.append(m_signature); +} + +void PNG::appendEnd() +{ + QByteArray ba; + appendChunk("IEND", ba); +} + +void PNG::appendChunk(const char *type, QByteArray chunk) +{ + appendInt(chunk.size()); + appendInt(typeStringToInt(type)); + m_bytes.append(chunk); + appendInt(crc(type, chunk)); // CRC type and data, but not length +} + +void PNG::append(QByteArray data) +{ + m_bytes.append(data); +} + +void PNG::appendInt(QByteArray& ba, quint32 value) +{ + // Network byte order + ba.append((value >> 24) & 0xff); + ba.append((value >> 16) & 0xff); + ba.append((value >> 8) & 0xff); + ba.append((value) & 0xff); +} + +void PNG::appendShort(QByteArray& ba, quint16 value) +{ + // Network byte order + ba.append((value >> 8) & 0xff); + ba.append((value) & 0xff); +} + +void PNG::appendInt(quint32 value) +{ + appendInt(m_bytes, value); +} + +qint32 PNG::getInt(int index) +{ + qint32 v = 0; + for (int i = 0; i < 4; i++) { + v |= (m_bytes[index+i] & 0xff) << ((3-i)*8); + } + return v; +} + +qint32 PNG::crc(const char *type, const QByteArray data) +{ + m_crc.init(); + m_crc.calculate((const uint8_t *)type, 4); + m_crc.calculate((const uint8_t *)data.data(), data.size()); + return m_crc.get(); +} + +qint32 PNG::typeStringToInt(const char *header) +{ + quint32 v = 0; + for (int i = 0; i < 4; i++) { + v |= header[i] << ((3-i)*8); + } + return v; +} + +QString PNG::intToTypeString(quint32 type) +{ + QString s; + for (int i = 0; i < 4; i++) + { + char c = (type >> ((3-i)*8)) & 0xff; + s.append(c); + } + return s; +} + +// Animation control chunk for APNGs (loops=0 is infinite) +void PNG::appendacTL(int frames, quint32 loops) +{ + QByteArray ba; + appendInt(ba, frames); + appendInt(ba, loops); + appendChunk("acTL", ba); +} + +// Frame control chunk for APNGs +void PNG::appendfcTL(quint32 seqNo, quint32 width, quint32 height, int fps, quint32 xOffset, quint32 yOffset) +{ + QByteArray ba; + appendInt(ba, seqNo); + appendInt(ba, width); + appendInt(ba, height); + appendInt(ba, xOffset); + appendInt(ba, yOffset); + appendShort(ba, 1); + appendShort(ba, fps); + ba.append((char)0); // No disposal + ba.append((char)0); // Overwrite previous image + appendChunk("fcTL", ba); +} + +// Animation frame data +void PNG::appendfdAT(quint32 seqNo, const QByteArray& data) +{ + QByteArray ba; + appendInt(ba, seqNo); + ba.append(data); + appendChunk("fdAT", ba); +} + +QByteArray PNG::data() +{ + return m_bytes; +} + +bool PNG::checkSignature() +{ + return m_bytes.startsWith(m_signature); +} + +int PNG::findChunk(const char *type, int startIndex) +{ + if ((startIndex == 0) && !checkSignature()) + { + qDebug() << "PNG::findChunk - PNG signature not found"; + return -1; + } + int i = startIndex == 0 ? m_signature.size() : startIndex; + qint32 typeInt = typeStringToInt(type); + while (i < m_bytes.size()) + { + qint32 chunkType = getInt(i+4); + if (typeInt == chunkType) { + return i; + } + qint32 length = getInt(i); + i += 12 + length; + } + return -1; +} + +// Get chunk including length, type data and CRC +QByteArray PNG::getChunk(const char *type) +{ + int start = findChunk(type); + if (start >= 0) + { + quint32 length = getInt(start); + return m_bytes.mid(start, length + 12); + } + return QByteArray(); +} + +// Get all chunks with same type +QByteArray PNG::getChunks(const char *type) +{ + int start = 0; + QByteArray bytes; + + while ((start = findChunk(type, start)) != -1) + { + quint32 length = getInt(start); + QByteArray chunk = m_bytes.mid(start, length + 12); + bytes.append(chunk); + start += chunk.size(); + } + return bytes; +} + +// Get data from chunk +QList PNG::getChunkData(const char *type) +{ + int start = 0; + QList chunks; + + while ((start = findChunk(type, start)) != -1) + { + quint32 length = getInt(start); + QByteArray chunk = m_bytes.mid(start + 8, length); + chunks.append(chunk); + start += length + 12; + } + + return chunks; +} + +quint32 PNG::getWidth() const +{ + return m_width; +} + +quint32 PNG::getHeight() const +{ + return m_height; +} + +bool APNG::addImage(const QImage& image, int fps) +{ + if (!m_ended) + { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::ReadWrite); + if (image.save(&buffer, "PNG")) + { + PNG pngIn(ba); + if (m_frame == 0) + { + m_png.append(pngIn.getChunk("IHDR")); + m_png.appendacTL(m_frames); + m_png.appendfcTL(m_seqNo++, pngIn.getWidth(), pngIn.getHeight(), fps); + // PNGs can contain multiple IDAT chunks, typically each limited to 8kB + m_png.append(pngIn.getChunks("IDAT")); + } + else + { + m_png.appendfcTL(m_seqNo++, pngIn.getWidth(), pngIn.getHeight(), fps); + QList data = pngIn.getChunkData("IDAT"); + for (int i = 0; i < data.size(); i++) { + m_png.appendfdAT(m_seqNo++, data[i]); + } + } + m_frame++; + return true; + } + else + { + qDebug() << "APNG::addImage - Failed to save image to PNG"; + return false; + } + } + else + { + qDebug() << "APNG::addImage - Call to addImage after IEND added"; + return false; + } +} + +bool APNG::save(const QString& fileName) +{ + if (!m_ended) + { + if (m_frame != m_frames) { + qDebug() << "APNG::save - " << m_frame << " frames added out of expected " << m_frames; + } + m_png.appendEnd(); + m_ended = true; + } + QFile animFile(fileName); + if (animFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + animFile.write(m_png.data()); + animFile.close(); + return true; + } + else + { + return false; + } +} diff --git a/android/app/src/main/cpp/util/png.h b/android/app/src/main/cpp/util/png.h new file mode 100644 index 0000000..5d16294 --- /dev/null +++ b/android/app/src/main/cpp/util/png.h @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PNG_H +#define INCLUDE_PNG_H + +#include +#include + +#include "export.h" + +#include "util/crc.h" + +// PNG (Portable Network Graphics) utility code +// Contains just enough functionality to support assembling APNG files (Animated PNGs) +// from multiple PNGs (which can be created using Qt) +class SDRBASE_API PNG { +public: + + PNG(); + PNG(QByteArray data); + void appendSignature(); + void appendEnd(); + void appendChunk(const char *type, QByteArray chunk); + void append(QByteArray data); + void appendInt(QByteArray& ba, quint32 value); + void appendShort(QByteArray& ba, quint16 value); + void appendInt(quint32 value); + qint32 getInt(int index); + qint32 crc(const char *type, const QByteArray data); + qint32 typeStringToInt(const char *header); + QString intToTypeString(quint32 type); + void appendacTL(int frames, quint32 loops=0); + void appendfcTL(quint32 seqNo, quint32 width, quint32 height, int fps, quint32 xOffset=0, quint32 yOffset=0); + void appendfdAT(quint32 seqNo, const QByteArray& data); + QByteArray data(); + bool checkSignature(); + int findChunk(const char *type, int startIndex=0); + QByteArray getChunk(const char *type); + QByteArray getChunks(const char *type); + QList getChunkData(const char *type); + quint32 getWidth() const; + quint32 getHeight() const; + +private: + + QByteArray m_signature = QByteArrayLiteral("\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"); + QByteArray m_bytes; + crc32 m_crc; + quint32 m_width; + quint32 m_height; + +}; + +// Animated PNG +class SDRBASE_API APNG { + +public: + + APNG(int frames) : + m_frames(frames), + m_frame(0), + m_seqNo(0), + m_ended(false) + { + m_png.appendSignature(); + } + + bool addImage(const QImage& image, int fps=5); + bool save(const QString& fileName); + +private: + + PNG m_png; + int m_frames; //!< Total number of frames in animation + int m_frame; //!< Current frame number + int m_seqNo; //!< Chunk sequence number + bool m_ended; //!< IEND chunk has added + +}; + +#endif // INCLUDE_PNG_H diff --git a/android/app/src/main/cpp/util/popcount.h b/android/app/src/main/cpp/util/popcount.h new file mode 100644 index 0000000..974a5b0 --- /dev/null +++ b/android/app/src/main/cpp/util/popcount.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_POPCOUNT_H +#define INCLUDE_POPCOUNT_H + +// Population count - count number of bits +#if defined(__cplusplus) && (__cplusplus >= 202002L) +#include +#define popcount std::popcount +#elif defined (__GNUC__) +#define popcount __builtin_popcount +#elif defined(_MSC_VER) +#include +#define popcount __popcnt +#else +static int popcount(int in) +{ + int cnt = 0; + for(int i = 0; i < 32; i++) + cnt += (in >> i) & 1; + return cnt; +} +#endif + +#endif /* INCLUDE_POPCOUNT_H */ diff --git a/android/app/src/main/cpp/util/poweroftwo.h b/android/app/src/main/cpp/util/poweroftwo.h new file mode 100644 index 0000000..cae1f46 --- /dev/null +++ b/android/app/src/main/cpp/util/poweroftwo.h @@ -0,0 +1,45 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_POWEROFTWO_H +#define INCLUDE_POWEROFTWO_H + +#include + +// Is x a power of two +// From: https://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2 +inline bool isPowerOfTwo(uint32_t x) +{ + return (x & (x - 1)) == 0; +} + +// Calculate next power of 2 lower than x +// From: https://stackoverflow.com/questions/2679815/previous-power-of-2 +inline uint32_t lowerPowerOfTwo(uint32_t x) +{ + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + return x - (x >> 1); +} + +#endif /* INCLUDE_POWEROFTWO_H */ diff --git a/android/app/src/main/cpp/util/prettyprint.cpp b/android/app/src/main/cpp/util/prettyprint.cpp new file mode 100644 index 0000000..802f609 --- /dev/null +++ b/android/app/src/main/cpp/util/prettyprint.cpp @@ -0,0 +1,37 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/prettyprint.h" + +const QString EscapeColors::red = "\033[0;31m"; +const QString EscapeColors::blue = "\033[0;34m"; +const QString EscapeColors::green = "\033[0;32m"; +const QString EscapeColors::cyan = "\033[0;36m"; +const QString EscapeColors::purple = "\033[0;35m"; +const QString EscapeColors::yellow = "\033[0;33m"; + +const QString EscapeColors::lightRed = "\033[1;31m"; +const QString EscapeColors::lightBlue = "\033[1;34m"; +const QString EscapeColors::lightGreen = "\033[1;32m"; +const QString EscapeColors::lightCyan = "\033[1;36m"; +const QString EscapeColors::lightPurple = "\033[1;35m"; +const QString EscapeColors::brown = "\033[1;33m"; + +const QString EscapeColors::terminator = "\033[0m"; diff --git a/android/app/src/main/cpp/util/prettyprint.h b/android/app/src/main/cpp/util/prettyprint.h new file mode 100644 index 0000000..f333937 --- /dev/null +++ b/android/app/src/main/cpp/util/prettyprint.h @@ -0,0 +1,46 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_PRETTYPRINT_H_ +#define INCLUDE_UTIL_PRETTYPRINT_H_ + +#include + +#include "export.h" + +class SDRBASE_API EscapeColors +{ +public: + static const QString red; + static const QString blue; + static const QString green; + static const QString cyan; + static const QString purple; + static const QString yellow; + static const QString lightRed; + static const QString lightBlue; + static const QString lightGreen; + static const QString lightCyan; + static const QString lightPurple; + static const QString brown; + static const QString terminator; +}; + +#endif /* INCLUDE_UTIL_PRETTYPRINT_H_ */ diff --git a/android/app/src/main/cpp/util/profiler.cpp b/android/app/src/main/cpp/util/profiler.cpp new file mode 100644 index 0000000..4ec5a45 --- /dev/null +++ b/android/app/src/main/cpp/util/profiler.cpp @@ -0,0 +1,42 @@ +////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +////////////////////////////////////////////////////////////////////////////////// + +#include "profiler.h" + +QHash GlobalProfileData::m_profileData; +QMutex GlobalProfileData::m_mutex; + +QHash& GlobalProfileData::getProfileData() +{ + m_mutex.lock(); + return m_profileData; +} + +void GlobalProfileData::releaseProfileData() +{ + m_mutex.unlock(); +} + +void GlobalProfileData::resetProfileData() +{ + m_mutex.lock(); + m_profileData.clear(); + m_mutex.unlock(); +} diff --git a/android/app/src/main/cpp/util/profiler.h b/android/app/src/main/cpp/util/profiler.h new file mode 100644 index 0000000..2e17f23 --- /dev/null +++ b/android/app/src/main/cpp/util/profiler.h @@ -0,0 +1,123 @@ +////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_PROFILEDATA_H_ +#define INCLUDE_UTIL_PROFILEDATA_H_ + +#include +#include +#include + +#include + +#include "export.h" + +// Profiler enables runtime collection of profile data (time taken to execute code) +// that can be displayed in the GUI +// +// PROFILER_START() and PROFILER_STOP() macros should be used in the same function: +// void func() { +// PROFILER_START() +// do_something(); +// PROFILER_STOP("slow_code") +// } +// +// The parameters to PROFILER_STOP are: +// name: Name for the code being profiled. Profiles using the same name are averaged. + +#ifdef ENABLE_PROFILER +#define PROFILER_START() \ + QElapsedTimer profileTimer; \ + profileTimer.start(); +#define PROFILER_RESTART() \ + profileTimer.start(); +#define PROFILER_STOP(name) \ + { \ + qint64 timeNanoSec = profileTimer.nsecsElapsed(); \ + QHash& globalData = GlobalProfileData::getProfileData(); \ + if (!globalData.contains(name)) { \ + globalData.insert(name, ProfileData()); \ + } \ + ProfileData& profileData = globalData[name]; \ + profileData.add(timeNanoSec); \ + GlobalProfileData::releaseProfileData(); \ + } +#else +#define PROFILER_START() +#define PROFILER_RESTART() +#define PROFILER_STOP(name) +#endif + +class ProfileData +{ +public: + ProfileData() : + m_numSamples(0), + m_last(0), + m_total(0) + { } + + void reset() + { + m_numSamples = 0; + m_total = 0; + } + + void add(qint64 sample) + { + m_last = sample; + m_total += sample; + m_numSamples++; + } + + double getAverage() const + { + if (m_numSamples > 0) { + return m_total / (double)m_numSamples; + } else { + return nan(""); + } + } + + qint64 getTotal() const { return m_total; } + qint64 getLast() const { return m_last; } + quint64 getNumSamples() const { return m_numSamples; } + +private: + quint64 m_numSamples; + qint64 m_last; + qint64 m_total; +}; + +// Global thread-safe profile data that can be displayed in the GUI +class SDRBASE_API GlobalProfileData +{ +public: + + // Calls to getProfileData must be paired with releaseProfileData + static QHash& getProfileData(); + static void releaseProfileData(); + static void resetProfileData(); + +private: + + static QHash m_profileData; + static QMutex m_mutex; + +}; + +#endif /* INCLUDE_UTIL_PROFILEDATA_H_ */ diff --git a/android/app/src/main/cpp/util/psk31.cpp b/android/app/src/main/cpp/util/psk31.cpp new file mode 100644 index 0000000..6bb8912 --- /dev/null +++ b/android/app/src/main/cpp/util/psk31.cpp @@ -0,0 +1,329 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "psk31.h" + +// ASCII varicode encoding +// From http://www.aintel.bi.ehu.es/psk31.html +const QStringList PSK31Varicode::m_varicode = { + "1010101011", + "1011011011", + "1011101101", + "1101110111", + "1011101011", + "1101011111", + "1011101111", + "1011111101", + "1011111111", + "11101111", + "11101", + "1101101111", + "1011011101", + "11111", + "1101110101", + "1110101011", + "1011110111", + "1011110101", + "1110101101", + "1110101111", + "1101011011", + "1101101011", + "1101101101", + "1101010111", + "1101111011", + "1101111101", + "1110110111", + "1101010101", + "1101011101", + "1110111011", + "1011111011", + "1101111111", + "1", + "111111111", + "101011111", + "111110101", + "111011011", + "1011010101", + "1010111011", + "101111111", + "11111011", + "11110111", + "101101111", + "111011111", + "1110101", + "110101", + "1010111", + "110101111", + "10110111", + "10111101", + "11101101", + "11111111", + "101110111", + "101011011", + "101101011", + "110101101", + "110101011", + "110110111", + "11110101", + "110111101", + "111101101", + "1010101", + "111010111", + "1010101111", + "1010111101", + "1111101", + "11101011", + "10101101", + "10110101", + "1110111", + "11011011", + "11111101", + "101010101", + "1111111", + "111111101", + "101111101", + "11010111", + "10111011", + "11011101", + "10101011", + "11010101", + "111011101", + "10101111", + "1101111", + "1101101", + "101010111", + "110110101", + "101011101", + "101110101", + "101111011", + "1010101101", + "111110111", + "111101111", + "111111011", + "1010111111", + "101101101", + "1011011111", + "1011", + "1011111", + "101111", + "101101", + "11", + "111101", + "1011011", + "101011", + "1101", + "111101011", + "10111111", + "11011", + "111011", + "1111", + "111", + "111111", + "110111111", + "10101", + "10111", + "101", + "110111", + "1111011", + "1101011", + "11011111", + "1011101", + "111010101", + "1010110111", + "110111011", + "1010110101", + "1011010111", + "1110110101", + "1110111101", + "1110111111", + "1111010101", + "1111010111", + "1111011011", + "1111011101", + "1111011111", + "1111101011", + "1111101101", + "1111101111", + "1111110101", + "1111110111", + "1111111011", + "1111111101", + "1111111111", + "10101010101", + "10101010111", + "10101011011", + "10101011101", + "10101011111", + "10101101011", + "10101101101", + "10101101111", + "10101110101", + "10101110111", + "10101111011", + "10101111101", + "10101111111", + "10110101011", + "10110101101", + "10110101111", + "10110110101", + "10110110111", + "10110111011", + "10110111101", + "10110111111", + "10111010101", + "10111010111", + "10111011011", + "10111011101", + "10111011111", + "10111101011", + "10111101101", + "10111101111", + "10111110101", + "10111110111", + "10111111011", + "10111111101", + "10111111111", + "11010101011", + "11010101101", + "11010101111", + "11010110101", + "11010110111", + "11010111011", + "11010111101", + "11010111111", + "11011010101", + "11011010111", + "11011011011", + "11011011101", + "11011011111", + "11011101011", + "11011101101", + "11011101111", + "11011110101", + "11011110111", + "11011111011", + "11011111101", + "11011111111", + "11101010101", + "11101010111", + "11101011011", + "11101011101", + "11101011111", + "11101101011", + "11101101101", + "11101101111", + "11101110101", + "11101110111", + "11101111011", + "11101111101", + "11101111111", + "11110101011", + "11110101101", + "11110101111", + "11110110101", + "11110110111", + "11110111011", + "11110111101", + "11110111111", + "11111010101", + "11111010111", + "11111011011", + "11111011101", + "11111011111", + "11111101011", + "11111101101", + "11111101111", + "11111110101", + "11111110111", + "11111111011", + "11111111101", + "11111111111", + "101010101011", + "101010101101", + "101010101111", + "101010110101", + "101010110111", + "101010111011", + "101010111101", + "101010111111", + "101011010101", + "101011010111", + "101011011011", + "101011011101", + "101011011111", + "101011101011", + "101011101101", + "101011101111", + "101011110101", + "101011110111", + "101011111011", + "101011111101", + "101011111111", + "101101010101", + "101101010111", + "101101011011" +}; + +PSK31Encoder::PSK31Encoder() +{ +} + +bool PSK31Encoder::encode(QChar c, unsigned &bits, unsigned int &bitCount) +{ + bits = 0; + bitCount = 0; + + unsigned char ch = (unsigned char) c.toLatin1(); + QString code = PSK31Varicode::m_varicode[ch]; + + addCode(bits, bitCount, code); + return true; +} + +void PSK31Encoder::addCode(unsigned& bits, unsigned int& bitCount, const QString& code) const +{ + int codeBits = 0; + unsigned int codeLen = code.size(); + + for (unsigned int i = 0; i < codeLen; i++) { + codeBits |= (code[i] == '1' ? 1 : 0) << i; + } + + addStartBits(bits, bitCount); + addBits(bits, bitCount, codeBits, codeLen); + addStopBits(bits, bitCount); +} + +void PSK31Encoder::addStartBits(unsigned& bits, unsigned int& bitCount) const +{ + // Start bit is 0 + addBits(bits, bitCount, 0, 1); +} + +void PSK31Encoder::addStopBits(unsigned& bits, unsigned int& bitCount) const +{ + // Stop bit is 0 + addBits(bits, bitCount, 0, 1); +} + +void PSK31Encoder::addBits(unsigned& bits, unsigned int& bitCount, int data, int count) const +{ + bits |= data << bitCount; + bitCount += count; +} diff --git a/android/app/src/main/cpp/util/psk31.h b/android/app/src/main/cpp/util/psk31.h new file mode 100644 index 0000000..2064c3e --- /dev/null +++ b/android/app/src/main/cpp/util/psk31.h @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021, 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_PSK31_H +#define INCLUDE_UTIL_PSK31_H + +#include + +#include "export.h" + +class SDRBASE_API PSK31Varicode { + +public: + + static const QStringList m_varicode; // Index with 8-bit extended-ASCII + +}; + +class SDRBASE_API PSK31Encoder { + +public: + + PSK31Encoder(); + bool encode(QChar c, unsigned& bits, unsigned int &bitCount); + +private: + + void addCode(unsigned& bits, unsigned int& bitCount, const QString& code) const; + void addStartBits(unsigned int& bits, unsigned int& bitCount) const; + void addStopBits(unsigned int& bits, unsigned int& bitCount) const; + void addBits(unsigned int& bits, unsigned int& bitCount, int data, int count) const; + +}; + +#endif // INCLUDE_UTIL_PSK31_H + diff --git a/android/app/src/main/cpp/util/radiosonde.cpp b/android/app/src/main/cpp/util/radiosonde.cpp new file mode 100644 index 0000000..124e1d4 --- /dev/null +++ b/android/app/src/main/cpp/util/radiosonde.cpp @@ -0,0 +1,785 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// Based on code and docs by einergehtnochrein, rs1729 and bazjo // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "util/radiosonde.h" +#include "util/coordinates.h" + +RS41Frame::RS41Frame(const QByteArray ba) : + m_statusValid(false), + m_batteryVoltage(0.0), + m_pcbTemperature(0), + m_humiditySensorHeating(0), + m_transmitPower(0), + m_maxSubframeNumber(0), + m_subframeNumber(0), + m_measValid(false), + m_gpsInfoValid(false), + m_posValid(false), + m_latitude(0.0), + m_longitude(0.0), + m_height(0.0), + m_bytes(ba), + m_pressureCalibrated(false), + m_temperatureCalibrated(false), + m_humidityTemperatureCalibrated(false), + m_humidityCalibrated(false) +{ + int length = getFrameLength(ba[RS41_OFFSET_FRAME_TYPE]); + for (int i = RS41_OFFSET_BLOCK_0; i < length; ) + { + uint8_t blockID = ba[i+0]; + uint8_t blockLength = ba[i+1]; + switch (blockID) + { + case RS41_ID_STATUS: + decodeStatus(ba.mid(i+2, blockLength)); + break; + case RS41_ID_MEAS: + decodeMeas(ba.mid(i+2, blockLength)); + break; + case RS41_ID_GPSINFO: + decodeGPSInfo(ba.mid(i+2, blockLength)); + break; + case RS41_ID_GPSRAW: + break; + case RS41_ID_GPSPOS: + decodeGPSPos(ba.mid(i+2, blockLength)); + break; + case RS41_ID_EMPTY: + break; + } + i += 2 + blockLength + 2; // ID, length, data, CRC + } +} + +QString RS41Frame::toHex() +{ + return m_bytes.toHex(); +} + +int16_t RS41Frame::getInt16(const QByteArray ba, int offset) const +{ + return (ba[offset] & 0xff) + | ((ba[offset+1] & 0xff) << 8); +} + +uint16_t RS41Frame::getUInt16(const QByteArray ba, int offset) const +{ + return (ba[offset] & 0xff) + | ((ba[offset+1] & 0xff) << 8); +} + +uint32_t RS41Frame::getUInt24(const QByteArray ba, int offset) const +{ + return (ba[offset] & 0xff) + | ((ba[offset+1] & 0xff) << 8) + | ((ba[offset+2] & 0xff) << 16); +} + +uint32_t RS41Frame::getUInt32(const QByteArray ba, int offset) const +{ + return (ba[offset] & 0xff) + | ((ba[offset+1] & 0xff) << 8) + | ((ba[offset+2] & 0xff) << 16) + | ((ba[offset+3] & 0xff) << 24); +} + +void RS41Frame::decodeStatus(const QByteArray ba) +{ + m_statusValid = true; + m_frameNumber = getUInt16(ba, 0); + m_serial = QString(ba.mid(0x2, 8)); + m_batteryVoltage = (ba[0xa] & 0xff) / 10.0; + QStringList phases = {"Ground", "Ascent", "0x2", "Descent"}; + int phase = ba[0xd] & 0x3; + m_flightPhase = phases[phase]; + m_batteryStatus = (ba[0xe] & 0x10) == 0 ? "OK" : "Low"; + m_pcbTemperature = (ba[0x10] & 0xff); + m_humiditySensorHeating = getUInt16(ba, 0x13); + m_transmitPower = ba[0x15] & 0xff; + m_maxSubframeNumber = ba[0x16] & 0xff; + m_subframeNumber = ba[0x17] & 0xff; + m_subframe = ba.mid(0x18, 16); +} + +void RS41Frame::decodeMeas(const QByteArray ba) +{ + m_measValid = true; + m_tempMain = getUInt24(ba, 0x0); + m_tempRef1 = getUInt24(ba, 0x3); + m_tempRef2 = getUInt24(ba, 0x6); + m_humidityMain = getUInt24(ba, 0x9); + m_humidityRef1 = getUInt24(ba, 0xc); + m_humidityRef2 = getUInt24(ba, 0xf); + m_humidityTempMain = getUInt24(ba, 0x12); + m_humidityTempRef1 = getUInt24(ba, 0x15); + m_humidityTempRef2 = getUInt24(ba, 0x18); + m_pressureMain = getUInt24(ba, 0x1b); + m_pressureRef1 = getUInt24(ba, 0x1e); + m_pressureRef2 = getUInt24(ba, 0x21); + m_pressureTemp = getInt16(ba, 0x26) / 100.0f; +} + +void RS41Frame::decodeGPSInfo(const QByteArray ba) +{ + m_gpsInfoValid = true; + uint16_t gpsWeek = getUInt16(ba, 0x0); + uint32_t gpsTimeOfWeek = getUInt32(ba, 0x2); // Milliseconds + QDateTime epoch(QDate(1980, 1, 6), QTime(0, 0, 0), Qt::OffsetFromUTC, 18); // GPS doesn't count leap seconds + m_gpsDateTime = epoch.addDays(gpsWeek*7).addMSecs(gpsTimeOfWeek); +} + +void RS41Frame::decodeGPSPos(const QByteArray ba) +{ + m_satellitesUsed = ba[0x12] & 0xff; + if (m_satellitesUsed > 0) + { + m_posValid = true; + int32_t ecefX = (int32_t)getUInt32(ba, 0x0); + int32_t ecefY = (int32_t)getUInt32(ba, 0x4); + int32_t ecefZ = (int32_t)getUInt32(ba, 0x8); + // Convert cm to m + // Convert to latitude, longitude and altitude + Coordinates::ecefToGeodetic(ecefX / 100.0, ecefY / 100.0, ecefZ / 100.0, m_latitude, m_longitude, m_height); + int32_t velX = (int16_t)getUInt16(ba, 0xc); + int32_t velY = (int16_t)getUInt16(ba, 0xe); + int32_t velZ = (int16_t)getUInt16(ba, 0x10); + // Convert cm/s to m/s + // Calculate speed / heading + Coordinates::ecefVelToSpeedHeading(m_latitude, m_longitude, velX / 100.0, velY / 100.0, velZ / 100.0, m_speed, m_verticalRate, m_heading); + } +} + +// Find the water vapor saturation pressure for a given temperature (for tCelsius < 0C). +static float waterVapourSaturationPressure(float tCelsius) +{ + // Convert to Kelvin + float T = tCelsius + 273.15f; + + // Correction + T = - 0.4931358f + + (1.0f + 4.6094296e-3f) * T + - 1.3746454e-5f * T * T + + 1.2743214e-8f * T * T * T; + + // Hyland and Wexler equation + float p = expf(-5800.2206f / T + + 1.3914993f + + 6.5459673f * logf(T) + - 4.8640239e-2f * T + + 4.1764768e-5f * T * T + - 1.4452093e-8f * T * T * T); + + // Scale result to hPa + return p / 100.0f; +} + +static float calcT(int f, int f1, int f2, float r1, float r2, const float *poly, const float *cal) +{ + /*float g = (float)(f2-f1) / (r2-r1); // gain + float Rb = (f1*r2-f2*r1) / (float)(f2-f1); // offset + float Rc = f/g - Rb; + float R = Rc * cal[0]; + float T = (poly[0] + poly[1]*R + poly[2]*R*R + cal[1])*(1.0 + cal[2]); + return T; + */ + + // Convert integer measurement to scale factor + float s = (f-f1)/(float)(f2-f1); + + // Calculate resistance (scale between two reference resistors) + float rUncal = r1 + (r2 - r1) * s; + float r = rUncal * cal[0]; + + // Convert resistance to temperature + float tUncal = poly[0] + poly[1]*r + poly[2]*r*r; + + // Correct temperature (5th order polynomial) + float tCal = 0.0f; + for (int i = 6; i > 0; i--) + { + tCal *= tUncal; + tCal += cal[i]; + } + tCal += tUncal; + + return tCal; +} + +static float calcU(int cInt, int cMin, int cMax, float c1, float c2, float T, float HT, const float *capCal, const float *matrixCal, float height, const float *vectorPCal, const float *matrixPCal) +{ + //qDebug() << "cInt " << cInt << " cMin " << cMin << " cMax " << cMax << " c1 " << c1 << " c2 " << c2 << " T " << T << " HT " << HT << " capCal[0] " << capCal[0] << " capCal[1] " << capCal[1] << "height" << height; + + /*float a0 = 7.5f; + float a1 = 350.0f / capCal[0]; + float fh = (cInt-cMin) / (float)(cMax-cMin); + float rh = 100.0f * (a1*fh - a0); + float T0 = 0.0f; + float T1 = -25.0f; + rh += T0 - T/5.5; + if (T < T1) { + rh *= 1.0 + (T1-T)/90.0; + } + if (rh < 0.0) { + rh = 0.0; + } + if (rh > 100.0) { + rh = 100.0; + } + if (T < -273.0) { + rh = -1.0; + } + + qDebug() << "RH old method: " << rh;*/ + + + // Convert integer measurement to scale factor + float s = (cInt - cMin) / (float)(cMax-cMin); + + // Calculate capacitance (scale between two reference caps) + float cUncal = c1 + (c2 - c1) * s; + float cCal = (cUncal / capCal[0] - 1.0f) * capCal[1]; + + float t = (HT - 20.0f) / 180.0f; + + // Calculate standard pressure at given height in hPa + float pressure = 1013.25f * expf(-1.18575919e-4f * height); + + // Compensation for pressure + float p = pressure / 1000.0f; + float powc = 1.0f; + float sum = 0.0f; + for (int i = 0; i < 3; i++) + { + float l = 0.0f; + float powt = 1.0f; + for (int j = 0; j < 4; j++) + { + l += matrixPCal[4*i+j] * powt; + powt *= t; + } + float x = vectorPCal[i]; + sum += l * (x * p / (1.0f + x * p) - x * powc / (1.0f + x)); + powc *= cCal; + } + cCal -= sum; + + float uUncal = 0.0f; + float f1 = 1.0f; + for (int i = 0; i < 7; i++) + { + float f2 = 1.0; + for (int j = 0; j < 6; j++) + { + uUncal += f1 * f2 * matrixCal[i*6+j]; + f2 *= t; + } + f1 *= cCal; + } + + // Adjust for difference in outside air temperature and the humidty sensor temperature + float uCal = uUncal * waterVapourSaturationPressure(HT) / waterVapourSaturationPressure(T); + + // Ensure within range of 0..100% + uCal = std::min(100.0f, uCal); + uCal = std::max(0.0f, uCal); + + //qDebug() << "RH new method" << uCal; + + return uCal; +} + +static float calcP(int f, int f1, int f2, float pressureTemp, const float *cal) +{ + // Convert integer measurement to scale factor + float s = (f-f1) / (float)(f2-f1); + + float t = pressureTemp; + float t2 = t * t; + float t3 = t2 * t; + + float poly[6]; + poly[0] = cal[0] + cal[7] * t + cal[11] * t2 + cal[15] * t3; + poly[1] = cal[1] + cal[8] * t + cal[12] * t2 + cal[16] * t3; + poly[2] = cal[2] + cal[9] * t + cal[13] * t2 + cal[17] * t3; + poly[3] = cal[3] + cal[10] * t + cal[14] * t2; + poly[4] = cal[4]; + poly[5] = cal[5]; + + float p = cal[6] / s; + float p2 = p * p; + float p3 = p2 * p; + float p4 = p3 * p; + float p5 = p4 * p; + + float pCal = poly[0] + poly[1] * p + poly[2] * p2 + poly[3] * p3 + poly[4] * p4 + poly[5] * p5; + + return pCal; +} + +float RS41Frame::getPressureFloat(const RS41Subframe *subframe) +{ + if (!m_pressureCalibrated) { + calcPressure(subframe); + } + return m_pressure; +} + +QString RS41Frame::getPressureString(const RS41Subframe *subframe) +{ + if (!m_pressureCalibrated) { + calcPressure(subframe); + } + return m_pressureString; +} + +float RS41Frame::getTemperatureFloat(const RS41Subframe *subframe) +{ + if (!m_temperatureCalibrated) { + calcTemperature(subframe); + } + return m_temperature; +} + +QString RS41Frame::getTemperatureString(const RS41Subframe *subframe) +{ + if (!m_temperatureCalibrated) { + calcTemperature(subframe); + } + return m_temperatureString; +} + +void RS41Frame::calcPressure(const RS41Subframe *subframe) +{ + float cal[18]; + + if (m_pressureMain == 0) + { + m_pressure = 0.0f; + m_pressureString = ""; + return; + } + + m_pressureCalibrated = subframe->getPressureCal(cal); + + m_pressure = calcP(m_pressureMain, m_pressureRef1, m_pressureRef2, m_pressureTemp, cal); + + // RS41 pressure resolution of 0.01hPa + m_pressureString = QString::number(m_pressure, 'f', 2); + + if (!m_pressureCalibrated) { + m_pressureString = m_pressureString + "U"; // U for uncalibrated + } +} + +void RS41Frame::calcTemperature(const RS41Subframe *subframe) +{ + float r1, r2; + float poly[3]; + float cal[7]; + + if (m_tempMain == 0) + { + m_temperature = 0.0f; + m_temperatureString = ""; + return; + } + + m_temperatureCalibrated = subframe->getTempCal(r1, r2, poly, cal); + + m_temperature = calcT(m_tempMain, m_tempRef1, m_tempRef2, + r1, r2, + poly, cal); + + // RS41 temperature resolution of 0.01C + m_temperatureString = QString::number(m_temperature, 'f', 2); + + if (!m_temperatureCalibrated) { + m_temperatureString = m_temperatureString + "U"; // U for uncalibrated + } +} + +float RS41Frame::getHumidityTemperatureFloat(const RS41Subframe *subframe) +{ + if (!m_humidityTemperatureCalibrated) { + calcHumidityTemperature(subframe); + } + return m_humidityTemperature; +} + +void RS41Frame::calcHumidityTemperature(const RS41Subframe *subframe) +{ + float r1, r2; + float poly[3]; + float cal[7]; + + if (m_humidityTempMain == 0) + { + m_humidityTemperature = 0.0f; + return; + } + + m_humidityTemperatureCalibrated = subframe->getHumidityTempCal(r1, r2, poly, cal); + + m_humidityTemperature = calcT(m_humidityTempMain, m_humidityTempRef1, m_humidityTempRef2, + r1, r2, + poly, cal); +} + +float RS41Frame::getHumidityFloat(const RS41Subframe *subframe) +{ + if (!m_humidityCalibrated) { + calcHumidity(subframe); + } + return m_humidity; +} + +QString RS41Frame::getHumidityString(const RS41Subframe *subframe) +{ + if (!m_humidityCalibrated) { + calcHumidity(subframe); + } + return m_humidityString; +} + +void RS41Frame::calcHumidity(const RS41Subframe *subframe) +{ + float c1, c2; + float capCal[2]; + float calMatrix[7*6]; + float pCalMatrix[12]; + float pCalVector[3]; + + if (m_humidityMain == 0) + { + m_humidity = 0.0f; + m_humidityString = ""; + return; + } + + float temperature = getTemperatureFloat(subframe); + float humidityTemperature = getHumidityTemperatureFloat(subframe); + + bool humidityCalibrated = subframe->getHumidityCal(c1, c2, capCal, calMatrix); + + m_humidityCalibrated = m_temperatureCalibrated && m_humidityTemperatureCalibrated && humidityCalibrated; + + subframe->getHumidityPressureCal(pCalVector, pCalMatrix); + + m_humidity = calcU(m_humidityMain, m_humidityRef1, m_humidityRef2, + c1, c2, + temperature, humidityTemperature, + capCal, calMatrix, + m_height, pCalVector, pCalMatrix); + + // RS41 humidity resolution of 0.1% + m_humidityString = QString::number(m_humidity, 'f', 1); + + if (!m_humidityCalibrated) { + m_humidityString = m_humidityString + "U"; // U for uncalibrated + } +} + +RS41Frame* RS41Frame::decode(const QByteArray ba) +{ + return new RS41Frame(ba); +} + +int RS41Frame::getFrameLength(int frameType) +{ + return frameType == RS41_FRAME_STD ? RS41_LENGTH_STD : RS41_LENGTH_EXT; +} + +RS41Subframe::RS41Subframe() : + m_subframe(51*16, (char)0) +{ + for (int i = 0; i < 51; i++) { + m_subframeValid[i] = false; + } +} + +// Update subframe with subframe data from received message +void RS41Subframe::update(RS41Frame *message) +{ + m_subframeValid[message->m_subframeNumber] = true; + int offset = message->m_subframeNumber * 16; + for (int i = 0; i < 16; i++) { + m_subframe[offset+i] = message->m_subframe[i]; + } +} + +// Indicate if we have all the required temperature calibration data +bool RS41Subframe::hasTempCal() const +{ + return m_subframeValid[3] && m_subframeValid[4] && m_subframeValid[5] && m_subframeValid[6] && m_subframeValid[7]; +} + +// Get temperature calibration data +// r1, r2 - Temperature reference resistances (Ohms) +// poly - Resistance to temperature 2nd order polynomial +bool RS41Subframe::getTempCal(float &r1, float &r2, float *poly, float *cal) const +{ + if (hasTempCal()) + { + r1 = getFloat(0x3d); + r2 = getFloat(0x41); + for (int i = 0; i < 3; i++) { + poly[i] = getFloat(0x4d + i * 4); + } + for (int i = 0; i < 7; i++) { + cal[i] = getFloat(0x59 + i * 4); + } + return true; + } + else + { + // Use default values + r1 = 750.0f; + r2 = 1100.0f; + poly[0] = -243.9108f; + poly[1] = 0.187654f; + poly[2] = 8.2e-06f; + cal[0] = 1.279928f; + for (int i = 1; i < 7; i++) { + cal[i] = 0.0f; + } + return false; + } +} + +// Indicate if we have all the required humidty calibration data +bool RS41Subframe::hasHumidityCal() const +{ + return m_subframeValid[4] && m_subframeValid[7] + && m_subframeValid[8] && m_subframeValid[9] && m_subframeValid[0xa] && m_subframeValid[0xb] + && m_subframeValid[0xc] && m_subframeValid[0xd] && m_subframeValid[0xe] && m_subframeValid[0xf] + && m_subframeValid[0x10] && m_subframeValid[0x11] && m_subframeValid[0x12]; +} + +// Get humidty calibration data +bool RS41Subframe::getHumidityCal(float &c1, float &c2, float *capCal, float *calMatrix) const +{ + if (hasHumidityCal()) + { + c1 = getFloat(0x45); + c2 = getFloat(0x49); + for (int i = 0; i < 2; i++) { + capCal[i] = getFloat(0x75 + i * 4); + } + for (int i = 0; i < 7*6; i++) { + calMatrix[i] = getFloat(0x7d + i * 4); + } + return true; + } + else + { + // Use default values + c1 = 0.0f; + c2 = 47.0f; + capCal[0] = 45.9068f; + capCal[1] = 4.92924f; + static const float calMatrixDefault[7*6] = { + -0.002586f, -2.24367f, 9.92294f, -3.61913f, 54.3554f, -93.3012f, + 51.7056f, 38.8709f, 209.437f, -378.437f, 9.17326f, 19.5301f, + 150.257f, -150.907f, -280.315f, 182.293f, 3247.39f, 4083.65f, + -233.568f, 345.375f, 200.217f, -388.246f, -3617.66f, 0.0f, + 225.841f, -233.051f, 0.0f, 0.0f, 0.0f, 0.0f, + -93.0635f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f + }; + std::copy(calMatrixDefault, calMatrixDefault + 7*6, calMatrix); + return false; + } +} + +// Indicate if we have all the required humidty temperature sensor calibration data +bool RS41Subframe::hasHumidityTempCal() const +{ + return m_subframeValid[3] && m_subframeValid[4] && m_subframeValid[0x12] && m_subframeValid[0x13] && m_subframeValid[0x14]; +} + +// Get humidty temperature sensor calibration data +bool RS41Subframe::getHumidityTempCal(float &r1, float &r2, float *poly, float *cal) const +{ + if (hasHumidityTempCal()) + { + r1 = getFloat(0x3d); + r2 = getFloat(0x41); + for (int i = 0; i < 3; i++) { + poly[i] = getFloat(0x125 + i * 4); + } + for (int i = 0; i < 7; i++) { + cal[i] = getFloat(0x131 + i * 4); + } + return true; + } + else + { + // Use default values + r1 = 750.0f; + r2 = 1100.0f; + poly[0] = -243.9108f; + poly[1] = 0.187654f; + poly[2] = 8.2e-06f; + cal[0] = 1.279928f; + for (int i = 1; i < 7; i++) { + cal[i] = 0.0f; + } + return false; + } +} + +// Indicate if we have all the required pressure calibration data +bool RS41Subframe::hasPressureCal() const +{ + return m_subframeValid[0x25] && m_subframeValid[0x26] && m_subframeValid[0x27] + && m_subframeValid[0x28] && m_subframeValid[0x29] && m_subframeValid[0x2a]; +} + +// Get pressure calibration data +bool RS41Subframe::getPressureCal(float *cal) const +{ + if (hasPressureCal()) + { + for (int i = 0; i < 18; i++) { + cal[i] = getFloat(0x25e + i * 4); + } + return true; + } + else + { + // Use default values - TODO: Need to obtain from inflight device + for (int i = 0; i < 18; i++) { + cal[i] = 0.0f; + } + return false; + } +} + +// Indicate if we have all the required humidity pressure calibration data +bool RS41Subframe::hasHumidityPressureCal() const +{ + return m_subframeValid[0x2a] && m_subframeValid[0x2b] && m_subframeValid[0x2c] + && m_subframeValid[0x2d] && m_subframeValid[0x2e] && m_subframeValid[0x2f]; +} + +bool RS41Subframe::getHumidityPressureCal(float *vec, float *mat) const +{ + if (hasHumidityPressureCal()) + { + for (int i = 0; i < 3; i++) { + vec[i] = getFloat(0x2a6 + i * 4); + } + for (int i = 0; i < 12; i++) { + mat[i] = getFloat(0x2ba + i * 4); + } + return true; + } + else + { + // Use default values - TODO: Need to obtain from inflight device + for (int i = 0; i < 3; i++) { + vec[i] = 0.0f; + } + for (int i = 0; i < 12; i++) { + mat[i] = 0.0f; + } + return false; + } +} + +// Get type of RS41. E.g. "RS41-SGP" +QString RS41Subframe::getType() const +{ + if (m_subframeValid[0x21] && m_subframeValid[0x22]) + { + QByteArray bytes = m_subframe.mid(0x218, 10); + + while ((bytes.size() > 0) && (bytes.back() == '\0')) { + bytes.remove(bytes.size() - 1, 1); + } + + return QString(bytes).trimmed(); + } + else + { + return "RS41"; + } +} + +// Get transmission frequency in MHz +QString RS41Subframe::getFrequencyMHz() const +{ + if (m_subframeValid[0]) + { + uint8_t lower = m_subframe[2] & 0xff; + uint8_t upper = m_subframe[3] & 0xff; + float freq = 400.0 + (upper + (lower / 255.0)) * 0.04; + return QString::number(freq, 'f', 3); + } + else + { + return ""; + } +} + +QString RS41Subframe::getBurstKillStatus() const +{ + if (m_subframeValid[2]) + { + uint8_t status = m_subframe[0x2b]; + return status == 0 ? "Inactive" : "Active"; + } + else + { + return ""; + } +} + +// Seconds until power-off once active +QString RS41Subframe::getBurstKillTimer() const +{ + if (m_subframeValid[0x31]) + { + uint16_t secs = getUInt16(0x316); + QTime t(0, 0, 0); + t = t.addSecs(secs); + return t.toString("hh:mm:ss"); + } + else + { + return ""; + } +} + +uint16_t RS41Subframe::getUInt16(int offset) const +{ + return (m_subframe[offset] & 0xff) | ((m_subframe[offset+1] & 0xff) << 8); +} + +float RS41Subframe::getFloat(int offset) const +{ + float f; + // Assumes host is little endian with 32-bit float + memcpy(&f, m_subframe.data() + offset, 4); + return f; +} diff --git a/android/app/src/main/cpp/util/radiosonde.h b/android/app/src/main/cpp/util/radiosonde.h new file mode 100644 index 0000000..001af7f --- /dev/null +++ b/android/app/src/main/cpp/util/radiosonde.h @@ -0,0 +1,184 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_RADIOSONDE_H +#define INCLUDE_RADIOSONDE_H + +#include +#include +#include + +#include +#include + +#include "export.h" + +#define RS41_LENGTH_STD 320 +#define RS41_LENGTH_EXT 518 + +#define RS41_OFFSET_RS 0x08 +#define RS41_OFFSET_FRAME_TYPE 0x38 +#define RS41_OFFSET_BLOCK_0 0x39 + +#define RS41_FRAME_STD 0x0f +#define RS41_FRAME_EXT 0xf0 + +#define RS41_ID_STATUS 0x79 +#define RS41_ID_MEAS 0x7a +#define RS41_ID_GPSINFO 0x7c +#define RS41_ID_GPSRAW 0x7d +#define RS41_ID_GPSPOS 0x7b +#define RS41_ID_EMPTY 0x76 + +#define RS41_RS_N 255 +#define RS41_RS_K 231 +#define RS41_RS_2T (RS41_RS_N-RS41_RS_K) +#define RS41_RS_INTERLEAVE 2 +#define RS41_RS_DATA (264/RS41_RS_INTERLEAVE) +#define RS41_RS_PAD (RS41_RS_K-RS41_RS_DATA) + +class RS41Subframe; + +// Frame of data transmitted by RS41 radiosonde +class SDRBASE_API RS41Frame { +public: + + // Status + bool m_statusValid; + uint16_t m_frameNumber; // Increments every frame + QString m_serial; // Serial number + float m_batteryVoltage; // In volts + QString m_flightPhase; // On ground, ascent, descent + QString m_batteryStatus; // OK or Low + uint8_t m_pcbTemperature; // In degrees C + uint16_t m_humiditySensorHeating; // 0..1000 + uint8_t m_transmitPower; // 0..7 + uint8_t m_maxSubframeNumber; + uint8_t m_subframeNumber; + QByteArray m_subframe; // 16 bytes of subframe + + // Meas + bool m_measValid; + uint32_t m_tempMain; + uint32_t m_tempRef1; + uint32_t m_tempRef2; + uint32_t m_humidityMain; + uint32_t m_humidityRef1; + uint32_t m_humidityRef2; + uint32_t m_humidityTempMain; + uint32_t m_humidityTempRef1; + uint32_t m_humidityTempRef2; + uint32_t m_pressureMain; + uint32_t m_pressureRef1; + uint32_t m_pressureRef2; + float m_pressureTemp; // Pressure sensor module temperature - In degrees C + + // GPSInfo + bool m_gpsInfoValid; + QDateTime m_gpsDateTime; + + // GPSPos + bool m_posValid; + double m_latitude; // In degrees + double m_longitude; // In degrees + double m_height; // In metres + double m_speed; // In m/s + double m_heading; // In degrees + double m_verticalRate; // In m/s + int m_satellitesUsed; + + RS41Frame(const QByteArray ba); + ~RS41Frame() {} + QString toHex(); + void decodeStatus(const QByteArray ba); + void decodeMeas(const QByteArray ba); + void decodeGPSInfo(const QByteArray ba); + void decodeGPSPos(const QByteArray ba); + + float getPressureFloat(const RS41Subframe *subframe); + QString getPressureString(const RS41Subframe *subframe); + bool isPressureCalibrated() const { return m_pressureCalibrated; } + float getTemperatureFloat(const RS41Subframe *subframe); + QString getTemperatureString(const RS41Subframe *subframe); + bool isTemperatureCalibrated() const { return m_temperatureCalibrated; } + float getHumidityTemperatureFloat(const RS41Subframe *subframe); + float getHumidityFloat(const RS41Subframe *subframe); + QString getHumidityString(const RS41Subframe *subframe); + bool isHumidityCalibrated() const { return m_humidityCalibrated; } + + static RS41Frame* decode(const QByteArray ba); + static int getFrameLength(int frameType); + +protected: + int16_t getInt16(const QByteArray ba, int offset) const; + uint16_t getUInt16(const QByteArray ba, int offset) const; + uint32_t getUInt24(const QByteArray ba, int offset) const; + uint32_t getUInt32(const QByteArray ba, int offset) const; + + void calcPressure(const RS41Subframe *subframe); + void calcTemperature(const RS41Subframe *subframe); + void calcHumidityTemperature(const RS41Subframe *subframe); + void calcHumidity(const RS41Subframe *subframe); + + QByteArray m_bytes; + + float m_pressure; + QString m_pressureString; + bool m_pressureCalibrated; + float m_temperature; + QString m_temperatureString; + bool m_temperatureCalibrated; + float m_humidityTemperature; + bool m_humidityTemperatureCalibrated; + float m_humidity; + QString m_humidityString; + bool m_humidityCalibrated; + +}; + +// RS41 subframe holding calibration data collected from multiple RS51Frames +class SDRBASE_API RS41Subframe { +public: + + RS41Subframe(); + void update(RS41Frame *message); + bool hasTempCal() const; + bool getTempCal(float &r1, float &r2, float *poly, float *cal) const; + bool hasHumidityCal() const; + bool getHumidityCal(float &c1, float &c2, float *capCal, float *calMatrix) const; + bool hasHumidityTempCal() const; + bool getHumidityTempCal(float &r1, float &r2, float *poly, float *cal) const; + bool hasPressureCal() const; + bool getPressureCal(float *cal) const; + bool hasHumidityPressureCal() const; + bool getHumidityPressureCal(float *vec, float *mat) const; + QString getType() const; + QString getFrequencyMHz() const; + QString getBurstKillStatus() const; + QString getBurstKillTimer() const; + +protected: + + bool m_subframeValid[51]; + QByteArray m_subframe; + + uint16_t getUInt16(int offset) const; + float getFloat(int offset) const; + +}; + +#endif // INCLUDE_RADIOSONDE_H diff --git a/android/app/src/main/cpp/util/rainviewer.cpp b/android/app/src/main/cpp/util/rainviewer.cpp new file mode 100644 index 0000000..8d761e3 --- /dev/null +++ b/android/app/src/main/cpp/util/rainviewer.cpp @@ -0,0 +1,130 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "rainviewer.h" + +#include +#include +#include +#include +#include +#include + +RainViewer::RainViewer() +{ + connect(&m_timer, &QTimer::timeout, this, &RainViewer::update); + m_networkManager = new QNetworkAccessManager(); + QObject::connect(m_networkManager, &QNetworkAccessManager::finished, this, &RainViewer::handleReply); +} + +RainViewer::~RainViewer() +{ + m_timer.stop(); + QObject::disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &RainViewer::handleReply); + delete m_networkManager; +} + +void RainViewer::getPathPeriodically(int periodInMins) +{ + // Rain maps updated every 10mins + m_timer.setInterval(periodInMins*60*1000); + m_timer.start(); + update(); +} + +void RainViewer::update() +{ + getPath(); +} + +void RainViewer::getPath() +{ + QUrl url(QString("https://api.rainviewer.com/public/weather-maps.json")); + m_networkManager->get(QNetworkRequest(url)); +} + +void RainViewer::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + if (document.isObject()) + { + QJsonObject obj = document.object(); + QString radarPath = ""; + QString satellitePath = ""; + + if (obj.contains(QStringLiteral("radar"))) + { + QJsonValue val = obj.value(QStringLiteral("radar")); + QJsonObject mainObj = val.toObject(); + if (mainObj.contains(QStringLiteral("past"))) + { + QJsonArray past = mainObj.value(QStringLiteral("past")).toArray(); + if (past.size() > 0) + { + QJsonObject mostRecent = past.last().toObject(); + if (mostRecent.contains(QStringLiteral("path"))) { + radarPath = mostRecent.value(QStringLiteral("path")).toString(); + } + } + } + } + else + { + qDebug() << "RainViewer::handleReply: Object doesn't contain a radar: " << obj; + } + if (obj.contains(QStringLiteral("satellite"))) + { + QJsonValue val = obj.value(QStringLiteral("satellite")); + QJsonObject mainObj = val.toObject(); + if (mainObj.contains(QStringLiteral("infrared"))) + { + QJsonArray ir = mainObj.value(QStringLiteral("infrared")).toArray(); + if (ir.size() > 0) + { + QJsonObject mostRecent = ir.last().toObject(); + if (mostRecent.contains(QStringLiteral("path"))) { + satellitePath = mostRecent.value(QStringLiteral("path")).toString(); + } + } + } + } + else + { + qDebug() << "RainViewer::handleReply: Object doesn't contain a satellite: " << obj; + } + emit pathUpdated(radarPath, satellitePath); + } + else + { + qDebug() << "RainViewer::handleReply: Document is not an object: " << document; + } + } + else + { + qDebug() << "RainViewer::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "RainViewer::handleReply: reply is null"; + } +} diff --git a/android/app/src/main/cpp/util/rainviewer.h b/android/app/src/main/cpp/util/rainviewer.h new file mode 100644 index 0000000..4a5acb9 --- /dev/null +++ b/android/app/src/main/cpp/util/rainviewer.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_RAINVIEWER_H +#define INCLUDE_RAINVIEWER_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +// RainViewer API wrapper (https://www.rainviewer.com/) +// Gets details of currently available weather radar and satellite IR data +class SDRBASE_API RainViewer : public QObject +{ + Q_OBJECT + +public: + RainViewer(); + ~RainViewer(); + + void getPath(); + void getPathPeriodically(int periodInMins=15); + +public slots: + void update(); + void handleReply(QNetworkReply* reply); + +signals: + void pathUpdated(const QString& radarPath, const QString& satellitePath); // Emitted when paths to new data are available. + +private: + QTimer m_timer; // Timer for periodic updates + QNetworkAccessManager *m_networkManager; + +}; + +#endif /* INCLUDE_RAINVIEWER_H */ diff --git a/android/app/src/main/cpp/util/reedsolomon.h b/android/app/src/main/cpp/util/reedsolomon.h new file mode 100644 index 0000000..724f910 --- /dev/null +++ b/android/app/src/main/cpp/util/reedsolomon.h @@ -0,0 +1,662 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/* + * Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014 Hard Consulting Corporation. + * Copyright (c) 2006 Phil Karn, KA9Q + * + * It may be used under the terms of the GNU Lesser General Public License (LGPL). + * + * Simplified version of https://github.com/pjkundert/ezpwd-reed-solomon which + * seems to be the fastest open-source decoder. + * + */ + +#ifndef REEDSOLOMON_H +#define REEDSOLOMON_H + +#include +#include +#include +#include +#include +#include + +// Preprocessor defines available: +// +// EZPWD_NO_MOD_TAB -- define to force no "modnn" Galois modulo table acceleration +// +//#define EZPWD_NO_MOD_TAB + +namespace ReedSolomon { + +// +// reed_solomon_base - Reed-Solomon codec generic base class +// +class reed_solomon_base { +public: + virtual size_t datum() const = 0; // a data element's bits + virtual size_t symbol() const = 0; // a symbol's bits + virtual int size() const = 0; // R-S block size (maximum total symbols) + virtual int nroots() const = 0; // R-S roots (parity symbols) + virtual int load() const = 0; // R-S net payload (data symbols) + + virtual ~reed_solomon_base() {} + + reed_solomon_base() {} + + // + // {en,de}code -- Compute/Correct errors/erasures in a Reed-Solomon encoded container + // + /// For decode, optionally specify some known erasure positions (up to nroots()). If + /// non-empty 'erasures' is provided, it contains the positions of each erasure. If a + /// non-zero pointer to a 'position' vector is provided, its capacity will be increased to + /// be capable of storing up to 'nroots()' ints; the actual deduced error locations will be + /// returned. + /// + /// RETURN VALUE + /// + /// Return -1 on error. The encode returns the number of parity symbols produced; + /// decode returns the number of symbols corrected. Both errors and erasures are included, + /// so long as they are actually different than the deduced value. In other words, if a + /// symbol is marked as an erasure but it actually turns out to be correct, it's index will + /// NOT be included in the returned count, nor the modified erasure vector! + /// + + virtual int encode(const uint8_t *data, int len, uint8_t *parity) const = 0; + + virtual int decode1(uint8_t *data, int len, uint8_t *parity, + const std::vector &erasure = std::vector(), std::vector *position = 0) const = 0; + + int decode(uint8_t *data, + int len, + int pad = 0, // ignore 'pad' symbols at start of array + const std::vector &erasure = std::vector(), + std::vector *position = 0) const + { + return decode1((uint8_t*)(data + pad), len, (uint8_t*)(data + len), erasure, position); + } + +}; + +// +// gfpoly - default field polynomial generator functor. +// +template +struct gfpoly { + int operator()(int sr) const + { + if (sr == 0) { + sr = 1; + } else { + sr <<= 1; + if (sr & (1 << 8)) + sr ^= PLY; + sr &= ((1 << 8) - 1); + } + return sr; + } +}; + +// +// class reed_solomon_tabs -- R-S tables common to all RS(NN,*) with same SYM, PRM and PLY +// +template +class reed_solomon_tabs : public reed_solomon_base { +public: + typedef uint8_t symbol_t; + static const size_t DATUM = 8; // bits + static const size_t SYMBOL = 8; // bits / symbol + static const int MM = 8; + static const int SIZE = (1 << 8) - 1; // maximum symbols in field + static const int NN = SIZE; + static const int A0 = SIZE; + static const int MODS // modulo table: 1/2 the symbol size squared, up to 4k +#if defined(EZPWD_NO_MOD_TAB) + = 0; +#else + = 8 > 8 ? (1 << 12) : (1 << 8 << 8 / 2); +#endif + + static int iprim; // initialized to -1, below + +protected: + static std::array alpha_to; + static std::array index_of; + static std::array mod_of; + virtual ~reed_solomon_tabs() {} + + reed_solomon_tabs() : reed_solomon_base() + { + // Do init if not already done. We check one value which is initialized to -1; this is + // safe, 'cause the value will not be set 'til the initializing thread has completely + // initialized the structure. Worst case scenario: multiple threads will initialize + // identically. No mutex necessary. + if (iprim >= 0) + return; + + // Generate Galois field lookup tables + index_of[0] = A0; // log(zero) = -inf + alpha_to[A0] = 0; // alpha**-inf = 0 + PLY poly; + int sr = poly(0); + for (int i = 0; i < NN; i++) { + index_of[sr] = i; + alpha_to[i] = sr; + sr = poly(sr); + } + // If it's not primitive, raise exception or abort + if (sr != alpha_to[0]) { + abort(); + } + + // Generate modulo table for some commonly used (non-trivial) values + for (int x = NN; x < NN + MODS; ++x) + mod_of[x - NN] = _modnn(x); + // Find prim-th root of 1, index form, used in decoding. + int iptmp = 1; + while (iptmp % PRM != 0) + iptmp += NN; + iprim = iptmp / PRM; + } + + // + // modnn -- modulo replacement for galois field arithmetic, optionally w/ table acceleration + // + // @x: the value to reduce (will never be -'ve) + // + // where + // MM = number of bits per symbol + // NN = (2^MM) - 1 + // + // Simple arithmetic modulo would return a wrong result for values >= 3 * NN + // + uint8_t _modnn(int x) const + { + while (x >= NN) { + x -= NN; + x = (x >> MM) + (x & NN); + } + return x; + } + + uint8_t modnn(int x) const + { + while (x >= NN + MODS) { + x -= NN; + x = (x >> MM) + (x & NN); + } + if (MODS && x >= NN) + x = mod_of[x - NN]; + return x; + } +}; + +// +// class reed_solomon - Reed-Solomon codec +// +// @TYP: A symbol datum; {en,de}code operates on arrays of these +// @DATUM: Bits per datum (a TYP()) +// @SYM{BOL}, MM: Bits per symbol +// @NN: Symbols per block (== (1< instances with the same template type parameters share a common +// (static) set of alpha_to, index_of and genpoly tables. The first instance to be constructed +// initializes the tables. +// +// Each specialized type of reed_solomon implements a specific encode/decode method +// appropriate to its datum 'TYP'. When accessed via a generic reed_solomon_base pointer, only +// access via "safe" (size specifying) containers or iterators is available. +// +template +class reed_solomon : public reed_solomon_tabs { +public: + typedef reed_solomon_tabs tabs_t; + using tabs_t::A0; + using tabs_t::DATUM; + using tabs_t::MM; + using tabs_t::NN; + using tabs_t::SIZE; + using tabs_t::SYMBOL; + + using tabs_t::iprim; + + using tabs_t::alpha_to; + using tabs_t::index_of; + + using tabs_t::modnn; + + static const int NROOTS = RTS; + static const int LOAD = SIZE - NROOTS; // maximum non-parity symbol payload + +protected: + static std::array genpoly; + +public: + virtual size_t datum() const { return DATUM; } + + virtual size_t symbol() const { return SYMBOL; } + + virtual int size() const { return SIZE; } + + virtual int nroots() const { return NROOTS; } + + virtual int load() const { return LOAD; } + + using reed_solomon_base::decode; + virtual int decode1(uint8_t *data, int len, uint8_t *parity, + const std::vector &erasure = std::vector(), std::vector *position = 0) const + { + return decode_mask(data, len, parity, erasure, position); + } + + // + // decode_mask -- mask INP data into valid SYMBOL data + // + /// Incoming data may be in a variety of sizes, and may contain information beyond the + /// R-S symbol capacity. For example, we might use a 6-bit R-S symbol to correct the lower + /// 6 bits of an 8-bit data character. This would allow us to correct common substitution + /// errors (such as '2' for '3', 'R' for 'T', 'n' for 'm'). + /// + int decode_mask(uint8_t *data, int len, + uint8_t *parity = 0, // either 0, or pointer to all parity symbols + const std::vector &erasure = std::vector(), std::vector *position = 0) const + { + if (!parity) { + len -= NROOTS; + parity = data + len; + } + + int corrects; + if (!erasure.size() && !position) { + // No erasures, and error position info not wanted. + corrects = decode(data, len, parity); + } else { + // Either erasure location info specified, or resultant error position info wanted; + // Prepare pos (a temporary, if no position vector provided), and copy any provided + // erasure positions. After number of corrections is known, resize the position + // vector. Thus, we use any supplied erasure info, and optionally return any + // correction position info separately. + std::vector _pos; + std::vector &pos = position ? *position : _pos; + pos.resize(std::max(size_t(NROOTS), erasure.size())); + std::copy(erasure.begin(), erasure.end(), pos.begin()); + corrects = decode(data, len, parity, &pos.front(), erasure.size()); + if (corrects > int(pos.size())) { + return -1; + } + pos.resize(std::max(0, corrects)); + } + + return corrects; + } + + virtual ~reed_solomon() + { + } + + reed_solomon() : reed_solomon_tabs() + { + // We check one element of the array; this is safe, 'cause the value will not be + // initialized 'til the initializing thread has completely initialized the array. Worst + // case scenario: multiple threads will initialize identically. No mutex necessary. + if (genpoly[0]) + return; + + std::array tmppoly; // uninitialized + // Form RS code generator polynomial from its roots. Only lower-index entries are + // consulted, when computing subsequent entries; only index 0 needs initialization. + tmppoly[0] = 1; + for (int i = 0, root = FCR * PRM; i < NROOTS; i++, root += PRM) { + tmppoly[i + 1] = 1; + // Multiply tmppoly[] by @**(root + x) + for (int j = i; j > 0; j--) { + if (tmppoly[j] != 0) + tmppoly[j] = tmppoly[j - 1] ^ alpha_to[modnn(index_of[tmppoly[j]] + root)]; + else + tmppoly[j] = tmppoly[j - 1]; + } + // tmppoly[0] can never be zero + tmppoly[0] = alpha_to[modnn(index_of[tmppoly[0]] + root)]; + } + // convert NROOTS entries of tmppoly[] to genpoly[] in index form for quicker encoding, + // in reverse order so genpoly[0] is last element initialized. + for (int i = NROOTS; i >= 0; --i) + genpoly[i] = index_of[tmppoly[i]]; + } + + virtual int encode(const uint8_t *data, int len, uint8_t *parity) // at least nroots + const + { + // Check length parameter for validity + for (int i = 0; i < NROOTS; i++) + parity[i] = 0; + for (int i = 0; i < len; i++) { + uint8_t feedback = index_of[data[i] ^ parity[0]]; + if (feedback != A0) { + for (int j = 1; j < NROOTS; j++) + parity[j] ^= alpha_to[modnn(feedback + genpoly[NROOTS - j])]; + } + + std::rotate(parity, parity + 1, parity + NROOTS); + if (feedback != A0) + parity[NROOTS - 1] = alpha_to[modnn(feedback + genpoly[0])]; + else + parity[NROOTS - 1] = 0; + } + return NROOTS; + } + + int decode(uint8_t *data, int len, + uint8_t *parity, // Requires: at least NROOTS + int *eras_pos = 0, // Capacity: at least NROOTS + int no_eras = 0, // Maximum: at most NROOTS + uint8_t *corr = 0) // Capacity: at least NROOTS + const + { + typedef std::array typ_nroots; + typedef std::array typ_nroots_1; + typedef std::array int_nroots; + + typ_nroots_1 lambda{{0}}; + typ_nroots syn; + typ_nroots_1 b; + typ_nroots_1 t; + typ_nroots_1 omega; + int_nroots root; + typ_nroots_1 reg; + int_nroots loc; + int count = 0; + + // Check length parameter and erasures for validity + int pad = NN - NROOTS - len; + if (no_eras) { + if (no_eras > NROOTS) { + return -1; + } + for (int i = 0; i < no_eras; ++i) { + if (eras_pos[i] < 0 || eras_pos[i] >= len + NROOTS) { + return -1; + } + } + } + + // form the syndromes; i.e., evaluate data(x) at roots of g(x) + for (int i = 0; i < NROOTS; i++) + syn[i] = data[0]; + + for (int j = 1; j < len; j++) { + for (int i = 0; i < NROOTS; i++) { + if (syn[i] == 0) { + syn[i] = data[j]; + } else { + syn[i] = data[j] ^ alpha_to[modnn(index_of[syn[i]] + (FCR + i) * PRM)]; + } + } + } + + for (int j = 0; j < NROOTS; j++) { + for (int i = 0; i < NROOTS; i++) { + if (syn[i] == 0) { + syn[i] = parity[j]; + } else { + syn[i] = parity[j] ^ alpha_to[modnn(index_of[syn[i]] + (FCR + i) * PRM)]; + } + } + } + + // Convert syndromes to index form, checking for nonzero condition + uint8_t syn_error = 0; + for (int i = 0; i < NROOTS; i++) { + syn_error |= syn[i]; + syn[i] = index_of[syn[i]]; + } + + int deg_lambda = 0; + int deg_omega = 0; + int r = no_eras; + int el = no_eras; + if (!syn_error) { + // if syndrome is zero, data[] is a codeword and there are no errors to correct. + count = 0; + goto finish; + } + + lambda[0] = 1; + if (no_eras > 0) { + // Init lambda to be the erasure locator polynomial. Convert erasure positions + // from index into data, to index into Reed-Solomon block. + lambda[1] = alpha_to[modnn(PRM * (NN - 1 - (eras_pos[0] + pad)))]; + for (int i = 1; i < no_eras; i++) { + uint8_t u = modnn(PRM * (NN - 1 - (eras_pos[i] + pad))); + for (int j = i + 1; j > 0; j--) { + uint8_t tmp = index_of[lambda[j - 1]]; + if (tmp != A0) { + lambda[j] ^= alpha_to[modnn(u + tmp)]; + } + } + } + } + + for (int i = 0; i < NROOTS + 1; i++) + b[i] = index_of[lambda[i]]; + + // + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial + // + while (++r <= NROOTS) { // r is the step number + // Compute discrepancy at the r-th step in poly-form + uint8_t discr_r = 0; + for (int i = 0; i < r; i++) { + if ((lambda[i] != 0) && (syn[r - i - 1] != A0)) { + discr_r ^= alpha_to[modnn(index_of[lambda[i]] + syn[r - i - 1])]; + } + } + discr_r = index_of[discr_r]; // Index form + if (discr_r == A0) { + // 2 lines below: B(x) <-- x*B(x) + // Rotate the last element of b[NROOTS+1] to b[0] + std::rotate(b.begin(), b.begin() + NROOTS, b.end()); + b[0] = A0; + } else { + // 7 lines below: T(x) <-- lambda(x)-discr_r*x*b(x) + t[0] = lambda[0]; + for (int i = 0; i < NROOTS; i++) { + if (b[i] != A0) { + t[i + 1] = lambda[i + 1] ^ alpha_to[modnn(discr_r + b[i])]; + } else + t[i + 1] = lambda[i + 1]; + } + if (2 * el <= r + no_eras - 1) { + el = r + no_eras - el; + // 2 lines below: B(x) <-- inv(discr_r) * lambda(x) + for (int i = 0; i <= NROOTS; i++) { + b[i] = ((lambda[i] == 0) ? A0 : modnn(index_of[lambda[i]] - discr_r + NN)); + } + } else { + // 2 lines below: B(x) <-- x*B(x) + std::rotate(b.begin(), b.begin() + NROOTS, b.end()); + b[0] = A0; + } + lambda = t; + } + } + + // Convert lambda to index form and compute deg(lambda(x)) + for (int i = 0; i < NROOTS + 1; i++) { + lambda[i] = index_of[lambda[i]]; + if (lambda[i] != NN) + deg_lambda = i; + } + // Find roots of error+erasure locator polynomial by Chien search + reg = lambda; + count = 0; // Number of roots of lambda(x) + for (int i = 1, k = iprim - 1; i <= NN; i++, k = modnn(k + iprim)) { + uint8_t q = 1; // lambda[0] is always 0 + for (int j = deg_lambda; j > 0; j--) { + if (reg[j] != A0) { + reg[j] = modnn(reg[j] + j); + q ^= alpha_to[reg[j]]; + } + } + if (q != 0) + continue; // Not a root + // store root (index-form) and error location number + root[count] = i; + loc[count] = k; + // If we've already found max possible roots, abort the search to save time + if (++count == deg_lambda) + break; + } + if (deg_lambda != count) { + // deg(lambda) unequal to number of roots => uncorrectable error detected + count = -1; + goto finish; + } + // + // Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo x**NROOTS). in + // index form. Also find deg(omega). + // + deg_omega = deg_lambda - 1; + for (int i = 0; i <= deg_omega; i++) { + uint8_t tmp = 0; + for (int j = i; j >= 0; j--) { + if ((syn[i - j] != A0) && (lambda[j] != A0)) + tmp ^= alpha_to[modnn(syn[i - j] + lambda[j])]; + } + omega[i] = index_of[tmp]; + } + + // + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(fcr-1) + // and den = lambda_pr(inv(X(l))) all in poly-form + // + for (int j = count - 1; j >= 0; j--) { + uint8_t num1 = 0; + for (int i = deg_omega; i >= 0; i--) { + if (omega[i] != A0) + num1 ^= alpha_to[modnn(omega[i] + i * root[j])]; + } + uint8_t num2 = alpha_to[modnn(root[j] * (FCR - 1) + NN)]; + uint8_t den = 0; + + // lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] + for (int i = std::min(deg_lambda, NROOTS - 1) & ~1; i >= 0; i -= 2) { + if (lambda[i + 1] != A0) { + den ^= alpha_to[modnn(lambda[i + 1] + i * root[j])]; + } + } + // Apply error to data. Padding ('pad' unused symbols) begin at index 0. + if (num1 != 0) { + if (loc[j] < pad) { + // If the computed error position is in the 'pad' (the unused portion of the + // R-S data capacity), then our solution has failed -- we've computed a + // correction location outside of the data and parity we've been provided! + count = -1; + goto finish; + } + + uint8_t cor = alpha_to[modnn(index_of[num1] + index_of[num2] + NN - index_of[den])]; + // Store the error correction pattern, if a correction buffer is available + if (corr) + corr[j] = cor; + // If a data/parity buffer is given and the error is inside the message or + // parity data, correct it + if (loc[j] < (NN - NROOTS)) { + if (data) { + data[loc[j] - pad] ^= cor; + } + } else if (loc[j] < NN) { + if (parity) + parity[loc[j] - (NN - NROOTS)] ^= cor; + } + } + } + + finish: + if (eras_pos != NULL) { + for (int i = 0; i < count; i++) + eras_pos[i] = loc[i] - pad; + } + return count; + } +}; + +// +// Define the static reed_solomon...<...> members; allowed in header for template types. +// +// The reed_solomon_tags<...>::iprim < 0 is used to indicate to the first instance that the +// static tables require initialization. +// +template +int reed_solomon_tabs::iprim = -1; + +template +std::array::NN + 1> reed_solomon_tabs::alpha_to; + +template +std::array::NN + 1> reed_solomon_tabs::index_of; +template +std::array::MODS> reed_solomon_tabs::mod_of; + +template +std::array::NROOTS + 1> reed_solomon::genpoly; + +// +// RS( ... ) -- Define a reed-solomon codec +// +// @SYMBOLS: Total number of symbols; must be a power of 2 minus 1, eg 2^8-1 == 255 +// @PAYLOAD: The maximum number of non-parity symbols, eg 253 ==> 2 parity symbols +// @POLY: A primitive polynomial appropriate to the SYMBOLS size +// @FCR: The first consecutive root of the Reed-Solomon generator polynomial +// @PRIM: The primitive root of the generator polynomial +// + +// +// RS -- Standard partial specializations for Reed-Solomon codec type access +// +// Normally, Reed-Solomon codecs are described with terms like RS(255,252). Obtain various +// standard Reed-Solomon codecs using macros of a similar form, eg. RS<255, 252>. Standard PLY, +// FCR and PRM values are provided for various SYMBOL sizes, along with appropriate basic types +// capable of holding all internal Reed-Solomon tabular data. +// +// In order to provide "default initialization" of const RS<...> types, a user-provided +// default constructor must be provided. +// +template +struct RS; +template +struct RS<255, PAYLOAD> : public ReedSolomon::reed_solomon<(255) - (PAYLOAD), 0, 1, ReedSolomon::gfpoly<0x11d>> +{ + RS() + : ReedSolomon::reed_solomon<(255) - (PAYLOAD), 0, 1, ReedSolomon::gfpoly<0x11d>>() + { + } +}; + +} // namespace ReedSolomon + +#endif // REEDSOLOMON_H diff --git a/android/app/src/main/cpp/util/rtpsink.cpp b/android/app/src/main/cpp/util/rtpsink.cpp new file mode 100644 index 0000000..6c5507b --- /dev/null +++ b/android/app/src/main/cpp/util/rtpsink.cpp @@ -0,0 +1,354 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019, 2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "rtpsink.h" +#include + +RTPSink::RTPSink(QUdpSocket *udpSocket, int sampleRate, bool stereo) : + m_payloadType(stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono), + m_sampleRate(sampleRate), + m_sampleBytes(0), + m_packetSamples(0), + m_bufferSize(0), + m_sampleBufferIndex(0), + m_byteBuffer(0), + m_destport(9998) +{ + m_rtpSessionParams.SetOwnTimestampUnit(1.0 / (double) m_sampleRate); + m_rtpTransmissionParams.SetRTCPMultiplexing(true); // do not allocate another socket for RTCP + m_rtpTransmissionParams.SetUseExistingSockets(udpSocket, udpSocket); + + int status = m_rtpTransmitter.Init(); + if (status < 0) { + qCritical("RTPSink::RTPSink: cannot initialize transmitter: %s", qrtplib::RTPGetErrorString(status).c_str()); + m_valid = false; + } else { + qDebug("RTPSink::RTPSink: initialized transmitter: %s", qrtplib::RTPGetErrorString(status).c_str()); + } + + m_rtpTransmitter.Create(m_rtpSessionParams.GetMaximumPacketSize(), &m_rtpTransmissionParams); + qDebug("RTPSink::RTPSink: created transmitter: %s", qrtplib::RTPGetErrorString(status).c_str()); + + status = m_rtpSession.Create(m_rtpSessionParams, &m_rtpTransmitter); + if (status < 0) { + qCritical("RTPSink::RTPSink: cannot create session: %s", qrtplib::RTPGetErrorString(status).c_str()); + m_valid = false; + } else { + qDebug("RTPSink::RTPSink: created session: %s", qrtplib::RTPGetErrorString(status).c_str()); + } + + setPayloadInformation(m_payloadType, m_sampleRate); + m_valid = true; + + uint32_t endianTest32 = 1; + uint8_t *ptr = (uint8_t*) &endianTest32; + m_endianReverse = (*ptr == 1); +} + +RTPSink::~RTPSink() +{ + qrtplib::RTPTime delay = qrtplib::RTPTime(10.0); + m_rtpSession.BYEDestroy(delay, "Time's up", 9); + + if (m_byteBuffer) + { + delete[] m_byteBuffer; + m_byteBuffer = nullptr; + } +} + +void RTPSink::setPayloadInformation(PayloadType payloadType, int sampleRate) +{ + uint32_t timestampinc; + QMutexLocker locker(&m_mutex); + + qDebug("RTPSink::setPayloadInformation: payloadType: %d sampleRate: %d", payloadType, sampleRate); + + switch (payloadType) + { + case PayloadPCMA8: + m_sampleBytes = 1; + m_rtpSession.SetDefaultPayloadType(8); + m_packetSamples = m_sampleRate / 50; // 20ms packet samples + timestampinc = m_sampleRate / 50; // 1 channel + break; + case PayloadPCMU8: + m_sampleBytes = 1; + m_rtpSession.SetDefaultPayloadType(0); + m_packetSamples = m_sampleRate / 50; // 20ms packet samples + timestampinc = m_sampleRate / 50; // 1 channel + break; + case PayloadL8: + m_sampleBytes = 1; + m_rtpSession.SetDefaultPayloadType(96); + m_packetSamples = m_sampleRate / 50; // 20ms packet samples + timestampinc = m_sampleRate / 50; // 1 channel + break; + case PayloadL16Stereo: + m_sampleBytes = 4; + m_rtpSession.SetDefaultPayloadType(96); + m_packetSamples = m_sampleRate / 50; // 20ms packet samples + timestampinc = m_sampleRate / 100; // 2 channels + break; + case PayloadG722: + m_sampleBytes = 1; + m_rtpSession.SetDefaultPayloadType(9); + m_packetSamples = 160; // Fixed 8 kB/s 20ms packet samples + timestampinc = 160; // 1 channel + break; + case PayloadOpus: + m_sampleBytes = 1; + m_rtpSession.SetDefaultPayloadType(96); + m_packetSamples = 160; // Payload size is 160 bytes + timestampinc = 960; // But increment is 960 + break; + case PayloadL16Mono: + default: + m_sampleBytes = 2; + m_rtpSession.SetDefaultPayloadType(96); + m_packetSamples = m_sampleRate / 50; // 20ms packet samples + timestampinc = m_sampleRate / 50; // 1 channel + break; + } + + m_bufferSize = m_packetSamples * m_sampleBytes; + + if (m_byteBuffer) + { + delete[] m_byteBuffer; + m_byteBuffer = nullptr; + } + + m_byteBuffer = new uint8_t[m_bufferSize]; + m_sampleBufferIndex = 0; + m_payloadType = payloadType; + + int status = m_rtpSession.SetTimestampUnit(1.0 / (double) m_sampleRate); + + if (status < 0) { + qCritical("RTPSink::setPayloadInformation: cannot set timestamp unit: %s", qrtplib::RTPGetErrorString(status).c_str()); + } else { + qDebug("RTPSink::setPayloadInformation: timestamp unit set to %f: %s", + 1.0 / (double) m_sampleRate, + qrtplib::RTPGetErrorString(status).c_str()); + } + + status = m_rtpSession.SetDefaultMark(false); + + if (status < 0) { + qCritical("RTPSink::setPayloadInformation: cannot set default mark: %s", qrtplib::RTPGetErrorString(status).c_str()); + } else { + qDebug("RTPSink::setPayloadInformation: set default mark to false: %s", qrtplib::RTPGetErrorString(status).c_str()); + } + + status = m_rtpSession.SetDefaultTimestampIncrement(timestampinc); + + if (status < 0) { + qCritical("RTPSink::setPayloadInformation: cannot set default timestamp increment: %s", qrtplib::RTPGetErrorString(status).c_str()); + } else { + qDebug("RTPSink::setPayloadInformation: set default timestamp increment to %d: %s", timestampinc, qrtplib::RTPGetErrorString(status).c_str()); + } + + int maximumPacketSize = m_bufferSize+20; // was +40 + + while (maximumPacketSize < RTP_MINPACKETSIZE) { + maximumPacketSize += m_bufferSize; + } + + status = m_rtpSession.SetMaximumPacketSize(maximumPacketSize); + + if (status < 0) { + qCritical("RTPSink::setPayloadInformation: cannot set maximum packet size: %s", qrtplib::RTPGetErrorString(status).c_str()); + } else { + qDebug("RTPSink::setPayloadInformation: set maximum packet size to %d bytes: %s", maximumPacketSize, qrtplib::RTPGetErrorString(status).c_str()); + } +} + +void RTPSink::setDestination(const QString& address, uint16_t port) +{ + m_rtpSession.ClearDestinations(); + m_rtpSession.DeleteDestination(qrtplib::RTPAddress(m_destip, m_destport)); + m_destip.setAddress(address); + m_destport = port; + + int status = m_rtpSession.AddDestination(qrtplib::RTPAddress(m_destip, m_destport)); + + if (status < 0) { + qCritical("RTPSink::setDestination: cannot set destination address: %s", qrtplib::RTPGetErrorString(status).c_str()); + } +} + +void RTPSink::deleteDestination(const QString& address, uint16_t port) +{ + QHostAddress destip(address); + + int status = m_rtpSession.DeleteDestination(qrtplib::RTPAddress(destip, port)); + + if (status < 0) { + qCritical("RTPSink::deleteDestination: cannot delete destination address: %s", qrtplib::RTPGetErrorString(status).c_str()); + } +} + +void RTPSink::addDestination(const QString& address, uint16_t port) +{ + QHostAddress destip(address); + + int status = m_rtpSession.AddDestination(qrtplib::RTPAddress(destip, port)); + + if (status < 0) { + qCritical("RTPSink::addDestination: cannot add destination address: %s", qrtplib::RTPGetErrorString(status).c_str()); + } else { + qDebug("RTPSink::addDestination: destination address set to %s:%d: %s", + address.toStdString().c_str(), + port, + qrtplib::RTPGetErrorString(status).c_str()); + } +} + +void RTPSink::write(const uint8_t *sampleByte) +{ + QMutexLocker locker(&m_mutex); + + if (m_sampleBufferIndex < m_packetSamples) + { + writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes], + sampleByte, + elemLength(m_payloadType), + m_sampleBytes, + m_endianReverse); + m_sampleBufferIndex++; + } + else + { + int status = m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize); + + if (status < 0) { + qCritical("RTPSink::write: cannot write packet: %s", qrtplib::RTPGetErrorString(status).c_str()); + } + + writeNetBuf(&m_byteBuffer[0], + sampleByte, + elemLength(m_payloadType), + m_sampleBytes, + m_endianReverse); + m_sampleBufferIndex = 1; + } +} + +void RTPSink::write(const uint8_t *sampleByteL, const uint8_t *sampleByteR) +{ + QMutexLocker locker(&m_mutex); + + if (m_sampleBufferIndex < m_packetSamples) + { + writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes], + sampleByteL, + elemLength(m_payloadType), + m_sampleBytes, + m_endianReverse); + writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes + elemLength(m_payloadType)], + sampleByteR, + elemLength(m_payloadType), + m_sampleBytes, + m_endianReverse); + m_sampleBufferIndex++; + } + else + { + int status = m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize); + + if (status < 0) { + qCritical("RTPSink::write: cannot write packet: %s", qrtplib::RTPGetErrorString(status).c_str()); + } + + writeNetBuf(&m_byteBuffer[0], sampleByteL, elemLength(m_payloadType), m_sampleBytes, m_endianReverse); + writeNetBuf(&m_byteBuffer[2], sampleByteR, elemLength(m_payloadType), m_sampleBytes, m_endianReverse); + m_sampleBufferIndex = 1; + } + +} + +void RTPSink::write(const uint8_t *samples, int nbSamples) +{ + int samplesIndex = 0; + QMutexLocker locker(&m_mutex); + + // fill remainder of buffer and send it + if (m_sampleBufferIndex + nbSamples > m_packetSamples) + { + writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes], + &samples[samplesIndex*m_sampleBytes], + elemLength(m_payloadType), + (m_packetSamples - m_sampleBufferIndex)*m_sampleBytes, + m_endianReverse); + m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize); + nbSamples -= (m_packetSamples - m_sampleBufferIndex); + m_sampleBufferIndex = 0; + } + + // send complete packets + while (nbSamples > m_packetSamples) + { + writeNetBuf(m_byteBuffer, + samples, + elemLength(m_payloadType), + m_bufferSize, + m_endianReverse); + m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize); + samplesIndex += m_packetSamples; + nbSamples -= m_packetSamples; + } + + // copy remainder of input to buffer + writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes], + &samples[samplesIndex*m_sampleBytes], + elemLength(m_payloadType), + nbSamples*m_sampleBytes,m_endianReverse); +} + +void RTPSink::writeNetBuf(uint8_t *dest, const uint8_t *src, unsigned int elemLen, unsigned int bytesLen, bool endianReverse) +{ + for (unsigned int i = 0; i < bytesLen; i += elemLen) + { + memcpy(&dest[i], &src[i], elemLen); + + if (endianReverse) { + std::reverse(&dest[i], &dest[i+elemLen]); + } + } +} + +unsigned int RTPSink::elemLength(PayloadType payloadType) +{ + switch (payloadType) + { + case PayloadPCMA8: + case PayloadPCMU8: + case PayloadG722: + case PayloadOpus: + case PayloadL8: + return sizeof(int8_t); + break; + case PayloadL16Stereo: + case PayloadL16Mono: + default: + return sizeof(int16_t); + break; + } +} + diff --git a/android/app/src/main/cpp/util/rtpsink.h b/android/app/src/main/cpp/util/rtpsink.h new file mode 100644 index 0000000..9d85e3d --- /dev/null +++ b/android/app/src/main/cpp/util/rtpsink.h @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_RTPSINK_H_ +#define SDRBASE_UTIL_RTPSINK_H_ + +#include +#include +#include +#include +#include + +// qrtplib includes +#include "rtpsession.h" +#include "rtpudptransmitter.h" +#include "rtpsessionparams.h" +#include "rtperrors.h" + +class QUdpSocket; + +class RTPSink +{ +public: + typedef enum + { + PayloadL16Mono, + PayloadL16Stereo, + PayloadL8, + PayloadPCMA8, + PayloadPCMU8, + PayloadG722, + PayloadOpus + } PayloadType; + + RTPSink(QUdpSocket *udpSocket, int sampleRate, bool stereo); + ~RTPSink(); + + bool isValid() const { return m_valid; } + void setPayloadInformation(PayloadType payloadType, int sampleRate); + + void setDestination(const QString& address, uint16_t port); + void deleteDestination(const QString& address, uint16_t port); + void addDestination(const QString& address, uint16_t port); + + void write(const uint8_t *sampleByte); + void write(const uint8_t *sampleByteL, const uint8_t *sampleByteR); + void write(const uint8_t *sampleByte, int nbSamples); + +protected: + /** Reverse endianness in destination buffer */ + static void writeNetBuf(uint8_t *dest, const uint8_t *src, unsigned int elemLen, unsigned int bytesLen, bool endianReverse); + static unsigned int elemLength(PayloadType payloadType); + + bool m_valid; + PayloadType m_payloadType; + int m_sampleRate; + int m_sampleBytes; + int m_packetSamples; + int m_bufferSize; + int m_sampleBufferIndex; + uint8_t *m_byteBuffer; + QHostAddress m_destip; + uint16_t m_destport; + qrtplib::RTPSession m_rtpSession; + qrtplib::RTPSessionParams m_rtpSessionParams; + qrtplib::RTPUDPTransmissionParams m_rtpTransmissionParams; + qrtplib::RTPUDPTransmitter m_rtpTransmitter; + bool m_endianReverse; + QRecursiveMutex m_mutex; +}; + + +#endif /* SDRBASE_UTIL_RTPSINK_H_ */ diff --git a/android/app/src/main/cpp/util/rtty.cpp b/android/app/src/main/cpp/util/rtty.cpp new file mode 100644 index 0000000..3fa7097 --- /dev/null +++ b/android/app/src/main/cpp/util/rtty.cpp @@ -0,0 +1,290 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/rtty.h" + +// From http://www.ct2fzi.net/abreviations/abreviations.html +const QHash Rtty::m_acronyms = { + {"AA", "After All"}, + {"AB", "All Before"}, + {"ABT", "About"}, + {"ACK", "Acknowledgement"}, + {"ADEE", "Addressee"}, + {"ADR", "Address"}, + {"AF", "Audio Frequency"}, + {"AGN", "Again"}, + {"AM", "Amplitude Modulation"}, + {"AMU", "Antenna Matching Unit"}, + {"ANS", "Answer"}, + {"ANT", "Antenna"}, + {"ARQ", "Automatic Repeat Request"}, + {"ATU", "Antenna Tuning Unit"}, + {"B4", "Before"}, + {"BCN", "Beacon"}, + {"BCNU", "Be Seeing You"}, + {"BD", "Bad"}, + {"BK", "Break"}, + {"BN", "Been"}, + {"BTH", "Both"}, + {"BTR", "Better"}, + {"BTW", "By The Way"}, + {"BTU", "Back To You"}, + {"C", "Correct"}, + {"CBA", "Callbook Address"}, + {"CFM", "Confirm"}, + {"CK", "Check"}, + {"CKT", "Circuit"}, + {"CL", "Closing Down"}, + {"CLBK", "Callbook"}, + {"CLD", "Called"}, + {"CLG", "Calling"}, + {"CMG", "Coming"}, + {"CNT", "Can't"}, + {"COMP", "Computer"}, + {"CONDX", "Conditions"}, + {"COZ", "Because"}, + {"CPI", "Copy"}, + {"CQ", "General Call"}, + {"CRD", "Card"}, + {"CS", "Callsign"}, + {"CTCSS", "Continuous Tone Coded Squelch System"}, + {"CU", "See You"}, + {"CUAGN", "See You Again"}, + {"CUD", "Could"}, + {"CUL", "See You Later"}, + {"CUM", "Come"}, + {"CUZ", "Because"}, + {"CW", "Continuous Wave / Morse"}, + {"DA", "Day"}, + {"DE", "From"}, + {"DF", "Direction Finding"}, + {"DIFF", "Difference"}, + {"DLD", "Delivered"}, + {"DLVD", "Delivered"}, + {"DN", "Down"}, + {"DR", "Dear"}, + {"DSB", "Double Side Band"}, + {"DSP", "Digital Signal Processing"}, + {"DSW", "Goodbye (Russian)"}, + {"DWN", "Down"}, + {"DX", "Distance"}, + {"EL", "Element"}, + {"EME", "Earth-Moon-Earth"}, + {"ENUF", "Enough"}, + {"ES", "And"}, + {"EU", "Europe"}, + {"EVE", "Evening"}, + {"FB", "Fine Business"}, + {"FER", "For"}, + {"FIO", "For Information Only"}, + {"FM", "Frequency Modulation"}, + {"FQ", "Frequency"}, + {"FREQ", "Frequency"}, + {"FSD", "Full Scale Deflection"}, + {"FSK", "Frequency Shift Keying"}, + {"FWD", "Forward"}, + {"FWIW", "For What It's Worth"}, + {"FYI", "For Your Information"}, + {"GA", "Good Afternoon"}, + {"GB", "Good Bye"}, + {"GD", "Good Day"}, + {"GE", "Good Evening"}, + {"GESS", "Guess"}, + {"GG", "Going"}, + {"GLD", "Glad"}, + {"GM", "Good Morning"}, + {"GMT", "Greenwich Mean Time"}, + {"GN", "Good Night"}, + {"GND", "Ground"}, + {"GP", "Ground Plane"}, + {"GPS", "Global Positioning System"}, + {"GS", "Green Stamp"}, + {"GUD", "Good"}, + {"GV", "Give"}, + {"GVG", "Giving"}, + {"HAGD", "Have A Good Day"}, + {"HAGWE", "Have A Good Weekend"}, + {"HF", "High Frequency"}, + {"HI", "High"}, + {"HPE", "Hope"}, + {"HQ", "Headquarters"}, + {"HR", "Here / Hour"}, + {"HRD", "Heard"}, + {"HV", "Have"}, + {"HVG", "Having"}, + {"HVY", "Heavy"}, + {"HW", "How"}, + {"IMHO", "In My Humble Opinion"}, + {"IMI", "Say again"}, + {"K", "Over"}, + {"KN", "Over"}, + {"LF", "Low Frequency"}, + {"LNG", "Long"}, + {"LP", "Long Path"}, + {"LSB", "Lower Sideband"}, + {"LSN", "Listen"}, + {"LTR", "Later"}, + {"LV", "Leave"}, + {"LVG", "Leaving"}, + {"LW", "Long Wire"}, + {"MGR", "Manager"}, + {"MI", "My"}, + {"MNI", "Many"}, + {"MOM", "Moment"}, + {"MS", "Meteor Scatter"}, + {"MSG", "Message"}, + {"N", "No"}, + {"NCS", "Net Control Station"}, + {"ND", "Nothing Doing"}, + {"NM", "No More"}, + {"NR", "Near / Number"}, + {"NW", "Now"}, + {"OB", "Old Boy"}, + {"OC", "Old Chap"}, + {"OM", "Old Man"}, + {"OP", "Operator"}, + {"OPR", "Operator"}, + {"OT", "Old Timer"}, + {"OW", "Old Woman"}, + {"PA", "Power Amplifier"}, + {"PBL", "Preamble"}, + {"PKG", "Package"}, + {"POV", "Point Of View"}, + {"PSE", "Please"}, + {"PSK", "Phase Shift Keying"}, + {"PT", "Point"}, + {"PTT", "Push To Talk"}, + {"PWR", "Power"}, + {"PX", "Prefix"}, + {"QRA", "Address"}, + {"QRG", "Frequency"}, + {"QRK", "Readability"}, + {"QRL", "Busy"}, + {"QRM", "Interference"}, + {"QRN", "Noise"}, + {"QRO", "High Power"}, + {"QRP", "Low Power"}, + {"QRQ", "Send Faster"}, + {"QRS", "Send Slower"}, + {"QRSS", "Send Very Slowly"}, + {"QRT", "Stop Sending"}, + {"QRU", "Nothing Further To Say"}, + {"QRV", "Ready"}, + {"QRX", "Wait"}, + {"QRZ", "Who Is Calling Me"}, + {"QSA", "Signal Strength"}, + {"QSB", "Fading"}, + {"QSK", "Break-in"}, + {"QSL", "All Received OK"}, + {"QSLL", "I Will Send A QSL Card"}, + {"QSO", "Contact"}, + {"QSP", "Relay A Message"}, + {"QSX", "Listening On Frequency"}, + {"QSY", "Change Frequency"}, + {"QTH", "Location"}, + {"R", "Received OK"}, + {"RC", "Ragchew"}, + {"RCD", "Recieved"}, + {"RCVR", "Receiver"}, + {"RE", "Regarding"}, + {"REF", "Reference"}, + {"RF", "Radio Frequency"}, + {"RFI", "Radio Frequency Interference"}, + {"RPT", "Repeat / Report"}, + {"RST", "Signal Report"}, + {"RTTY", "Radio Teletype"}, + {"RX", "Receive"}, + {"SA", "Say"}, + {"SDR", "Software Defined Radio"}, + {"SEZ", "Says"}, + {"SGD", "Signed"}, + {"SHUD", "Should"}, + {"SIG", "Signal"}, + {"SK", "End Of Work"}, + {"SKED", "Schedule"}, + {"SN", "Soon"}, + {"SP", "Short Path"}, + {"SRI", "Sorry"}, + {"SSB", "Single Sideband"}, + {"STN", "Station"}, + {"SUM", "Some"}, + {"SVC", "Service"}, + {"SWR", "Standing Wave Ratio"}, + {"TFC", "Traffic"}, + {"TIA", "Thanks In Advance"}, + {"TKS", "Thanks"}, + {"TMW", "Tomorrow"}, + {"TNC", "Terminal Node Controller"}, + {"TNX", "Thanks"}, + {"TR", "Transmit"}, + {"T/R", "Transmit/Receive"}, + {"TRBL", "Trouble"}, + {"TRF", "Tuned Radio Frequency"}, + {"TRIX", "Tricks"}, + {"TRX", "Transceiver"}, + {"TT", "That"}, + {"TTS", "That Is"}, + {"TU", "Thank You"}, + {"TVI", "Television Interference"}, + {"TX", "Transmit"}, + {"TXT", "Text"}, + {"U", "You"}, + {"UHF", "Ultra High Frequency"}, + {"UNLIS", "Unlicensed"}, + {"UR", "Your"}, + {"URS", "Yours"}, + {"UTC", "Coordinated Universal Time"}, + {"V", "Volts"}, + {"VHF", "Very High Frequency"}, + {"VE", "Understood"}, + {"VERT", "Vertical"}, + {"VFB", "Very Fine Business"}, + {"VFO", "Variable Frequency Oscillator"}, + {"VLF", "Very Low Frequency"}, + {"VOX", "Voice Operated"}, + {"VSB", "Vestigial Sideband"}, + {"VSWR", "Voltage Standing Wave Ratio"}, + {"VY", "Very"}, + {"W", "Watts"}, + {"WA", "Word After"}, + {"WAT", "What"}, + {"WATSA", "What Say"}, + {"WB", "Word Before"}, + {"WD", "Word"}, + {"WDS", "Words"}, + {"WID", "With"}, + {"WKD", "Worked"}, + {"WKG", "Working"}, + {"WL", "Will"}, + {"WPM", "Words Per Minute"}, + {"WRD", "Word"}, + {"WRK", "Work"}, + {"WUD", "Would"}, + {"WX", "Weather"}, + {"XCVR", "Transceiver"}, + {"XMTR", "Transmitter"}, + {"XTAL", "Crystal"}, + {"YF", "Wife"}, + {"YL", "Young Lady"}, + {"YR", "Year"}, + {"Z", "Zulu Time"}, + {"30", "I Have Nothing More to Send"}, + {"33", "Fondest Regards"}, + {"55", "Best Success"}, + {"73", "Best Wishes"}, + {"88", "Love And Kisses"}, +}; diff --git a/android/app/src/main/cpp/util/rtty.h b/android/app/src/main/cpp/util/rtty.h new file mode 100644 index 0000000..db83488 --- /dev/null +++ b/android/app/src/main/cpp/util/rtty.h @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_RTTY_H +#define INCLUDE_UTIL_RTTY_H + +#include + +#include "export.h" + +class SDRBASE_API Rtty +{ +public: + static const QHash m_acronyms; +}; + +#endif /* INCLUDE_UTIL_RTTY_H */ diff --git a/android/app/src/main/cpp/util/samplesourceserializer.cpp b/android/app/src/main/cpp/util/samplesourceserializer.cpp new file mode 100644 index 0000000..3599f59 --- /dev/null +++ b/android/app/src/main/cpp/util/samplesourceserializer.cpp @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/samplesourceserializer.h" + +const uint SampleSourceSerializer::m_version = 1; + +void SampleSourceSerializer::writeSerializedData(const Data& data, QByteArray& serializedData) +{ + SimpleSerializer s(1); + + s.writeU64(1, data.m_frequency); + s.writeS32(2, data.m_correction); + s.writeS32(3, data.m_rate); + s.writeU32(4, data.m_log2Decim); + s.writeS32(5, data.m_bandwidth); + s.writeS32(6, data.m_fcPosition); + s.writeS32(7, data.m_lnaGain); + s.writeS32(8, data.m_RxGain1); + s.writeS32(9, data.m_RxGain2); + s.writeS32(10, data.m_RxGain3); + + serializedData = s.final(); +} + +bool SampleSourceSerializer::readSerializedData(const QByteArray& serializedData, Data& data) +{ + SimpleDeserializer d(serializedData); + + if (!d.isValid()) + { + setDefaults(data); + return false; + } + + if (d.getVersion() == m_version) + { + d.readU64(1, &data.m_frequency, 0); + d.readS32(2, &data.m_correction, 0); + d.readS32(3, &data.m_rate, 0); + d.readU32(4, &data.m_log2Decim, 0); + d.readS32(5, &data.m_bandwidth, 0); + d.readS32(6, &data.m_fcPosition, 0); + d.readS32(7, &data.m_lnaGain, 0); + d.readS32(8, &data.m_RxGain1, 0); + d.readS32(9, &data.m_RxGain2, 0); + d.readS32(10, &data.m_RxGain3, 0); + + return true; + } + else + { + setDefaults(data); + return false; + } +} + +void SampleSourceSerializer::setDefaults(Data& data) +{ + data.m_frequency = 0; + data.m_correction = 0; + data.m_rate = 0; + data.m_log2Decim = 0; + data.m_bandwidth = 0; + data.m_fcPosition = 0; + data.m_lnaGain = 0; + data.m_RxGain1 = 0; + data.m_RxGain2 = 0; + data.m_RxGain3 = 0; +} diff --git a/android/app/src/main/cpp/util/samplesourceserializer.h b/android/app/src/main/cpp/util/samplesourceserializer.h new file mode 100644 index 0000000..2a49c24 --- /dev/null +++ b/android/app/src/main/cpp/util/samplesourceserializer.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_SAMPLESOURCESERIALIZER_H_ +#define INCLUDE_UTIL_SAMPLESOURCESERIALIZER_H_ + +#include "util/simpleserializer.h" +#include "export.h" + +class SDRBASE_API SampleSourceSerializer +{ +public: + struct Data + { + quint64 m_frequency; //!< RF center frequency + qint32 m_correction; //!< LO correction factor + qint32 m_rate; //!< RF sampler sample rate + quint32 m_log2Decim; //!< Decimation ratio log2 + qint32 m_bandwidth; //!< RF bandwidth + qint32 m_fcPosition; //!< Decimated band placement (infradyne, supradyne, centered) + qint32 m_lnaGain; //!< RF LNA gain + qint32 m_RxGain1; //!< Rx first stage amplifier gain + qint32 m_RxGain2; //!< Rx second stage amplifier gain + qint32 m_RxGain3; //!< Rx third stage amplifier gain + }; + + static void writeSerializedData(const Data& data, QByteArray& serializedData); + static bool readSerializedData(const QByteArray& serializedData, Data& data); + static void setDefaults(Data& data); + static uint getSerializerVersion() { return m_version; } + +protected: + static const uint m_version; +}; + + + +#endif /* INCLUDE_UTIL_SAMPLESOURCESERIALIZER_H_ */ diff --git a/android/app/src/main/cpp/util/sdrangelserverlist.cpp b/android/app/src/main/cpp/util/sdrangelserverlist.cpp new file mode 100644 index 0000000..eed5e9c --- /dev/null +++ b/android/app/src/main/cpp/util/sdrangelserverlist.cpp @@ -0,0 +1,186 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "sdrangelserverlist.h" + +#include +#include +#include +#include +#include +#include + +SDRangelServerList::SDRangelServerList() +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect(m_networkManager, &QNetworkAccessManager::finished, this, &SDRangelServerList::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("sdrangelserver"))) { + qDebug() << "Failed to create cache/sdrangelserver"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("sdrangelserver")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); + + connect(&m_timer, &QTimer::timeout, this, &SDRangelServerList::update); +} + +SDRangelServerList::~SDRangelServerList() +{ + QObject::disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &SDRangelServerList::handleReply); + delete m_networkManager; +} + +void SDRangelServerList::getData() +{ + QUrl url = QUrl("https://sdrangel.org/websdr/websdrs.json"); + m_networkManager->get(QNetworkRequest(url)); +} + +void SDRangelServerList::getDataPeriodically(int periodInMins) +{ + m_timer.setInterval(periodInMins*60*1000); + m_timer.start(); + update(); +} + +void SDRangelServerList::update() +{ + getData(); +} + +void SDRangelServerList::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QString url = reply->url().toEncoded().constData(); + QByteArray bytes = reply->readAll(); + + handleJSON(url, bytes); + } + else + { + qDebug() << "SDRangelServerList::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "SDRangelServerList::handleReply: reply is null"; + } +} + +void SDRangelServerList::handleJSON(const QString& url, const QByteArray& bytes) +{ + (void) url; + + QList sdrs; + QJsonDocument document = QJsonDocument::fromJson(bytes); + + if (document.isArray()) + { + QJsonArray servers = document.array(); + + for (auto valRef : servers) + { + if (valRef.isObject()) + { + QJsonObject serverObj = valRef.toObject(); + SDRangelServer sdr; + + if (serverObj.contains(QStringLiteral("address"))) { + sdr.m_address = serverObj.value(QStringLiteral("address")).toString(); + } + if (serverObj.contains(QStringLiteral("port"))) { + sdr.m_port = serverObj.value(QStringLiteral("port")).toInt(); + } + if (serverObj.contains(QStringLiteral("protocol"))) { + sdr.m_protocol = serverObj.value(QStringLiteral("protocol")).toString(); + } + if (serverObj.contains(QStringLiteral("minFrequency"))) { + sdr.m_minFrequency = serverObj.value(QStringLiteral("minFrequency")).toInt(); + } + if (serverObj.contains(QStringLiteral("maxFrequency"))) { + sdr.m_maxFrequency = serverObj.value(QStringLiteral("maxFrequency")).toInt(); + } + if (serverObj.contains(QStringLiteral("maxSampleRate"))) { + sdr.m_maxSampleRate = serverObj.value(QStringLiteral("maxSampleRate")).toInt(); + } + if (serverObj.contains(QStringLiteral("device"))) { + sdr.m_device = serverObj.value(QStringLiteral("device")).toString(); + } + if (serverObj.contains(QStringLiteral("antenna"))) { + sdr.m_antenna = serverObj.value(QStringLiteral("antenna")).toString(); + } + if (serverObj.contains(QStringLiteral("remoteControl"))) { + sdr.m_remoteControl = serverObj.value(QStringLiteral("remoteControl")).toInt() == 1; + } + if (serverObj.contains(QStringLiteral("stationName"))) { + sdr.m_stationName = serverObj.value(QStringLiteral("stationName")).toString(); + } + if (serverObj.contains(QStringLiteral("location"))) { + sdr.m_location = serverObj.value(QStringLiteral("location")).toString(); + } + if (serverObj.contains(QStringLiteral("latitude"))) { + sdr.m_latitude = serverObj.value(QStringLiteral("latitude")).toDouble(); + } + if (serverObj.contains(QStringLiteral("longitude"))) { + sdr.m_longitude = serverObj.value(QStringLiteral("longitude")).toDouble(); + } + if (serverObj.contains(QStringLiteral("altitude"))) { + sdr.m_altitude = serverObj.value(QStringLiteral("altitude")).toDouble(); + } + if (serverObj.contains(QStringLiteral("isotropic"))) { + sdr.m_isotropic = serverObj.value(QStringLiteral("isotropic")).toInt() == 1; + } + if (serverObj.contains(QStringLiteral("azimuth"))) { + sdr.m_azimuth = serverObj.value(QStringLiteral("azimuth")).toDouble(); + } + if (serverObj.contains(QStringLiteral("elevation"))) { + sdr.m_elevation = serverObj.value(QStringLiteral("elevation")).toDouble(); + } + if (serverObj.contains(QStringLiteral("clients"))) { + sdr.m_clients = serverObj.value(QStringLiteral("clients")).toInt(); + } + if (serverObj.contains(QStringLiteral("maxClients"))) { + sdr.m_maxClients = serverObj.value(QStringLiteral("maxClients")).toInt(); + } + if (serverObj.contains(QStringLiteral("timeLimit"))) { + sdr.m_timeLimit = serverObj.value(QStringLiteral("timeLimit")).toInt(); + } + + sdrs.append(sdr); + } + else + { + qDebug() << "SDRangelServerList::handleJSON: Element not an object:\n" << valRef; + } + } + } + else + { + qDebug() << "SDRangelServerList::handleJSON: Doc doesn't contain an array:\n" << document; + } + + emit dataUpdated(sdrs); +} diff --git a/android/app/src/main/cpp/util/sdrangelserverlist.h b/android/app/src/main/cpp/util/sdrangelserverlist.h new file mode 100644 index 0000000..9cb08b4 --- /dev/null +++ b/android/app/src/main/cpp/util/sdrangelserverlist.h @@ -0,0 +1,82 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SDRANGELSERVERLIST_H +#define INCLUDE_SDRANGELSERVERLIST_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// Gets a list of public SDRangel Servers from https://sdrangel.org/websdr/ +class SDRBASE_API SDRangelServerList : public QObject +{ + Q_OBJECT + +public: + + struct SDRangelServer { + QString m_address; + quint16 m_port; + QString m_protocol; + qint64 m_minFrequency; + qint64 m_maxFrequency; + int m_maxSampleRate; + QString m_device; + QString m_antenna; + bool m_remoteControl; + QString m_stationName; + QString m_location; + float m_latitude; + float m_longitude; + float m_altitude; + bool m_isotropic; + float m_azimuth; + float m_elevation; + int m_clients; + int m_maxClients; + int m_timeLimit; // In minutes + }; + + SDRangelServerList(); + ~SDRangelServerList(); + + void getData(); + void getDataPeriodically(int periodInMins=1); + +public slots: + void handleReply(QNetworkReply* reply); + void update(); + +signals: + void dataUpdated(const QList& sdrs); // Emitted when data are available. + +private: + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + QTimer m_timer; // Timer for periodic updates + + void handleJSON(const QString& url, const QByteArray& bytes); + +}; + +#endif /* INCLUDE_SDRANGELSERVERLIST_H */ diff --git a/android/app/src/main/cpp/util/serialutil.cpp b/android/app/src/main/cpp/util/serialutil.cpp new file mode 100644 index 0000000..183718e --- /dev/null +++ b/android/app/src/main/cpp/util/serialutil.cpp @@ -0,0 +1,123 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#if defined(_WIN32) +#include +#include +#include +#elif defined(__linux__) +#include +#include +#include +#include +#endif + +#include "serialutil.h" + +#if defined(_WIN32) +void SerialUtil::getComPorts(std::vector& comPorts, const std::string& regexStr) +{ + (void) regexStr; + TCHAR lpTargetPath[5000]; // buffer to store the path of the COMPORTS + DWORD test; + bool gotPort = 0; // in case the port is not found + + char portName[100]; + + for (int i = 0; i<255; i++) // checking ports from COM0 to COM255 + { + snprintf(portName, sizeof(portName), "COM%d", i); + + test = QueryDosDeviceA((LPCSTR)portName, (LPSTR)lpTargetPath, 5000); + + // Test the return value and error if any + if (test != 0) //QueryDosDevice returns zero if it didn't find an object + { + comPorts.push_back(std::string(portName)); + } + + if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + lpTargetPath[10000]; // in case the buffer got filled, increase size of the buffer. + continue; + } + } +} +#elif defined(__linux__) +void SerialUtil::getComPorts(std::vector& comPorts, const std::string& regexStr) +{ + int n; + struct dirent **namelist; + comPorts.clear(); + const char* sysdir = "/sys/class/tty/"; + struct stat fileStat; + std::regex devRegex(regexStr); + std::smatch devMatch; + std::string devString = "/dev/"; + + // Scan through /sys/class/tty - it contains all tty-devices in the system + n = scandir(sysdir, &namelist, NULL, alphasort); + if (n < 0) + { + perror("scandir"); + } + else + { + while (n--) + { + if (strcmp(namelist[n]->d_name, "..") && strcmp(namelist[n]->d_name, ".")) + { + // Construct full absolute file path + std::string fullpath = sysdir; + std::string devName = std::string(namelist[n]->d_name); + fullpath += devName; + fullpath += std::string("/device"); + + if (lstat(fullpath.c_str(), &fileStat) == 0) + { + if (regexStr.size() != 0) + { + std::regex_search(devName, devMatch, devRegex); + + if (devMatch.size() != 0) { + comPorts.push_back(devString + devName); + } + } + else + { + comPorts.push_back(devString + devName); + } + } + } + + free(namelist[n]); + } + + free(namelist); + } +} +#else // not supported +void SerialUtil::getComPorts(std::vector& comPorts, const std::string& regexStr) +{ + (void) comPorts; + (void) regexStr; +} +#endif diff --git a/android/app/src/main/cpp/util/serialutil.h b/android/app/src/main/cpp/util/serialutil.h new file mode 100644 index 0000000..d39a787 --- /dev/null +++ b/android/app/src/main/cpp/util/serialutil.h @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_SERIALUTIL_H_ +#define SDRBASE_UTIL_SERIALUTIL_H_ + +#include +#include +#include "export.h" + +class SDRBASE_API SerialUtil +{ +public: + static void getComPorts(std::vector& comPorts, const std::string& regexStr); +}; + +#endif // SDRBASE_UTIL_SERIALUTIL_H_ diff --git a/android/app/src/main/cpp/util/sha512.h b/android/app/src/main/cpp/util/sha512.h new file mode 100644 index 0000000..26920a1 --- /dev/null +++ b/android/app/src/main/cpp/util/sha512.h @@ -0,0 +1,314 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +/** + * @file sha512.hh + * @author Stefan Wilhelm (stfwi) + * @ccflags + * @ldflags + * @platform linux, bsd, windows + * @standard >= c++98 + * + * SHA512 calculation class template. + * + * ------------------------------------------------------------------------------------- + * +++ BSD license header +++ + * Copyright (c) 2010, 2012, Stefan Wilhelm (stfwi, ) + * All rights reserved. + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: (1) Redistributions + * of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. (2) Redistributions in binary form must reproduce + * the above copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the distribution. + * (3) Neither the name of the project nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ------------------------------------------------------------------------------------- + * 01-2013: (stfwi) Class to template class, reformatting + */ +#ifndef SHA512_HH +#define SHA512_HH + +#if defined(OS_WIN) || defined (_WINDOWS_) || defined(_WIN32) || defined(__MSC_VER) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +namespace sw { namespace detail { + +/** + * @class basic_sha512 + * @template + */ +template +class basic_sha512 +{ +public: + + /** + * Types + */ + typedef std::basic_string str_t; + +public: + + /** + * Constructor + */ + basic_sha512() + { clear(); } + + /** + * Destructor + */ + ~basic_sha512() + { ; } + +public: + + /** + * Clear/reset all internal buffers and states. + */ + void clear() + { + sum_[0] = 0x6a09e667f3bcc908; sum_[1] = 0xbb67ae8584caa73b; + sum_[2] = 0x3c6ef372fe94f82b; sum_[3] = 0xa54ff53a5f1d36f1; + sum_[4] = 0x510e527fade682d1; sum_[5] = 0x9b05688c2b3e6c1f; + sum_[6] = 0x1f83d9abfb41bd6b; sum_[7] = 0x5be0cd19137e2179; + sz_ = 0; iterations_ = 0; memset(&block_, 0, sizeof(block_)); + } + + /** + * Push new binary data into the internal buf_ and recalculate the checksum. + * @param const void* data + * @param size_t size + */ + void update(const void* data, size_t size) + { + unsigned nb, n, n_tail; + const uint8_t *p; + n = 128 - sz_; + n_tail = size < n ? size : n; + memcpy(&block_[sz_], data, n_tail); + if (sz_ + size < 128) { sz_ += size; return; } + n = size - n_tail; + nb = n >> 7; + p = (const uint8_t*) data + n_tail; + transform(block_, 1); + transform(p, nb); + n_tail = n & 0x7f; + memcpy(block_, &p[nb << 7], n_tail); + sz_ = n_tail; + iterations_ += (nb+1) << 7; + } + + /** + * Finanlise checksum, return hex string. + * @return str_t + */ + str_t final_data() + { + #if (defined (BYTE_ORDER)) && (defined (BIG_ENDIAN)) && ((BYTE_ORDER == BIG_ENDIAN)) + #define U32_B(x,b) *((b)+0)=(uint8_t)((x)); *((b)+1)=(uint8_t)((x)>>8); \ + *((b)+2)=(uint8_t)((x)>>16); *((b)+3)=(uint8_t)((x)>>24); + #else + #define U32_B(x,b) *((b)+3)=(uint8_t)((x)); *((b)+2)=(uint8_t)((x)>>8); \ + *((b)+1)=(uint8_t)((x)>>16); *((b)+0)=(uint8_t)((x)>>24); + #endif + unsigned nb, n; + uint64_t n_total; + nb = 1 + ((0x80-17) < (sz_ & 0x7f)); + n_total = (iterations_ + sz_) << 3; + n = nb << 7; + memset(block_ + sz_, 0, n - sz_); + block_[sz_] = 0x80; + U32_B(n_total, block_ + n-4); + transform(block_, nb); + std::basic_stringstream ss; // hex string + for (unsigned i = 0; i < 8; ++i) { + ss << std::hex << std::setfill('0') << std::setw(16) << (sum_[i]); + } + clear(); + return ss.str(); + #undef U32_B + } + +public: + + /** + * Calculates the SHA256 for a given string. + * @param const str_t & s + * @return str_t + */ + static str_t calculate(const str_t & s) + { + basic_sha512 r; + r.update(s.data(), s.length()); + return r.final_data(); + } + + /** + * Calculates the SHA256 for a given C-string. + * @param const char* s + * @return str_t + */ + static str_t calculate(const void* data, size_t size) + { basic_sha512 r; r.update(data, size); return r.final_data(); } + + /** + * Calculates the SHA256 for a stream. Returns an empty string on error. + * @param std::istream & is + * @return str_t + */ + static str_t calculate(std::istream & is) + { + basic_sha512 r; + char data[64]; + while(is.good() && is.read(data, sizeof(data)).good()) { + r.update(data, sizeof(data)); + } + if(!is.eof()) return str_t(); + if(is.gcount()) r.update(data, is.gcount()); + return r.final_data(); + } + + /** + * Calculates the SHA256 checksum for a given file, either read binary or as text. + * @param const str_t & path + * @param bool binary = true + * @return str_t + */ + static str_t file(const str_t & path, bool binary=true) + { + std::ifstream fs; + fs.open(path.c_str(), binary ? (std::ios::in|std::ios::binary) : (std::ios::in)); + str_t s = calculate(fs); + fs.close(); + return s; + } + +private: + + /** + * Performs the SHA256 transformation on a given block + * @param uint32_t *block + */ + void transform(const uint8_t *data, size_t size) + { + #define SR(x, n) (x >> n) + #define RR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) + #define RL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) + #define CH(x, y, z) ((x & y) ^ (~x & z)) + #define MJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) + #define F1(x) (RR(x, 28) ^ RR(x, 34) ^ RR(x, 39)) + #define F2(x) (RR(x, 14) ^ RR(x, 18) ^ RR(x, 41)) + #define F3(x) (RR(x, 1) ^ RR(x, 8) ^ SR(x, 7)) + #define F4(x) (RR(x, 19) ^ RR(x, 61) ^ SR(x, 6)) + #if (defined (BYTE_ORDER)) && (defined (BIG_ENDIAN)) && ((BYTE_ORDER == BIG_ENDIAN)) + #define B_U64(b,x) *(x)=((uint64_t)*((b)+0))|((uint64_t)*((b)+1)<<8)|\ + ((uint64_t)*((b)+2)<<16)|((uint64_t)*((b)+3)<<24)|((uint64_t)*((b)+4)<<32)|\ + ((uint64_t)*((b)+5)<<40)|((uint64_t)*((b)+6)<<48)|((uint64_t)*((b)+7)<<56); + #else + #define B_U64(b,x) *(x)=((uint64_t)*((b)+7))|((uint64_t)*((b)+6)<<8)|\ + ((uint64_t)*((b)+5)<<16)|((uint64_t)*((b)+4)<<24)|((uint64_t)*((b)+3)<<32)|\ + ((uint64_t)*((b)+2)<<40)|((uint64_t)*((b)+1)<<48)|((uint64_t)*((b)+0)<<56); + #endif + uint64_t t, u, v[8], w[80]; + const uint8_t *tblock; + unsigned j; + for(unsigned i = 0; i < size; ++i) { + tblock = data + (i << 7); + for(j = 0; j < 16; ++j) B_U64(&tblock[j<<3], &w[j]); + for(j = 16; j < 80; ++j) w[j] = F4(w[j-2]) + w[j-7] + F3(w[j-15]) + w[j-16]; + for(j = 0; j < 8; ++j) v[j] = sum_[j]; + for(j = 0; j < 80; ++j) { + t = v[7] + F2(v[4]) + CH(v[4], v[5], v[6]) + lut_[j] + w[j]; + u = F1(v[0]) + MJ(v[0], v[1], v[2]); v[7] = v[6]; v[6] = v[5]; v[5] = v[4]; + v[4] = v[3] + t; v[3] = v[2]; v[2] = v[1]; v[1] = v[0]; v[0] = t + u; + } + for(j = 0; j < 8; ++j) sum_[j] += v[j]; + } + #undef SR + #undef RR + #undef RL + #undef CH + #undef MJ + #undef F1 + #undef F2 + #undef F3 + #undef F4 + #undef B_U64 + } + +private: + + uint64_t iterations_; // Number of iterations + uint64_t sum_[8]; // Intermediate checksum buffer + unsigned sz_; // Number of currently stored bytes in the block + uint8_t block_[256]; + static const uint64_t lut_[80]; // Lookup table +}; + +template +const uint64_t basic_sha512::lut_[80] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 +}; + +}} + +namespace sw { + typedef detail::basic_sha512<> sha512; +} + +#endif diff --git a/android/app/src/main/cpp/util/simpleserializer.cpp b/android/app/src/main/cpp/util/simpleserializer.cpp new file mode 100644 index 0000000..77857b0 --- /dev/null +++ b/android/app/src/main/cpp/util/simpleserializer.cpp @@ -0,0 +1,744 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#include +#include "util/simpleserializer.h" + +#if __WORDSIZE == 64 +#define PRINTF_FORMAT_S32 "%d" +#define PRINTF_FORMAT_U32 "%u" +#define PRINTF_FORMAT_S64 "%d" +#define PRINTF_FORMAT_U64 "%u" +#else +#define PRINTF_FORMAT_S32 "%d" +#define PRINTF_FORMAT_U32 "%u" +#define PRINTF_FORMAT_S64 "%lld" +#define PRINTF_FORMAT_U64 "%llu" +#endif + +template struct Assert { }; +typedef Assert<(bool(sizeof(float) == 4))> float_must_be_32_bits[bool(sizeof(float) == 4) ? 1 : -1]; +typedef Assert<(bool(sizeof(double) == 8))> double_must_be_64_bits[bool(sizeof(double) == 8) ? 1 : -1]; + +SimpleSerializer::SimpleSerializer(uint version) : + m_data(), + m_finalized(false) +{ + m_data.reserve(100); + + // write version information + int length; + if(version >= (1 << 24)) + length = 4; + else if(version >= (1 << 16)) + length = 3; + else if(version >= (1 << 8)) + length = 2; + else if(version > 0) + length = 1; + else length = 0; + if(!writeTag(TVersion, 0, length)) + return; + length--; + for(int i = length; i >= 0; i--) + m_data.push_back((char)((version >> (i * 8)) & 0xff)); +} + +void SimpleSerializer::writeS32(quint32 id, qint32 value) +{ + int length; + + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if((value < -(1 << 23)) || (value >= (1 << 23))) + length = 4; + else if((value < -(1 << 15)) || (value >= (1 << 15))) + length = 3; + else if((value < -(1 << 7)) || (value >= (1 << 7))) + length = 2; + else if(value != 0) + length = 1; + else length = 0; + + if(!writeTag(TSigned32, id, length)) + return; + + length--; + for(int i = length; i >= 0; i--) + m_data.push_back((char)((value >> (i * 8)) & 0xff)); +} + +void SimpleSerializer::writeU32(quint32 id, quint32 value) +{ + int length; + + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if(value >= (1 << 24)) + length = 4; + else if(value >= (1 << 16)) + length = 3; + else if(value >= (1 << 8)) + length = 2; + else if(value > 0) + length = 1; + else length = 0; + + if(!writeTag(TUnsigned32, id, length)) + return; + + length--; + for(int i = length; i >= 0; i--) + m_data.push_back((char)((value >> (i * 8)) & 0xff)); +} + +void SimpleSerializer::writeS64(quint32 id, qint64 value) +{ + int length; + + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if((value < -(1ll << 55)) || (value >= (1ll << 55))) + length = 8; + else if((value < -(1ll << 47)) || (value >= (1ll << 47))) + length = 7; + else if((value < -(1ll << 39)) || (value >= (1ll << 39))) + length = 6; + else if((value < -(1ll << 31)) || (value >= (1ll << 31))) + length = 5; + else if((value < -(1ll << 23)) || (value >= (1ll << 23))) + length = 4; + else if((value < -(1ll << 15)) || (value >= (1ll << 15))) + length = 3; + else if((value < -(1ll << 7)) || (value >= (1ll << 7))) + length = 2; + else if(value != 0) + length = 1; + else length = 0; + + if(!writeTag(TSigned64, id, length)) + return; + + length--; + for(int i = length; i >= 0; i--) + m_data.push_back((char)((value >> (i * 8)) & 0xff)); +} + +void SimpleSerializer::writeU64(quint32 id, quint64 value) +{ + int length; + + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if(value >= (1ll << 56)) + length = 8; + else if(value >= (1ll << 48)) + length = 7; + else if(value >= (1ll << 40)) + length = 6; + else if(value >= (1ll << 32)) + length = 5; + else if(value >= (1ll << 24)) + length = 4; + else if(value >= (1ll << 16)) + length = 3; + else if(value >= (1ll << 8)) + length = 2; + else if(value > 0) + length = 1; + else length = 0; + + if(!writeTag(TUnsigned64, id, length)) + return; + + length--; + for(int i = length; i >= 0; i--) + m_data.push_back((char)((value >> (i * 8)) & 0xff)); +} + +union floatasint { + quint32 u; + float f; +}; + +void SimpleSerializer::writeFloat(quint32 id, float value) +{ + union floatasint tmp; + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if(!writeTag(TFloat, id, 4)) + return; + + tmp.f = value; + m_data.push_back((char)((tmp.u >> 24) & 0xff)); + m_data.push_back((char)((tmp.u >> 16) & 0xff)); + m_data.push_back((char)((tmp.u >> 8) & 0xff)); + m_data.push_back((char)(tmp.u & 0xff)); +} + +union doubleasint { + quint64 u; + double d; +}; + +void SimpleSerializer::writeDouble(quint32 id, double value) +{ + union doubleasint tmp; + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if(!writeTag(TDouble, id, 8)) + return; + + tmp.d = value; + m_data.push_back((char)((tmp.u >> 56) & 0xff)); + m_data.push_back((char)((tmp.u >> 48) & 0xff)); + m_data.push_back((char)((tmp.u >> 40) & 0xff)); + m_data.push_back((char)((tmp.u >> 32) & 0xff)); + m_data.push_back((char)((tmp.u >> 24) & 0xff)); + m_data.push_back((char)((tmp.u >> 16) & 0xff)); + m_data.push_back((char)((tmp.u >> 8) & 0xff)); + m_data.push_back((char)(tmp.u & 0xff)); +} + +void SimpleSerializer::writeBool(quint32 id, bool value) +{ + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if(!writeTag(TBool, id, 1)) + return; + if(value) + m_data.push_back((char)0x01); + else m_data.push_back((char)0x00); +} + +void SimpleSerializer::writeString(quint32 id, const QString& value) +{ + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + QByteArray utf8 = value.toUtf8(); + if(!writeTag(TString, id, utf8.size())) + return; + m_data.append(utf8); +} + +void SimpleSerializer::writeBlob(quint32 id, const QByteArray& value) +{ + if(id == 0) { + qCritical("SimpleSerializer: ID 0 is not allowed"); + return; + } + + if(!writeTag(TBlob, id, value.size())) + return; + m_data.append(value); +} + +const QByteArray& SimpleSerializer::final() +{ + m_finalized = true; + return m_data; +} + +bool SimpleSerializer::writeTag(Type type, quint32 id, quint32 length) +{ + if(m_finalized) { + qCritical("SimpleSerializer: config has already been finalized (id %u)", id); + return false; + } + + int idLen; + int lengthLen; + + if(id < (1 << 8)) + idLen = 0; + else if(id < (1 << 16)) + idLen = 1; + else if(id < (1 << 24)) + idLen = 2; + else idLen = 3; + + if(length < (1 << 8)) + lengthLen = 0; + else if(length < (1 << 16)) + lengthLen = 1; + else if(length < (1 << 24)) + lengthLen = 2; + else lengthLen = 3; + + m_data.push_back((char)((type << 4) | (idLen << 2) | lengthLen)); + for(int i = idLen; i >= 0; i--) + m_data.push_back((char)((id >> (i * 8)) & 0xff)); + for(int i = lengthLen; i >= 0; i--) + m_data.push_back((char)((length >> (i * 8)) & 0xff)); + return true; +} + +SimpleDeserializer::SimpleDeserializer(const QByteArray& data) : + m_data(data) +{ + m_valid = parseAll(); + + // read version information + uint readOfs; + Elements::const_iterator it = m_elements.constFind(0); + if(it == m_elements.constEnd()) + goto setInvalid; + if(it->type != TVersion) + goto setInvalid; + if(it->length > 4) + goto setInvalid; + + readOfs = it->ofs; + m_version = 0; + for(uint i = 0; i < it->length; i++) + m_version = (m_version << 8) | readByte(&readOfs); + return; + +setInvalid: + m_valid = false; +} + +bool SimpleDeserializer::readS32(quint32 id, qint32* result, qint32 def) const +{ + uint readOfs; + qint32 tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TSigned32) + goto returnDefault; + if(it->length > 4) + goto returnDefault; + + readOfs = it->ofs; + tmp = 0; + for(uint i = 0; i < it->length; i++) { + quint8 byte = readByte(&readOfs); + if((i == 0) && (byte & 0x80)) + tmp = -1; + tmp = (tmp << 8) | byte; + } + *result = tmp; + return true; + +returnDefault: + *result = def; + return false; +} + +bool SimpleDeserializer::readU32(quint32 id, quint32* result, quint32 def) const +{ + uint readOfs; + quint32 tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TUnsigned32) + goto returnDefault; + if(it->length > 4) + goto returnDefault; + + readOfs = it->ofs; + tmp = 0; + for(uint i = 0; i < it->length; i++) + tmp = (tmp << 8) | readByte(&readOfs); + *result = tmp; + return true; + +returnDefault: + *result = def; + return false; +} + +bool SimpleDeserializer::readS64(quint32 id, qint64* result, qint64 def) const +{ + uint readOfs; + qint64 tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TSigned64) + goto returnDefault; + if(it->length > 8) + goto returnDefault; + + readOfs = it->ofs; + tmp = 0; + for(uint i = 0; i < it->length; i++) { + quint8 byte = readByte(&readOfs); + if((i == 0) && (byte & 0x80)) + tmp = -1; + tmp = (tmp << 8) | byte; + } + *result = tmp; + return true; + +returnDefault: + *result = def; + return false; +} + +bool SimpleDeserializer::readU64(quint32 id, quint64* result, quint64 def) const +{ + uint readOfs; + quint64 tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TUnsigned64) + goto returnDefault; + if(it->length > 8) + goto returnDefault; + + readOfs = it->ofs; + tmp = 0; + for(uint i = 0; i < it->length; i++) + tmp = (tmp << 8) | readByte(&readOfs); + *result = tmp; + return true; + +returnDefault: + *result = def; + return false; +} + +bool SimpleDeserializer::readFloat(quint32 id, float* result, float def) const +{ + uint readOfs; + union floatasint tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TFloat) + goto returnDefault; + if(it->length != 4) + goto returnDefault; + + readOfs = it->ofs; + tmp.u = 0; + for(int i = 0; i < 4; i++) + tmp.u = (tmp.u << 8) | readByte(&readOfs); + *result = tmp.f; + return true; + +returnDefault: + *result = def; + return false; +} + +bool SimpleDeserializer::readDouble(quint32 id, double* result, double def) const +{ + uint readOfs; + union doubleasint tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TDouble) + goto returnDefault; + if(it->length != 8) + goto returnDefault; + + readOfs = it->ofs; + tmp.u = 0; + for(int i = 0; i < 8; i++) + tmp.u = (tmp.u << 8) | readByte(&readOfs); + *result = tmp.d; + return true; + +returnDefault: + *result = def; + return false; +} + +union real4asint { + quint32 u; + Real r; +}; + +union real8asint { + quint64 u; + Real r; +}; + +bool SimpleDeserializer::readReal(quint32 id, Real* result, Real def) const +{ + if(sizeof(Real) == 4) { + uint readOfs; + union real4asint tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault32; + if(it->type != TFloat) + goto returnDefault32; + if(it->length != 4) + goto returnDefault32; + + readOfs = it->ofs; + tmp.u = 0; + for(int i = 0; i < 4; i++) + tmp.u = (tmp.u << 8) | readByte(&readOfs); + *result = tmp.r; + return true; + + returnDefault32: + *result = def; + return false; + } else { + uint readOfs; + union real8asint tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault64; + if(it->type != TDouble) + goto returnDefault64; + if(it->length != 8) + goto returnDefault64; + + readOfs = it->ofs; + tmp.u = 0; + for(int i = 0; i < 8; i++) + tmp.u = (tmp.u << 8) | readByte(&readOfs); + *result = tmp.r; + return true; + + returnDefault64: + *result = def; + return false; + } +} + +bool SimpleDeserializer::readBool(quint32 id, bool* result, bool def) const +{ + uint readOfs; + quint8 tmp; + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TBool) + goto returnDefault; + if(it->length != 1) + goto returnDefault; + + readOfs = it->ofs; + tmp = readByte(&readOfs); + if(tmp == 0x00) + *result = false; + else *result = true; + return true; + +returnDefault: + *result = def; + return false; +} + +bool SimpleDeserializer::readString(quint32 id, QString* result, const QString& def) const +{ + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TString) + goto returnDefault; + + *result = QString::fromUtf8(m_data.data() + it->ofs, it->length); + return true; + +returnDefault: + *result = def; + return false; +} + +bool SimpleDeserializer::readBlob(quint32 id, QByteArray* result, const QByteArray& def) const +{ + Elements::const_iterator it = m_elements.constFind(id); + if(it == m_elements.constEnd()) + goto returnDefault; + if(it->type != TBlob) + goto returnDefault; + + *result = QByteArray(m_data.data() + it->ofs, it->length); + return true; + +returnDefault: + *result = def; + return false; +} + +void SimpleDeserializer::dump() const +{ + if(!m_valid) { + qDebug("SimpleDeserializer dump: data invalid"); + return; + } else { + qDebug("SimpleDeserializer dump: version %u", m_version); + } + + for(Elements::const_iterator it = m_elements.constBegin(); it != m_elements.constEnd(); ++it) { + switch(it->type) { + case TSigned32: { + qint32 tmp; + readS32(it.key(), &tmp); + qDebug("id %d, S32, len %d: " PRINTF_FORMAT_S32, it.key(), it->length, tmp); + break; + } + case TUnsigned32: { + quint32 tmp; + readU32(it.key(), &tmp); + qDebug("id %d, U32, len %d: " PRINTF_FORMAT_U32, it.key(), it->length, tmp); + break; + } + case TSigned64: { + qint64 tmp; + readS64(it.key(), &tmp); +// qDebug("id %d, S64, len %d: " PRINTF_FORMAT_S64, it.key(), it->length, (int)tmp); + break; + } + case TUnsigned64: { + quint64 tmp; + readU64(it.key(), &tmp); +// qDebug("id %d, U64, len %d: " PRINTF_FORMAT_U64, it.key(), it->length, (uint)tmp); + break; + } + case TFloat: { + float tmp; + readFloat(it.key(), &tmp); + qDebug("id %d, FLOAT, len %d: %f", it.key(), it->length, tmp); + break; + } + case TDouble: { + double tmp; + readDouble(it.key(), &tmp); + qDebug("id %d, DOUBLE, len %d: %f", it.key(), it->length, tmp); + break; + } + case TBool: { + bool tmp; + readBool(it.key(), &tmp); + qDebug("id %d, BOOL, len %d: %s", it.key(), it->length, tmp ? "true" : "false"); + break; + } + case TString: { + QString tmp; + readString(it.key(), &tmp); + qDebug("id %d, STRING, len %d: \"%s\"", it.key(), it->length, qPrintable(tmp)); + break; + } + case TBlob: { + QByteArray tmp; + readBlob(it.key(), &tmp); + qDebug("id %d, BLOB, len %d", it.key(), it->length); + break; + } + case TVersion: { + qDebug("id %d, VERSION, len %d", it.key(), it->length); + break; + } + default: { + qDebug("id %d, UNKNOWN TYPE 0x%02x, len %d", it.key(), it->type, it->length); + break; + } + } + } +} + +bool SimpleDeserializer::parseAll() +{ + uint readOfs = 0; + Type type; + quint32 id; + quint32 length; + + /* + QString hex; + for(int i = 0; i < m_data.size(); i++) + hex.append(QString("%1 ").arg((quint8)m_data[i], 2, 16, QChar('0'))); + qDebug("%s", qPrintable(hex)); + qDebug("=="); + */ + + while(readOfs < (uint)m_data.size()) { + if(!readTag(&readOfs, m_data.size(), &type, &id, &length)) + return false; + + //qDebug("-- id %d, TYPE 0x%02x, len %d", id, type, length); + + if(m_elements.contains(id)) { + qDebug("SimpleDeserializer: same ID found twice (id %u)", id); + return false; + } + + m_elements.insert(id, Element(type, readOfs, length)); + + readOfs += length; + + if(readOfs == (uint)m_data.size()) + return true; + } + return false; +} + +bool SimpleDeserializer::readTag(uint* readOfs, uint readEnd, Type* type, quint32* id, quint32* length) const +{ + quint8 tag = readByte(readOfs); + + *type = (Type)(tag >> 4); + int idLen = ((tag >> 2) & 0x03) + 1; + int lengthLen = (tag & 0x03) + 1; + + // make sure we have enough header bytes left + if(((*readOfs) + idLen + lengthLen) > readEnd) + return false; + + quint32 tmp = 0; + for(int i = 0; i < idLen; i++) + tmp = (tmp << 8) | readByte(readOfs); + *id = tmp; + tmp = 0; + for(int i = 0; i < lengthLen; i++) + tmp = (tmp << 8) | readByte(readOfs); + *length = tmp; + + // check if payload fits the buffer + if(((*readOfs) + (*length)) > readEnd) + return false; + else return true; +} + diff --git a/android/app/src/main/cpp/util/simpleserializer.h b/android/app/src/main/cpp/util/simpleserializer.h new file mode 100644 index 0000000..de152c3 --- /dev/null +++ b/android/app/src/main/cpp/util/simpleserializer.h @@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_SIMPLESERIALIZER_H +#define INCLUDE_SIMPLESERIALIZER_H + +#include +#include +#include +#include +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API SimpleSerializer { +public: + SimpleSerializer(quint32 version); + + void writeS32(quint32 id, qint32 value); + void writeU32(quint32 id, quint32 value); + void writeS64(quint32 id, qint64 value); + void writeU64(quint32 id, quint64 value); + void writeFloat(quint32 id, float value); + void writeDouble(quint32 id, double value); + void writeReal(quint32 id, Real value) + { + if(sizeof(Real) == 4) + writeFloat(id, value); + else writeDouble(id, value); + } + void writeBool(quint32 id, bool value); + void writeString(quint32 id, const QString& value); + void writeBlob(quint32 id, const QByteArray& value); + + template + void writeList(quint32 id, const QList& value) + { + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << value; + delete stream; + writeBlob(id, data); + } + template + void writeHash(quint32 id, const QHash& value) + { + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << value; + delete stream; + writeBlob(id, data); + } + + const QByteArray& final(); + +protected: + // Lists and hashes are written as TBlob + enum Type { + TSigned32 = 0, + TUnsigned32 = 1, + TSigned64 = 2, + TUnsigned64 = 3, + TFloat = 4, + TDouble = 5, + TBool = 6, + TString = 7, + TBlob = 8, + TVersion = 9 + }; + + QByteArray m_data; + bool m_finalized; + + bool writeTag(Type type, quint32 id, quint32 length); +}; + +class SDRBASE_API SimpleDeserializer { +public: + SimpleDeserializer(const QByteArray& data); + + bool readS32(quint32 id, qint32* result, qint32 def = 0) const; + bool readU32(quint32 id, quint32* result, quint32 def = 0) const; + bool readS64(quint32 id, qint64* result, qint64 def = 0) const; + bool readU64(quint32 id, quint64* result, quint64 def = 0) const; + bool readFloat(quint32 id, float* result, float def = 0) const; + bool readDouble(quint32 id, double* result, double def = 0) const; + bool readReal(quint32 id, Real* result, Real def = 0) const; + bool readBool(quint32 id, bool* result, bool def = false) const; + bool readString(quint32 id, QString* result, const QString& def = QString()) const; + bool readBlob(quint32 id, QByteArray* result, const QByteArray& def = QByteArray()) const; + + template + bool readList(quint32 id, QList* result, const QList& def = {}) + { + QByteArray data; + bool ok = readBlob(id, &data); + if (ok) + { + QDataStream *stream = new QDataStream(data); + (*stream) >> *result; + delete stream; + } + else + { + *result = def; + } + return ok; + } + template + bool readHash(quint32 id, QHash* result, const QHash& def = {}) + { + QByteArray data; + bool ok = readBlob(id, &data); + if (ok) + { + QDataStream *stream = new QDataStream(data); + (*stream) >> *result; + delete stream; + } + else + { + *result = def; + } + return ok; + } + + bool isValid() const { return m_valid; } + quint32 getVersion() const { return m_version; } + void dump() const; + +private: + enum Type { + TSigned32 = 0, + TUnsigned32 = 1, + TSigned64 = 2, + TUnsigned64 = 3, + TFloat = 4, + TDouble = 5, + TBool = 6, + TString = 7, + TBlob = 8, + TVersion = 9 + }; + + struct Element { + Type type; + quint32 ofs; + quint32 length; + + Element(Type _type, quint32 _ofs, quint32 _length) : + type(_type), + ofs(_ofs), + length(_length) + { } + }; + typedef QMap Elements; + + QByteArray m_data; + bool m_valid; + Elements m_elements; + quint32 m_version; + + bool parseAll(); + bool readTag(uint* readOfs, uint readEnd, Type* type, quint32* id, quint32* length) const; + quint8 readByte(uint* readOfs) const + { + quint8 res = m_data[*readOfs]; + (*readOfs)++; + return res; + } +}; + +#endif // INCLUDE_SIMPLESERIALIZER_H diff --git a/android/app/src/main/cpp/util/socket.cpp b/android/app/src/main/cpp/util/socket.cpp new file mode 100644 index 0000000..300a04e --- /dev/null +++ b/android/app/src/main/cpp/util/socket.cpp @@ -0,0 +1,193 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "socket.h" + +Socket::Socket(QObject *socket, QObject *parent) : + QObject(parent), + m_socket(socket) +{ +} + +Socket::~Socket() +{ + delete m_socket; +} + +TCPSocket::TCPSocket(QTcpSocket *socket) : + Socket(socket) +{ +} + +qint64 TCPSocket::write(const char *data, qint64 length) +{ + QTcpSocket *socket = qobject_cast(m_socket); + + return socket->write(data, length); +} + +qint64 TCPSocket::read(char *data, qint64 length) +{ + QTcpSocket *socket = qobject_cast(m_socket); + + return socket->read(data, length); +} + +QByteArray TCPSocket::readAll() +{ + QTcpSocket *socket = qobject_cast(m_socket); + + return socket->readAll(); +} + +void TCPSocket::close() +{ + QTcpSocket *socket = qobject_cast(m_socket); + + socket->close(); +} + +qint64 TCPSocket::bytesAvailable() +{ + QTcpSocket *socket = qobject_cast(m_socket); + + return socket->bytesAvailable(); +} + +void TCPSocket::flush() +{ + QTcpSocket *socket = qobject_cast(m_socket); + + socket->flush(); +} + +QHostAddress TCPSocket::peerAddress() +{ + QTcpSocket *socket = qobject_cast(m_socket); + + return socket->peerAddress(); +} + +quint16 TCPSocket::peerPort() +{ + QTcpSocket *socket = qobject_cast(m_socket); + + return socket->peerPort(); +} + +bool TCPSocket::isConnected() +{ + QTcpSocket *socket = qobject_cast(m_socket); + + return socket->state() == QAbstractSocket::ConnectedState; +} + +WebSocket::WebSocket(QWebSocket *socket) : + Socket(socket) +{ + m_rxBuffer.reserve(64000); + m_txBuffer.reserve(64000); + connect(socket, &QWebSocket::binaryFrameReceived, this, &WebSocket::binaryFrameReceived); +} + + qint64 WebSocket::write(const char *data, qint64 length) +{ + //QWebSocket *socket = qobject_cast(m_socket); + //return socket->sendBinaryMessage(QByteArray(data, length)); + + m_txBuffer.append(data, length); + return length; +} + +void WebSocket::flush() +{ + QWebSocket *socket = qobject_cast(m_socket); + if (m_txBuffer.size() > 0) + { + qint64 len = socket->sendBinaryMessage(m_txBuffer); + if (len != m_txBuffer.size()) { + qDebug() << "WebSocket::flush: Failed to send all of message" << len << "/" << m_txBuffer.size(); + } + m_txBuffer.clear(); + } + socket->flush(); +} + +qint64 WebSocket::read(char *data, qint64 length) +{ + length = std::min(length, (qint64)m_rxBuffer.size()); + + memcpy(data, m_rxBuffer.constData(), length); + + m_rxBuffer = m_rxBuffer.mid(length); // Yep, not very efficient + + return length; +} + +QByteArray WebSocket::readAll() +{ + QByteArray b = m_rxBuffer; + + m_rxBuffer.clear(); + + return b; +} + +void WebSocket::close() +{ + QWebSocket *socket = qobject_cast(m_socket); + + // Will crash if we call close on unopened socket + if (socket->state() != QAbstractSocket::UnconnectedState) { + socket->close(); + } +} + +qint64 WebSocket::bytesAvailable() +{ + return m_rxBuffer.size(); +} + +void WebSocket::binaryFrameReceived(const QByteArray &frame, bool isLastFrame) +{ + (void) isLastFrame; + + m_rxBuffer.append(frame); +} + +QHostAddress WebSocket::peerAddress() +{ + QWebSocket *socket = qobject_cast(m_socket); + + return socket->peerAddress(); +} + +quint16 WebSocket::peerPort() +{ + QWebSocket *socket = qobject_cast(m_socket); + + return socket->peerPort(); +} + +bool WebSocket::isConnected() +{ + QWebSocket *socket = qobject_cast(m_socket); + + return socket->state() == QAbstractSocket::ConnectedState; +} diff --git a/android/app/src/main/cpp/util/socket.h b/android/app/src/main/cpp/util/socket.h new file mode 100644 index 0000000..28adb86 --- /dev/null +++ b/android/app/src/main/cpp/util/socket.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SOCKET_H_ +#define INCLUDE_SOCKET_H_ + +#include +#include + +#include "export.h" + +// Class to allow easy use of either QTCPSocket or QWebSocket +class SDRBASE_API Socket : public QObject { + Q_OBJECT +protected: + Socket(QObject *socket, QObject *parent=nullptr); + +public: + virtual ~Socket(); + virtual qint64 write(const char *data, qint64 length) = 0; + virtual void flush() = 0; + virtual qint64 read(char *data, qint64 length) = 0; + virtual qint64 bytesAvailable() = 0; + virtual QByteArray readAll() = 0; + virtual void close() = 0; + virtual QHostAddress peerAddress() = 0; + virtual quint16 peerPort() = 0; + virtual bool isConnected() = 0; + + QObject *socket() { return m_socket; } + +protected: + + QObject *m_socket; + +}; + +class SDRBASE_API TCPSocket : public Socket { + Q_OBJECT + +public: + + explicit TCPSocket(QTcpSocket *socket) ; + qint64 write(const char *data, qint64 length) override; + void flush() override; + qint64 read(char *data, qint64 length) override; + qint64 bytesAvailable() override; + QByteArray readAll() override; + void close() override; + QHostAddress peerAddress() override; + quint16 peerPort() override; + bool isConnected() override; + +}; + +class SDRBASE_API WebSocket : public Socket { + Q_OBJECT + +public: + + explicit WebSocket(QWebSocket *socket); + qint64 write(const char *data, qint64 length) override; + void flush() override; + qint64 read(char *data, qint64 length) override; + qint64 bytesAvailable() override; + QByteArray readAll() override; + void close() override; + QHostAddress peerAddress() override; + quint16 peerPort() override; + bool isConnected() override; + +private slots: + + void binaryFrameReceived(const QByteArray &frame, bool isLastFrame); + +private: + + QByteArray m_rxBuffer; + QByteArray m_txBuffer; + +}; + +#endif // INCLUDE_SOCKET_H_ diff --git a/android/app/src/main/cpp/util/solardynamicsobservatory.cpp b/android/app/src/main/cpp/util/solardynamicsobservatory.cpp new file mode 100644 index 0000000..22d80ac --- /dev/null +++ b/android/app/src/main/cpp/util/solardynamicsobservatory.cpp @@ -0,0 +1,433 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "solardynamicsobservatory.h" + +#include +#include +#include +#include + +SolarDynamicsObservatory::SolarDynamicsObservatory() : + m_size(512), + m_todayCache(nullptr) +{ + connect(&m_dataTimer, &QTimer::timeout, this, qOverload<>(&SolarDynamicsObservatory::getImage)); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &SolarDynamicsObservatory::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("solardynamicsobservatory"))) { + qDebug() << "SolarDynamicsObservatory::SolarDynamicsObservatory: Failed to create cache/solardynamicsobservatory"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("solardynamicsobservatory")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); +} + +SolarDynamicsObservatory::~SolarDynamicsObservatory() +{ + disconnect(&m_dataTimer, &QTimer::timeout, this, qOverload<>(&SolarDynamicsObservatory::getImage)); + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &SolarDynamicsObservatory::handleReply); + delete m_networkManager; + delete m_todayCache; +} + +SolarDynamicsObservatory* SolarDynamicsObservatory::create() +{ + return new SolarDynamicsObservatory(); +} + +QList SolarDynamicsObservatory::getImageSizes() +{ + return {512, 1024, 2048, 4096}; +} + +QList SolarDynamicsObservatory::getVideoSizes() +{ + return {512, 1024}; +} + +const QStringList SolarDynamicsObservatory::getImageNames() +{ + QChar angstronm(0x212B); + QStringList names; + + // SDO + names.append(QString("AIA 094 %1").arg(angstronm)); + names.append(QString("AIA 131 %1").arg(angstronm)); + names.append(QString("AIA 171 %1").arg(angstronm)); + names.append(QString("AIA 193 %1").arg(angstronm)); + names.append(QString("AIA 211 %1").arg(angstronm)); + names.append(QString("AIA 304 %1").arg(angstronm)); + names.append(QString("AIA 335 %1").arg(angstronm)); + names.append(QString("AIA 1600 %1").arg(angstronm)); + names.append(QString("AIA 1700 %1").arg(angstronm)); + names.append(QString("AIA 211 %1, 193 %1, 171 %1").arg(angstronm)); + names.append(QString("AIA 304 %1, 211 %1, 171 %1").arg(angstronm)); + names.append(QString("AIA 094 %1, 335 %1, 193 %1").arg(angstronm)); + names.append(QString("AIA 171 %1, HMIB").arg(angstronm)); + names.append("HMI Magneotgram"); + names.append("HMI Colorized Magneotgram"); + names.append("HMI Intensitygram - Colored"); + names.append("HMI Intensitygram - Flattened"); + names.append("HMI Intensitygram"); + names.append("HMI Dopplergram"); + + // SOHO + names.append("LASCO C2"); + names.append("LASCO C3"); + + return names; +} + +const QStringList SolarDynamicsObservatory::getChannelNames() +{ + QStringList channelNames = { + "0094", + "0131", + "0171", + "0193", + "0211", + "0304", + "0335", + "1600", + "1700", + "211193171", + "304211171", + "094335193", + "HMImag", + "HMIB", + "HMIBC", + "HMIIC", + "HMIIF", + "HMII", + "HMID", + "c2", + "c3" + }; + + return channelNames; +} + +const QStringList SolarDynamicsObservatory::getImageFileNames() +{ + // Ordering needs to match getImageNames() + // %1 replaced with size + QStringList filenames = { + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0094.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0131.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0171.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0193.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0211.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0304.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0335.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_1600.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_1700.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_211193171.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/f_304_211_171_%1.jpg", + //"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_304211171.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/f_094_335_193_%1.jpg", + //"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_094335193.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/f_HMImag_171_%1.jpg", + //"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMImag.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIB.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIBC.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIIC.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIIF.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMII.jpg", + "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMID.jpg", + "https://soho.nascom.nasa.gov/data/realtime/c2/512/latest.jpg", + "https://soho.nascom.nasa.gov/data/realtime/c3/512/latest.jpg" + }; + + return filenames; +} + +const QStringList SolarDynamicsObservatory::getVideoNames() +{ + QChar angstronm(0x212B); + QStringList names; + + // SDO + names.append(QString("AIA 094 %1").arg(angstronm)); + names.append(QString("AIA 131 %1").arg(angstronm)); + names.append(QString("AIA 171 %1").arg(angstronm)); + names.append(QString("AIA 193 %1").arg(angstronm)); + names.append(QString("AIA 211 %1").arg(angstronm)); + names.append(QString("AIA 304 %1").arg(angstronm)); + names.append(QString("AIA 335 %1").arg(angstronm)); + names.append(QString("AIA 1600 %1").arg(angstronm)); + names.append(QString("AIA 1700 %1").arg(angstronm)); + + // SOHO + names.append("LASCO C2"); + names.append("LASCO C3"); + + return names; +} + +const QStringList SolarDynamicsObservatory::getVideoFileNames() +{ + const QStringList filenames = { + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0094.mp4", // Videos sometimes fail to load on Windows if https used + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0131.mp4", + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0171.mp4", + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0193.mp4", + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0211.mp4", + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0304.mp4", + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0335.mp4", + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_1600.mp4", + "http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_1700.mp4", + "http://soho.nascom.nasa.gov/data/LATEST/current_c2.mp4", + "http://soho.nascom.nasa.gov/data/LATEST/current_c3.mp4", + }; + + return filenames; +} + +QString SolarDynamicsObservatory::getImageURL(const QString& image, int size) +{ + const QStringList names = SolarDynamicsObservatory::getImageNames(); + const QStringList filenames = SolarDynamicsObservatory::getImageFileNames(); + int idx = names.indexOf(image); + + if (idx != -1) { + return QString(filenames[idx]).arg(size); + } else { + return ""; + } +} + +QString SolarDynamicsObservatory::getVideoURL(const QString& video, int size) +{ + const QStringList names = SolarDynamicsObservatory::getVideoNames(); + const QStringList filenames = SolarDynamicsObservatory::getVideoFileNames(); + int idx = names.indexOf(video); + + if (idx != -1) { + return QString(filenames[idx]).arg(size); + } else { + return ""; + } +} + +void SolarDynamicsObservatory::getImagePeriodically(const QString& image, int size, int periodInMins) +{ + m_image = image; + m_size = size; + if (periodInMins > 0) + { + m_dataTimer.setInterval(periodInMins*60*1000); + m_dataTimer.start(); + getImage(); + } + else + { + m_dataTimer.stop(); + } +} + +void SolarDynamicsObservatory::getImage() +{ + getImage(m_image, m_size); +} + +void SolarDynamicsObservatory::getImage(const QString& imageName, int size) +{ + QString urlString = getImageURL(imageName, size); + if (!urlString.isEmpty()) + { + QUrl url(urlString); + + m_networkManager->get(QNetworkRequest(url)); + } +} + +void SolarDynamicsObservatory::getImage(const QString& imageName, QDateTime dateTime, int size) +{ + // Stop periodic updates, if not after latest data + m_dataTimer.stop(); + + // Save details of image we are after + Request request; + request.m_dateTime = dateTime; + request.m_size = size; + request.m_image = imageName; + + // Get file index, as we don't know what time will be used in the file + QDate date = dateTime.date(); + if (m_indexCache.contains(date)) + { + handleIndex(m_indexCache.take(date), request); + } + else if ((m_todayCache != nullptr) && (date == m_todayCacheDateTime.date()) && (dateTime < m_todayCacheDateTime.addSecs(-60 * 60))) + { + handleIndex(m_todayCache, request); + } + else + { + QString urlString = QString("https://sdo.gsfc.nasa.gov/assets/img/browse/%1/%2/%3/") + .arg(date.year()) + .arg(date.month(), 2, 10, QLatin1Char('0')) + .arg(date.day(), 2, 10, QLatin1Char('0')); + QUrl url(urlString); + + request.m_url = urlString; + m_requests.append(request); + + m_networkManager->get(QNetworkRequest(url)); + } +} + +void SolarDynamicsObservatory::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + if (reply->url().fileName().endsWith(".jpg")) + { + handleJpeg(reply->readAll()); + } + else + { + // Find corresponding request + QString urlString = reply->url().toString(); + + for (int i = 0; i < m_requests.size(); i++) + { + if (m_requests[i].m_url == urlString) + { + QByteArray *bytes = new QByteArray(reply->readAll()); + + handleIndex(bytes, m_requests[i]); + m_requests.removeAt(i); + break; + } + } + } + } + else + { + qDebug() << "SolarDynamicsObservatory::handleReply: Error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "SolarDynamicsObservatory::handleReply: Reply is null"; + } +} + +void SolarDynamicsObservatory::handleJpeg(const QByteArray& bytes) +{ + QImage image; + + if (image.loadFromData(bytes)) { + emit imageUpdated(image); + } else { + qWarning() << "SolarDynamicsObservatory::handleJpeg: Failed to load image"; + } +} + +void SolarDynamicsObservatory::handleIndex(QByteArray* bytes, const Request& request) +{ + const QStringList names = SolarDynamicsObservatory::getImageNames(); + const QStringList channelNames = SolarDynamicsObservatory::getChannelNames(); + int idx = names.indexOf(request.m_image); + if (idx < 0) { + return; + } + QString channel = channelNames[idx]; + + QString file(*bytes); + QStringList lines = file.split("\n"); + + QString date = request.m_dateTime.date().toString("yyyyMMdd"); + QString pattern = QString("\"%1_([0-9]{6})_%2_%3.jpg\"").arg(date).arg(request.m_size).arg(channel); + QRegularExpression re(pattern); + + // Get all times the image is available + QList times; + for (const auto& line : lines) + { + QRegularExpressionMatch match = re.match(line); + if (match.hasMatch()) + { + QString t = match.capturedTexts()[1]; + int h = t.left(2).toInt(); + int m = t.mid(2, 2).toInt(); + int s = t.right(2).toInt(); + times.append(QTime(h, m, s)); + } + } + + if (times.length() > 0) + { + QTime target = request.m_dateTime.time(); + QTime current = times[0]; + for (int i = 1; i < times.size(); i++) + { + if (target < times[i]) { + break; + } + current = times[i]; + } + + // Get image + QDate date = request.m_dateTime.date(); + QString urlString = QString("https://sdo.gsfc.nasa.gov/assets/img/browse/%1/%2/%3/%1%2%3_%4%5%6_%7_%8.jpg") + .arg(date.year()) + .arg(date.month(), 2, 10, QLatin1Char('0')) + .arg(date.day(), 2, 10, QLatin1Char('0')) + .arg(current.hour(), 2, 10, QLatin1Char('0')) + .arg(current.minute(), 2, 10, QLatin1Char('0')) + .arg(current.second(), 2, 10, QLatin1Char('0')) + .arg(request.m_size) + .arg(channel); + + QUrl url(urlString); + + m_networkManager->get(QNetworkRequest(url)); + } + else + { + qDebug() << "SolarDynamicsObservatory: No image available for " << request.m_dateTime; + } + + // Save index in cache + if (request.m_dateTime.date() == QDate::currentDate()) + { + if (m_todayCache != bytes) + { + m_todayCache = bytes; + m_todayCacheDateTime = QDateTime::currentDateTime(); + } + } + else if (request.m_dateTime.date() < QDate::currentDate()) + { + m_indexCache.insert(request.m_dateTime.date(), bytes); + } + else + { + delete bytes; + } +} diff --git a/android/app/src/main/cpp/util/solardynamicsobservatory.h b/android/app/src/main/cpp/util/solardynamicsobservatory.h new file mode 100644 index 0000000..90832ff --- /dev/null +++ b/android/app/src/main/cpp/util/solardynamicsobservatory.h @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SOLARDYNAMICSOBSERVATORY_H +#define INCLUDE_SOLARDYNAMICSOBSERVATORY_H + +#include +#include +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// This gets solar imagery from SDO (Solar Dynamics Observatory) - https://sdo.gsfc.nasa.gov/ +// and LASCO images from SOHO - https://soho.nascom.nasa.gov/ +class SDRBASE_API SolarDynamicsObservatory : public QObject +{ + Q_OBJECT +protected: + SolarDynamicsObservatory(); + +public: + + static SolarDynamicsObservatory* create(); + + ~SolarDynamicsObservatory(); + void getImagePeriodically(const QString& image, int size=512, int periodInMins=15); + void getImage(const QString& m_image, int size); + void getImage(const QString& m_image, QDateTime dateTime, int size=512); + + static QString getImageURL(const QString& image, int size); + static QString getVideoURL(const QString& video, int size=512); + + static QList getImageSizes(); + static const QStringList getChannelNames(); + static const QStringList getImageNames(); + static QList getVideoSizes(); + static const QStringList getVideoNames(); + +private slots: + void getImage(); + void handleReply(QNetworkReply* reply); + +signals: + void imageUpdated(const QImage& image); // Called when new image is available. + +private: + + struct Request { + QString m_url; + QDateTime m_dateTime; + int m_size; + QString m_image; + }; + + QTimer m_dataTimer; // Timer for periodic updates + QString m_image; // Saved parameters for periodic updates + int m_size; + + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + + // Index page isn't cacheable (using network cache), so we cache it ourselves, as it can take up to 5 seconds to fetch + QCache m_indexCache; + QDateTime m_todayCacheDateTime; + QByteArray *m_todayCache; + + QList m_requests; + + void handleJpeg(const QByteArray& bytes); + void handleIndex(QByteArray *bytes, const Request& request); + static const QStringList getImageFileNames(); + static const QStringList getVideoFileNames(); + +}; + +#endif /* INCLUDE_SOLARDYNAMICSOBSERVATORY_H */ diff --git a/android/app/src/main/cpp/util/sondehub.cpp b/android/app/src/main/cpp/util/sondehub.cpp new file mode 100644 index 0000000..d15e665 --- /dev/null +++ b/android/app/src/main/cpp/util/sondehub.cpp @@ -0,0 +1,298 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "sondehub.h" +#include "util/radiosonde.h" + +#include +#include +#include +#include +#include +#include + +SondeHub::SondeHub() +{ + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &SondeHub::handleReply); +} + +SondeHub::~SondeHub() +{ + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &SondeHub::handleReply); + delete m_networkManager; +} + +SondeHub* SondeHub::create() +{ + return new SondeHub(); +} + +void SondeHub::upload( + const QString uploaderCallsign, + QDateTime timeReceived, + RS41Frame *frame, + const RS41Subframe *subframe, + float uploaderLat, + float uploaderLon, + float uploaderAlt + ) +{ + // Check we have required data + if (!frame->m_statusValid || !frame->m_posValid) { + return; + } + + QJsonArray uploaderPos { + QString::number(uploaderLat, 'f', 5).toDouble(), + QString::number(uploaderLon, 'f', 5).toDouble(), + QString::number(uploaderAlt, 'f', 1).toDouble() + }; + + QJsonObject obj { + {"software_name", "SDRangel"}, + {"software_version", qApp->applicationVersion()}, + {"uploader_callsign", uploaderCallsign}, + {"time_received", timeReceived.toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz000Z")}, + {"manufacturer", "Vaisala"}, + {"type", "RS41"}, + {"uploader_position", uploaderPos} + }; + + if (frame->m_statusValid) + { + obj.insert("frame", frame->m_frameNumber); + obj.insert("serial", frame->m_serial); + obj.insert("batt", QString::number(frame->m_batteryVoltage, 'f', 2).toDouble()); + } + + if (frame->m_measValid) + { + // Don't upload uncalibrated measurements, as there can be a significant error + if (frame->isTemperatureCalibrated()) { + obj.insert("temp", QString::number(frame->getTemperatureFloat(subframe), 'f', 1).toDouble()); + } + if (frame->isHumidityCalibrated()) + { + float humidity = frame->getHumidityFloat(subframe); + if (humidity != 0.0f) { + obj.insert("humidity", QString::number(humidity, 'f', 1).toDouble()); + } + } + if (frame->isPressureCalibrated()) + { + float pressure = frame->getPressureFloat(subframe); + if (pressure != 0.0f) { + obj.insert("pressure", QString::number(pressure, 'f', 2).toDouble()); + } + } + } + + if (frame->m_gpsInfoValid) + { + obj.insert("datetime", frame->m_gpsDateTime.toUTC().addSecs(18).toString("yyyy-MM-ddTHH:mm:ss.zzz000Z")); // +18 adjusts UTC to GPS time + } + + if (frame->m_posValid) + { + obj.insert("lat", QString::number(frame->m_latitude, 'f', 5).toDouble()); + obj.insert("lon", QString::number(frame->m_longitude, 'f', 5).toDouble()); + obj.insert("alt", QString::number(frame->m_height, 'f', 1).toDouble()); + obj.insert("vel_h", QString::number(frame->m_speed, 'f', 2).toDouble()); + obj.insert("vel_v", QString::number(frame->m_verticalRate, 'f', 2).toDouble()); + obj.insert("heading", QString::number(frame->m_heading, 'f', 1).toDouble()); + obj.insert("sats", frame->m_satellitesUsed); + } + + if (!subframe->getFrequencyMHz().isEmpty()) { + obj.insert("frequency", std::round(subframe->getFrequencyMHz().toFloat() * 100.0) / 100.0); + } + + if (subframe->getType() != "RS41") { + obj.insert("subtype", subframe->getType()); + } + + //obj.insert("dev", true); + //qDebug() << obj; + QJsonArray payloads { + obj + }; + + QJsonDocument doc(payloads); + QByteArray data = doc.toJson(); + + QUrl url(QString("https://api.v2.sondehub.org/sondes/telemetry")); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::UserAgentHeader, "sdrangel"); + request.setRawHeader("Date", QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs).toLatin1()); + + m_networkManager->put(request, data); +} + +void SondeHub::updatePosition( + const QString& callsign, + float latitude, + float longitude, + float altitude, + const QString& radio, + const QString& antenna, + const QString& email, + bool mobile + ) +{ + QJsonArray position { + latitude, longitude, altitude + }; + + QJsonObject obj { + {"software_name", "SDRangel"}, + {"software_version", qApp->applicationVersion()}, + {"uploader_callsign", callsign}, + {"uploader_position", position}, + {"uploader_radio", radio}, + {"uploader_antenna", antenna}, + {"uploader_contact_email", email}, + {"mobile", mobile} + }; + + QJsonDocument doc(obj); + QByteArray data = doc.toJson(); + + QUrl url(QString("https://api.v2.sondehub.org/listeners")); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::UserAgentHeader, "sdrangel"); + + m_networkManager->put(request, data); +} + +void SondeHub::getPrediction(const QString& serial) +{ + QUrl url(QString("https://api.v2.sondehub.org/predictions?vehicles=%1").arg(serial)); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::UserAgentHeader, "sdrangel"); + + m_networkManager->get(request); +} + +void SondeHub::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QByteArray bytes = reply->readAll(); + QJsonDocument document = QJsonDocument::fromJson(bytes); + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("message"))) + { + QString message = obj.value(QStringLiteral("message")).toString(); + qWarning() << "SondeHub message:" << message; + } + if (obj.contains(QStringLiteral("errors"))) + { + QJsonArray errors = obj.value(QStringLiteral("errors")).toArray(); + for (auto errorObjRef : errors) + { + QJsonObject errorObj = errorObjRef.toObject(); + if (errorObj.contains(QStringLiteral("error_message"))) + { + QString errorMessage = errorObj.value(QStringLiteral("error_message")).toString(); + qWarning() << "SondeHub error:" << errorMessage; + if (errorObj.contains(QStringLiteral("payload"))) + { + QJsonObject payload = errorObj.value(QStringLiteral("payload")).toObject(); + qWarning() << "SondeHub error:" << QJsonDocument(payload); + } + } + else + { + qWarning() << "SondeHub error:" << QJsonDocument(errorObj); + } + } + } + //qDebug() << "SondeHub::handleReply: obj" << QJsonDocument(obj); + } + else if (document.isArray()) + { + QJsonArray array = document.array(); + + for (auto arrayRef : array) + { + if (arrayRef.isObject()) + { + QJsonObject obj = arrayRef.toObject(); + + if (obj.contains(QStringLiteral("vehicle")) && obj.contains(QStringLiteral("data"))) + { + QJsonArray data; + // Perhaps a bug that data is a string rather than an array? + if (obj.value(QStringLiteral("data")).isString()) + { + QJsonDocument dataDocument = QJsonDocument::fromJson(obj.value(QStringLiteral("data")).toString().toUtf8()); + data = dataDocument.array(); + } + else + { + data = obj.value(QStringLiteral("data")).toArray(); + } + + QList positions; + for (auto dataObjRef : data) + { + QJsonObject positionObj = dataObjRef.toObject(); + Position position; + + position.m_dateTime = QDateTime::fromSecsSinceEpoch(positionObj.value(QStringLiteral("time")).toInt()); + position.m_latitude = positionObj.value(QStringLiteral("lat")).toDouble(); + position.m_longitude = positionObj.value(QStringLiteral("lon")).toDouble(); + position.m_altitude = positionObj.value(QStringLiteral("alt")).toDouble(); + positions.append(position); + } + + emit prediction(obj.value("vehicle").toString(), positions); + } + } + else + { + qDebug() << "SondeHub::handleReply:" << bytes; + } + } + } + else + { + qDebug() << "SondeHub::handleReply:" << bytes; + } + } + else + { + qDebug() << "SondeHub::handleReply: error: " << reply->error() << reply->readAll(); + } + reply->deleteLater(); + } + else + { + qDebug() << "SondeHub::handleReply: reply is null"; + } +} diff --git a/android/app/src/main/cpp/util/sondehub.h b/android/app/src/main/cpp/util/sondehub.h new file mode 100644 index 0000000..8409ac9 --- /dev/null +++ b/android/app/src/main/cpp/util/sondehub.h @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SONDEHUB_H +#define INCLUDE_SONDEHUB_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class RS41Frame; +class RS41Subframe; + +class SDRBASE_API SondeHub : public QObject +{ + Q_OBJECT +protected: + SondeHub(); + +public: + + struct Position { + float m_latitude; + float m_longitude; + float m_altitude; + QDateTime m_dateTime; + }; + + static SondeHub* create(); + + ~SondeHub(); + + void upload( + const QString uploaderCallsign, + QDateTime timeReceived, + RS41Frame *frame, + const RS41Subframe *subframe, + float uploaderLat, + float uploaderLon, + float uploaderAlt + ); + + void updatePosition( + const QString& callsign, + float latitude, + float longitude, + float altitude, + const QString& radio, + const QString& antenna, + const QString& email, + bool mobile + ); + + void getPrediction(const QString& serial); + +private slots: + void handleReply(QNetworkReply* reply); + +signals: + void prediction(const QString& serial, const QList& path); + +private: + + QNetworkAccessManager *m_networkManager; + +}; + +#endif /* INCLUDE_SONDEHUB_H */ diff --git a/android/app/src/main/cpp/util/spinlock.cpp b/android/app/src/main/cpp/util/spinlock.cpp new file mode 100644 index 0000000..407000f --- /dev/null +++ b/android/app/src/main/cpp/util/spinlock.cpp @@ -0,0 +1,18 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#include "util/spinlock.h" diff --git a/android/app/src/main/cpp/util/spinlock.h b/android/app/src/main/cpp/util/spinlock.h new file mode 100644 index 0000000..64e8fc5 --- /dev/null +++ b/android/app/src/main/cpp/util/spinlock.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2016, 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_SPINLOCK_H +#define INCLUDE_SPINLOCK_H + +#include + +#include "export.h" + +class SDRBASE_API Spinlock { +public: + void lock() + { + while(!m_atomic.testAndSetAcquire(0, 1)) ; + } + + void unlock() + { + while(!m_atomic.testAndSetRelease(1, 0)) ; + } + +protected: + QAtomicInt m_atomic; +}; + +class SpinlockHolder { +public: + SpinlockHolder(Spinlock* spinlock) : + m_spinlock(spinlock) + { + m_spinlock->lock(); + } + + ~SpinlockHolder() + { + m_spinlock->unlock(); + } + +protected: + Spinlock* m_spinlock; +}; + +#endif // INCLUDE_SPINLOCK_H diff --git a/android/app/src/main/cpp/util/spyserverlist.cpp b/android/app/src/main/cpp/util/spyserverlist.cpp new file mode 100644 index 0000000..93b2cb1 --- /dev/null +++ b/android/app/src/main/cpp/util/spyserverlist.cpp @@ -0,0 +1,166 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "spyserverlist.h" + +#include +#include +#include +#include +#include +#include +#include + +SpyServerList::SpyServerList() +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect(m_networkManager, &QNetworkAccessManager::finished, this, &SpyServerList::handleReply); + + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + QDir writeableDir(locations[0]); + if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("spyserver"))) { + qDebug() << "Failed to create cache/spyserver"; + } + + m_cache = new QNetworkDiskCache(); + m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("spyserver")); + m_cache->setMaximumCacheSize(100000000); + m_networkManager->setCache(m_cache); + + connect(&m_timer, &QTimer::timeout, this, &SpyServerList::update); +} + +SpyServerList::~SpyServerList() +{ + QObject::disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &SpyServerList::handleReply); + delete m_networkManager; +} + +void SpyServerList::getData() +{ + QUrl url(QString("https://airspy.com/directory/status.json")); + m_networkManager->get(QNetworkRequest(url)); +} + +void SpyServerList::getDataPeriodically(int periodInMins) +{ + m_timer.setInterval(periodInMins*60*1000); + m_timer.start(); + update(); +} + +void SpyServerList::update() +{ + getData(); +} + +void SpyServerList::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QString url = reply->url().toEncoded().constData(); + QByteArray bytes = reply->readAll(); + + handleJSON(url, bytes); + } + else + { + qDebug() << "SpyServerList::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "SpyServerList::handleReply: reply is null"; + } +} + +void SpyServerList::handleJSON(const QString& url, const QByteArray& bytes) +{ + (void) url; + + QList sdrs; + QJsonDocument document = QJsonDocument::fromJson(bytes); + + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("servers"))) + { + QJsonArray servers = obj.value(QStringLiteral("servers")).toArray(); + + for (auto valRef : servers) + { + if (valRef.isObject()) + { + QJsonObject serverObj = valRef.toObject(); + SpyServer sdr; + + if (serverObj.contains(QStringLiteral("generalDescription"))) { + sdr.m_generalDescription = serverObj.value(QStringLiteral("generalDescription")).toString(); + } + if (serverObj.contains(QStringLiteral("deviceType"))) { + sdr.m_deviceType = serverObj.value(QStringLiteral("deviceType")).toString(); + } + if (serverObj.contains(QStringLiteral("streamingHost"))) { + sdr.m_streamingHost = serverObj.value(QStringLiteral("streamingHost")).toString(); + } + if (serverObj.contains(QStringLiteral("streamingPort"))) { + sdr.m_streamingPort = serverObj.value(QStringLiteral("streamingPort")).toInt(); + } + if (serverObj.contains(QStringLiteral("currentClientCount"))) { + sdr.m_currentClientCount = serverObj.value(QStringLiteral("currentClientCount")).toInt(); + } + if (serverObj.contains(QStringLiteral("maxClients"))) { + sdr.m_maxClients = serverObj.value(QStringLiteral("maxClients")).toInt(); + } + if (serverObj.contains(QStringLiteral("antennaType"))) { + sdr.m_antennaType = serverObj.value(QStringLiteral("antennaType")).toString(); + } + if (serverObj.contains(QStringLiteral("antennaLocation"))) + { + QJsonObject location = serverObj.value(QStringLiteral("antennaLocation")).toObject(); + sdr.m_latitude = location.value(QStringLiteral("lat")).toDouble(); + sdr.m_longitude = location.value(QStringLiteral("long")).toDouble(); + } + if (serverObj.contains(QStringLiteral("minimumFrequency"))) { + sdr.m_minimumFrequency = serverObj.value(QStringLiteral("minimumFrequency")).toInt(); + } + if (serverObj.contains(QStringLiteral("maximumFrequency"))) { + sdr.m_maximumFrequency = serverObj.value(QStringLiteral("maximumFrequency")).toInt(); + } + if (serverObj.contains(QStringLiteral("fullControlAllowed"))) { + sdr.m_fullControlAllowed = serverObj.value(QStringLiteral("fullControlAllowed")).toBool(); + } + if (serverObj.contains(QStringLiteral("online"))) { + sdr.m_online = serverObj.value(QStringLiteral("online")).toBool(); + } + + sdrs.append(sdr); + } + } + } + } + else + { + qDebug() << "SpyServerList::handleJSON: Doc doesn't contain an object:\n" << document; + } + + emit dataUpdated(sdrs); +} diff --git a/android/app/src/main/cpp/util/spyserverlist.h b/android/app/src/main/cpp/util/spyserverlist.h new file mode 100644 index 0000000..bafe3f4 --- /dev/null +++ b/android/app/src/main/cpp/util/spyserverlist.h @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SPYSERVERLIST_H +#define INCLUDE_SPYSERVERLIST_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QNetworkDiskCache; + +// Gets a list of public SpyServers from https://airspy.com/directory/status.json +class SDRBASE_API SpyServerList : public QObject +{ + Q_OBJECT + +public: + + struct SpyServer { + QString m_generalDescription; + QString m_deviceType; + QString m_streamingHost; // IP address + int m_streamingPort; // IP port + int m_currentClientCount; + int m_maxClients; + QString m_antennaType; + float m_latitude; + float m_longitude; + qint64 m_minimumFrequency; + qint64 m_maximumFrequency; + bool m_fullControlAllowed; + bool m_online; + }; + + SpyServerList(); + ~SpyServerList(); + + void getData(); + void getDataPeriodically(int periodInMins=2); + +public slots: + void handleReply(QNetworkReply* reply); + void update(); + +signals: + void dataUpdated(const QList& sdrs); // Emitted when data are available. + +private: + QNetworkAccessManager *m_networkManager; + QNetworkDiskCache *m_cache; + QTimer m_timer; // Timer for periodic updates + + void handleJSON(const QString& url, const QByteArray& bytes); + +}; + +#endif /* INCLUDE_SPYSERVERLIST_H */ diff --git a/android/app/src/main/cpp/util/stacktrace.h b/android/app/src/main/cpp/util/stacktrace.h new file mode 100644 index 0000000..e6538ca --- /dev/null +++ b/android/app/src/main/cpp/util/stacktrace.h @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// +// stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/ +// published under the WTFPL v2.0 + +#ifndef _STACKTRACE_H_ +#define _STACKTRACE_H_ + +#include +#include +#include +#include + +/** Print a demangled stack backtrace of the caller function to FILE* out. */ +static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63) +{ + fprintf(out, "stack trace:\n"); + + // storage array for stack trace address data + void* addrlist[max_frames+1]; + + // retrieve current stack addresses + int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*)); + + if (addrlen == 0) { + fprintf(out, " \n"); + return; + } + + // resolve addresses into strings containing "filename(function+address)", + // this array must be free()-ed + char** symbollist = backtrace_symbols(addrlist, addrlen); + + // allocate string which will be filled with the demangled function name + size_t funcnamesize = 256; + char* funcname = (char*)malloc(funcnamesize); + + // iterate over the returned symbol lines. skip the first, it is the + // address of this function. + for (int i = 1; i < addrlen; i++) + { + char *begin_name = 0, *begin_offset = 0, *end_offset = 0; + + // find parentheses and +address offset surrounding the mangled name: + // ./module(function+0x15c) [0x8048a6d] + for (char *p = symbollist[i]; *p; ++p) + { + if (*p == '(') + begin_name = p; + else if (*p == '+') + begin_offset = p; + else if (*p == ')' && begin_offset) { + end_offset = p; + break; + } + } + + if (begin_name && begin_offset && end_offset + && begin_name < begin_offset) + { + *begin_name++ = '\0'; + *begin_offset++ = '\0'; + *end_offset = '\0'; + + // mangled name is now in [begin_name, begin_offset) and caller + // offset in [begin_offset, end_offset). now apply + // __cxa_demangle(): + + int status; + char* ret = abi::__cxa_demangle(begin_name, + funcname, &funcnamesize, &status); + if (status == 0) { + funcname = ret; // use possibly realloc()-ed string + fprintf(out, " %s : %s+%s\n", + symbollist[i], funcname, begin_offset); + } + else { + // demangling failed. Output function name as a C function with + // no arguments. + fprintf(out, " %s : %s()+%s\n", + symbollist[i], begin_name, begin_offset); + } + } + else + { + // couldn't parse the line? print the whole line. + fprintf(out, " %s\n", symbollist[i]); + } + } + + free(funcname); + free(symbollist); +} + +#endif // _STACKTRACE_H_ diff --git a/android/app/src/main/cpp/util/stepfunctions.h b/android/app/src/main/cpp/util/stepfunctions.h new file mode 100644 index 0000000..1e6ba84 --- /dev/null +++ b/android/app/src/main/cpp/util/stepfunctions.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2017, 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_STEPFUNCTIONS_H_ +#define SDRBASE_UTIL_STEPFUNCTIONS_H_ + +class StepFunctions +{ +public: + static float smootherstep(float x) + { + if (x == 1.0f) { + return 1.0f; + } else if (x == 0.0f) { + return 0.0f; + } + + double x3 = x * x * x; + double x4 = x * x3; + double x5 = x * x4; + + return (float) (6.0*x5 - 15.0*x4 + 10.0*x3); + } +}; + +#endif /* SDRBASE_UTIL_STEPFUNCTIONS_H_ */ diff --git a/android/app/src/main/cpp/util/stix.cpp b/android/app/src/main/cpp/util/stix.cpp new file mode 100644 index 0000000..9230141 --- /dev/null +++ b/android/app/src/main/cpp/util/stix.cpp @@ -0,0 +1,176 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "stix.h" + +#include +#include +#include +#include +#include + +STIX::STIX() +{ + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &STIX::handleReply); + connect(&m_dataTimer, &QTimer::timeout, this, &STIX::getData); +} + + +STIX::~STIX() +{ + disconnect(&m_dataTimer, &QTimer::timeout, this, &STIX::getData); + disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &STIX::handleReply); + delete m_networkManager; +} + +STIX* STIX::create() +{ + return new STIX(); +} + +void STIX::getDataPeriodically(int periodInMins) +{ + if (periodInMins > 0) + { + m_dataTimer.setInterval(periodInMins*60*1000); + m_dataTimer.start(); + getData(); + } + else + { + m_dataTimer.stop(); + } +} + +void STIX::getData() +{ + QUrlQuery data(QString("https://datacenter.stix.i4ds.net/api/request/flare-list")); + QDateTime start; + + if (m_mostRecent.isValid()) { + start = m_mostRecent; + } else { + start = QDateTime::currentDateTime().addDays(-5); + } + + data.addQueryItem("start_utc", start.toString(Qt::ISODate)); + data.addQueryItem("end_utc", QDateTime::currentDateTime().toString(Qt::ISODate)); + data.addQueryItem("sort", "time"); + + QUrl url("https://datacenter.stix.i4ds.net/api/request/flare-list"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + m_networkManager->post(request, data.toString(QUrl::FullyEncoded).toUtf8()); +} + +bool STIX::containsNonNull(const QJsonObject& obj, const QString &key) const +{ + if (obj.contains(key)) + { + QJsonValue val = obj.value(key); + return !val.isNull(); + } + return false; +} + +void STIX::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + + if (document.isArray()) + { + QJsonArray array = document.array(); + QList data; + for (auto valRef : array) + { + if (valRef.isObject()) + { + QJsonObject obj = valRef.toObject(); + + FlareData measurement; + + if (obj.contains(QStringLiteral("flare_id"))) { + measurement.m_id = obj.value(QStringLiteral("flare_id")).toString(); + } + if (obj.contains(QStringLiteral("start_UTC"))) + { + measurement.m_startDateTime = QDateTime::fromString(obj.value(QStringLiteral("start_UTC")).toString(), Qt::ISODate); + if (!m_mostRecent.isValid() || (measurement.m_startDateTime > m_mostRecent)) { + m_mostRecent = measurement.m_startDateTime; + } + } + if (obj.contains(QStringLiteral("end_UTC"))) { + measurement.m_endDateTime = QDateTime::fromString(obj.value(QStringLiteral("end_UTC")).toString(), Qt::ISODate); + } + if (obj.contains(QStringLiteral("peak_UTC"))) { + measurement.m_peakDateTime = QDateTime::fromString(obj.value(QStringLiteral("peak_UTC")).toString(), Qt::ISODate); + } + if (obj.contains(QStringLiteral("duration"))) { + measurement.m_duration = obj.value(QStringLiteral("duration")).toInt(); + } + if (obj.contains(QStringLiteral("GOES_flux"))) { + measurement.m_flux = obj.value(QStringLiteral("GOES_flux")).toDouble(); + } + + data.append(measurement); + } + else + { + qDebug() << "STIX::handleReply: Array element is not an object: " << valRef; + } + } + if (data.size() > 0) + { + m_data.append(data); + emit dataUpdated(m_data); + } + else + { + qDebug() << "STIX::handleReply: No data in array: " << document; + } + } + else + { + qDebug() << "STIX::handleReply: Document is not an array: " << document; + } + } + else + { + qDebug() << "STIX::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "STIX::handleReply: reply is null"; + } +} + + QString STIX::FlareData::getLightCurvesURL() const + { + return QString("https://datacenter.stix.i4ds.net/view/plot/lightcurves?start=%1&span=%2").arg(m_startDateTime.toSecsSinceEpoch()).arg(m_duration); + } + + QString STIX::FlareData::getDataURL() const + { + return QString("https://datacenter.stix.i4ds.net/view/list/fits/%1/%2").arg(m_startDateTime.toSecsSinceEpoch()).arg(m_duration); + } diff --git a/android/app/src/main/cpp/util/stix.h b/android/app/src/main/cpp/util/stix.h new file mode 100644 index 0000000..924b8e7 --- /dev/null +++ b/android/app/src/main/cpp/util/stix.h @@ -0,0 +1,83 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_STIX_H +#define INCLUDE_STIX_H + +#include +#include +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +// Solar Orbiter STIX (Spectrometer/Telescope for Imaging X-rays) instrument +// Gets solar flare data - Newest data is often about 24 hours old +class SDRBASE_API STIX : public QObject +{ + Q_OBJECT +protected: + STIX(); + +public: + struct SDRBASE_API FlareData { + QString m_id; + QDateTime m_startDateTime; + QDateTime m_endDateTime; + QDateTime m_peakDateTime; + int m_duration; // In seconds + double m_flux; + FlareData() : + m_duration(0), + m_flux(NAN) + { + } + + QString getLightCurvesURL() const; + QString getDataURL() const; + }; + + static STIX* create(); + + ~STIX(); + void getDataPeriodically(int periodInMins=60); + +public slots: + void getData(); + +private slots: + void handleReply(QNetworkReply* reply); + +signals: + void dataUpdated(const QList& data); // Called when new data available. + +private: + bool containsNonNull(const QJsonObject& obj, const QString &key) const; + + QTimer m_dataTimer; // Timer for periodic updates + QNetworkAccessManager *m_networkManager; + + QDateTime m_mostRecent; + QList m_data; + +}; + +#endif /* INCLUDE_STIX_H */ + diff --git a/android/app/src/main/cpp/util/syncmessenger.cpp b/android/app/src/main/cpp/util/syncmessenger.cpp new file mode 100644 index 0000000..00f59bb --- /dev/null +++ b/android/app/src/main/cpp/util/syncmessenger.cpp @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2014 John Greb // +// Copyright (C) 2015, 2017-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/syncmessenger.h" +#include "util/message.h" + +SyncMessenger::SyncMessenger() : + m_complete(0), + m_message(0), + m_result(0) +{ + qRegisterMetaType("Message"); +} + +SyncMessenger::~SyncMessenger() +{} + +int SyncMessenger::sendWait(Message& message, unsigned long msPollTime) +{ + m_message = &message; + m_mutex.lock(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + m_complete.storeRelaxed(0); +#else + m_complete.store(0); +#endif + + emit messageSent(); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + while (!m_complete.loadRelaxed()) { + m_waitCondition.wait(&m_mutex, msPollTime); + } +#else + while (!m_complete.load()) { + m_waitCondition.wait(&m_mutex, msPollTime); + } +#endif + + int result = m_result; + m_mutex.unlock(); + + return result; +} + +void SyncMessenger::done(int result) +{ + m_result = result; +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + m_complete.storeRelaxed(1); +#else + m_complete.store(1); +#endif + m_waitCondition.wakeAll(); +} + + + diff --git a/android/app/src/main/cpp/util/syncmessenger.h b/android/app/src/main/cpp/util/syncmessenger.h new file mode 100644 index 0000000..a66c1b9 --- /dev/null +++ b/android/app/src/main/cpp/util/syncmessenger.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_SYNCMESSENGER_H_ +#define INCLUDE_UTIL_SYNCMESSENGER_H_ + +#include +#include +#include +#include +#include "export.h" + +class Message; + +/** + * This class is responsible of managing the synchronous processing of a message across threads + */ +class SDRBASE_API SyncMessenger : public QObject { + Q_OBJECT + +public: + SyncMessenger(); + ~SyncMessenger(); + + int sendWait(Message& message, unsigned long msPollTime = 100); //!< Send message and waits for its process completion + Message* getMessage() const { return m_message; } + void storeMessage(Message& message) { m_message = &message; } + void done(int result = 0); //!< Processing of the message is complete + +signals: + void messageSent(); + +protected: + QWaitCondition m_waitCondition; + QMutex m_mutex; + QAtomicInt m_complete; + Message *m_message; + int m_result; +}; + + + +#endif /* INCLUDE_UTIL_SYNCMESSENGER_H_ */ diff --git a/android/app/src/main/cpp/util/threadsafesingleton.h b/android/app/src/main/cpp/util/threadsafesingleton.h new file mode 100644 index 0000000..d9d7dda --- /dev/null +++ b/android/app/src/main/cpp/util/threadsafesingleton.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_THREADSAFESINGLETON_H_ +#define INCLUDE_UTIL_THREADSAFESINGLETON_H_ + +//! Template class used to make from a class a singleton. +/** S is the type of the singleton object to instantiate. +
This class permits to gather all singleton code in the same place + and not to pollute object interface with singleton methods. +
Accessing to the instance of S is made through ThreadSafeSingleton::instance() +
Note : The class using this adapter should have constructor and + destructor protected and copy constructor and operator= private. +
Besides it should declare the class ThreadSafeSingleton friend in order + this class can make the construction. +*/ + +template class ThreadSafeSingleton +{ +public: + static S& instance() { return *_pInstance; } + +protected: + ThreadSafeSingleton() {} + ~ThreadSafeSingleton() { delete _pInstance; } + static S* _pInstance; + +private: + ThreadSafeSingleton(const ThreadSafeSingleton&) {} + ThreadSafeSingleton& operator = (const ThreadSafeSingleton&) {return *this;} +}; + +template S *ThreadSafeSingleton::_pInstance = new S; + +#endif /* INCLUDE_UTIL_THREADSAFESINGLETON_H_ */ diff --git a/android/app/src/main/cpp/util/timeutil.cpp b/android/app/src/main/cpp/util/timeutil.cpp new file mode 100644 index 0000000..c877c8d --- /dev/null +++ b/android/app/src/main/cpp/util/timeutil.cpp @@ -0,0 +1,40 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "timeutil.h" + +uint64_t TimeUtil::nowms() +{ + auto now = std::chrono::system_clock::now(); + auto now_ms = std::chrono::time_point_cast(now); + auto epoch = now_ms.time_since_epoch(); + auto value = std::chrono::duration_cast(epoch); + + return value.count(); +} + +uint64_t TimeUtil::nowus() +{ + auto now = std::chrono::system_clock::now(); + auto now_ms = std::chrono::time_point_cast(now); + auto epoch = now_ms.time_since_epoch(); + auto value = std::chrono::duration_cast(epoch); + + return value.count(); +} diff --git a/android/app/src/main/cpp/util/timeutil.h b/android/app/src/main/cpp/util/timeutil.h new file mode 100644 index 0000000..ba6cbce --- /dev/null +++ b/android/app/src/main/cpp/util/timeutil.h @@ -0,0 +1,35 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_TIMEUTIL_H_ +#define SDRBASE_UTIL_TIMEUTIL_H_ + +#include +#include + +#include "export.h" + +class SDRBASE_API TimeUtil +{ +public: + static uint64_t nowms(); //!< returns the current epoch in milliseconds + static uint64_t nowus(); //!< returns the current epoch in microseconds +}; + +#endif // SDRBASE_UTIL_TIMEUTIL_H_ diff --git a/android/app/src/main/cpp/util/udpsinkutil.h b/android/app/src/main/cpp/util/udpsinkutil.h new file mode 100644 index 0000000..aedbc2b --- /dev/null +++ b/android/app/src/main/cpp/util/udpsinkutil.h @@ -0,0 +1,149 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UTIL_UDPSINK_H_ +#define INCLUDE_UTIL_UDPSINK_H_ + +#include +#include +#include +#include + +template +class UDPSinkUtil +{ +public: + UDPSinkUtil(QObject *parent, unsigned int udpSize) : + m_udpSize(udpSize), + m_udpSamples(udpSize/sizeof(T)), + m_address(QHostAddress::LocalHost), + m_port(9999), + m_sampleBufferIndex(0) + { + m_sampleBuffer = new T[m_udpSamples]; + m_socket = new QUdpSocket(parent); + } + + UDPSinkUtil(QObject *parent, unsigned int udpSize, unsigned int port) : + m_udpSize(udpSize), + m_udpSamples(udpSize/sizeof(T)), + m_address(QHostAddress::LocalHost), + m_port(port), + m_sampleBufferIndex(0) + { + m_sampleBuffer = new T[m_udpSamples]; + m_socket = new QUdpSocket(parent); + } + + UDPSinkUtil (QObject *parent, unsigned int udpSize, QHostAddress& address, unsigned int port) : + m_udpSize(udpSize), + m_udpSamples(udpSize/sizeof(T)), + m_address(address), + m_port(port), + m_sampleBufferIndex(0) + { + m_sampleBuffer = new T[m_udpSamples]; + m_socket = new QUdpSocket(parent); + } + + ~UDPSinkUtil() + { + delete[] m_sampleBuffer; + delete m_socket; + } + + void moveToThread(QThread *thread) + { + m_socket->moveToThread(thread); + } + + void setAddress(const QString& address) { m_address.setAddress(address); } + void setPort(unsigned int port) { m_port = port; } + + void setDestination(const QString& address, int port) + { + m_address.setAddress(const_cast(address)); + m_port = port; + } + + /** + * Write one sample + */ + void write(T sample) + { + if (m_sampleBufferIndex < m_udpSamples) + { + m_sampleBuffer[m_sampleBufferIndex] = sample; + m_sampleBufferIndex++; + } + else + { + m_socket->writeDatagram((const char*)&m_sampleBuffer[0], (qint64 ) m_udpSize, m_address, m_port); + m_sampleBuffer[0] = sample; + m_sampleBufferIndex = 1; + } + } + + /** + * Write a bunch of samples + */ + void write(T *samples, int nbSamples) + { + int samplesIndex = 0; + + if (m_sampleBufferIndex + nbSamples > m_udpSamples) // fill remainder of buffer and send it + { + memcpy(&m_sampleBuffer[m_sampleBufferIndex], &samples[samplesIndex], (m_udpSamples - m_sampleBufferIndex)*sizeof(T)); // fill remainder of buffer + m_socket->writeDatagram((const char*)&m_sampleBuffer[0], (qint64 ) m_udpSize, m_address, m_port); // send buffer + samplesIndex += (m_udpSamples - m_sampleBufferIndex); + nbSamples -= (m_udpSamples - m_sampleBufferIndex); + m_sampleBufferIndex = 0; + } + + while (nbSamples > m_udpSamples) // send directly from input without buffering + { + m_socket->writeDatagram((const char*)&samples[samplesIndex], (qint64 ) m_udpSize, m_address, m_port); + samplesIndex += m_udpSamples; + nbSamples -= m_udpSamples; + } + + memcpy(&m_sampleBuffer[m_sampleBufferIndex], &samples[samplesIndex], nbSamples*sizeof(T)); // copy remainder of input to buffer + } + + /** + * Write a bunch of samples unbuffered + */ + void writeUnbuffered(const T *samples, int nbSamples) + { + m_socket->writeDatagram((const char*)samples, (qint64 ) nbSamples, m_address, m_port); // send given samples + } + +private: + int m_udpSize; + int m_udpSamples; + QHostAddress m_address; + unsigned int m_port; + QUdpSocket *m_socket; + T *m_sampleBuffer;; + int m_sampleBufferIndex; +}; + + + +#endif /* INCLUDE_UTIL_UDPSINK_H_ */ diff --git a/android/app/src/main/cpp/util/uid.cpp b/android/app/src/main/cpp/util/uid.cpp new file mode 100644 index 0000000..0923424 --- /dev/null +++ b/android/app/src/main/cpp/util/uid.cpp @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// Object unique id calculator loosely inspired by MongoDB object id // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#if (defined _WIN32_) || (defined _MSC_VER) +#include +#else +#include +#endif + +#include "uid.h" + +uint64_t UidCalculator::getNewObjectId() +{ + QDateTime currentDateTime = QDateTime::currentDateTime(); + uint64_t uid = currentDateTime.toSecsSinceEpoch(); + uid *= 1000000UL; // make room for microseconds + +// Fallback to milliseconds: +// QTime timeNow = QTime::currentTime(); +// uint64_t usecs = timeNow.msec() * 1000UL; +// uid += usecs; + uid += getCurrentMiroseconds(); + + return uid; +} + +uint32_t UidCalculator::getNewInstanceId() +{ + uint32_t uid = (QCoreApplication::applicationPid() % (1<<16)); + + QString hostname = QHostInfo::localHostName(); + QByteArray hashKey = QCryptographicHash::hash(hostname.toUtf8(), QCryptographicHash::Sha1); + uint32_t hashHost = 0; + + for (int i = 0; i < hashKey.size(); i++) { + char c = hashKey.at(i); + hashHost += (uint32_t) c; + } + + hashHost %= (1<<16); + uid += (hashHost<<16); + + return uid; +} + +uint64_t UidCalculator::getCurrentMiroseconds() +{ +#if (defined _WIN32_) || (defined _MSC_VER) + LARGE_INTEGER tickPerSecond; + LARGE_INTEGER tick; // a point in time + + // get the high resolution counter's accuracy + QueryPerformanceFrequency(&tickPerSecond); + + // what time is it ? + QueryPerformanceCounter(&tick); + + // and here we get the current microsecond! \o/ + return (tick.QuadPart % tickPerSecond.QuadPart); +#else + struct timeval tv; + gettimeofday(&tv, 0); + return tv.tv_usec; +#endif +} + diff --git a/android/app/src/main/cpp/util/uid.h b/android/app/src/main/cpp/util/uid.h new file mode 100644 index 0000000..5ab31f5 --- /dev/null +++ b/android/app/src/main/cpp/util/uid.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2015 John Greb // +// // +// Object unique id calculator loosely inspired by MongoDB object id // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// +#ifndef SDRBASE_UTIL_UID_H_ +#define SDRBASE_UTIL_UID_H_ + +#include + +#include "export.h" + +class SDRBASE_API UidCalculator +{ +public: + /** + * Get a new object unique Id. It is the addition of: + * - 6 digit microseconds in current second + * - 4 byte Unix epoch multiplied by 1000000 + */ + static uint64_t getNewObjectId(); + + /** + * Get a new instance unique Id. It is made of from LSB to MSB: + * - 2 byte process id + * - 2 byte hashed host name + */ + static uint32_t getNewInstanceId(); + +private: + static uint64_t getCurrentMiroseconds(); +}; + +#endif /* SDRBASE_UTIL_UID_H_ */ diff --git a/android/app/src/main/cpp/util/units.cpp b/android/app/src/main/cpp/util/units.cpp new file mode 100644 index 0000000..e6ede06 --- /dev/null +++ b/android/app/src/main/cpp/util/units.cpp @@ -0,0 +1,21 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "units.h" diff --git a/android/app/src/main/cpp/util/units.h b/android/app/src/main/cpp/util/units.h new file mode 100644 index 0000000..5d770a2 --- /dev/null +++ b/android/app/src/main/cpp/util/units.h @@ -0,0 +1,483 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2016, 2018-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2020-2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_UNITS_H +#define INCLUDE_UNITS_H + +#include +#include +#include +#include +#include + +#include "export.h" + +// Unit conversions +class SDRBASE_API Units +{ +public: + + static inline float feetToMetres(float feet) + { + return feet * 0.3048f; + } + + static inline int feetToIntegerMetres(float feet) + { + return (int)std::round(feetToMetres(feet)); + } + + static inline float metresToFeet(float metres) + { + return metres * 3.28084f; + } + + static inline int metresToIntegerFeet(float metres) + { + return (int)std::round(metresToFeet(metres)); + } + + static inline float nauticalMilesToMetres(float nauticalMiles) + { + return nauticalMiles * 1855.0f; + } + + static inline int nauticalMilesToIntegerMetres(float nauticalMiles) + { + return (int)std::round(nauticalMilesToMetres(nauticalMiles)); + } + + static float knotsToKPH(float knots) + { + return knots * 1.852f; + } + + static int knotsToIntegerKPH(float knots) + { + return (int)std::round(knotsToKPH(knots)); + } + + static float knotsToMPH(float knots) + { + return knots * 1.15078f; + } + + static int knotsToIntegerMPH(float knots) + { + return (int)std::round(knotsToMPH(knots)); + } + + static float knotsToMetresPerSecond(float knots) + { + return knots * 0.514444f; + } + + static float kmpsToKPH(float kps) + { + return kps * (60.0 * 60.0); + } + + static int kmpsToIntegerKPH(float kps) + { + return (int)std::round(kmpsToKPH(kps)); + } + + static float feetPerMinToMetresPerSecond(float fpm) + { + return fpm * 0.00508f; + } + + static int feetPerMinToIntegerMetresPerSecond(float fpm) + { + return (int)std::round(feetPerMinToMetresPerSecond(fpm)); + } + + template + static T degreesToRadians(T degrees) + { + return degrees * ((T)M_PI) / 180.0f; + } + + template + static T radiansToDegrees(T radians) + { + return radians * 180.0f / ((T)M_PI); + } + + static float fahrenheitToCelsius(float fahrenheit) + { + return (fahrenheit - 32.0f) * (5.0f/9.0f); + } + + static float celsiusToKelvin(float celsius) + { + return celsius + 273.15f; + } + + static float inchesToMilimetres(float inches) + { + return inches * 25.4f; + } + + static float milesToKilometres(float miles) + { + return miles * 1.60934f; + } + + static float degreesMinutesSecondsToDecimal(int degrees, int minutes, float seconds) + { + float dec = std::abs(degrees) + minutes * 1.0f/60.0f + seconds * 1.0f/(60.0f*60.0f); + if (degrees < 0) + return -dec; + else + return dec; + } + + static float hoursMinutesSecondsToDecimal(int hours, int minutes, float seconds) + { + return hours + minutes * 1.0f/60.0f + seconds * 1.0f/(60.0f*60.0f); + } + + // Also supports decimal degrees + static bool degreeMinuteAndSecondsToDecimalDegrees(const QString& string, float& degrees) + { + QRegularExpression decimal(QRegularExpression::anchoredPattern("(-?[0-9]+(\\.[0-9]+)?)")); + QRegularExpressionMatch match; + match = decimal.match(string); + if (match.hasMatch()) + { + degrees = match.capturedTexts()[1].toFloat(); + return true; + } + + QRegularExpression dms(QRegularExpression::anchoredPattern(QString("(-)?([0-9]+)[%1d](([0-9]+)['m](([0-9]+(\\.[0-9]+)?)[\"s])?)?").arg(QChar(0xb0)))); + match = dms.match(string); + if (match.hasMatch()) + { + float d = 0.0f; + bool neg = false; + if (dms.captureCount() >= 1) { + neg = match.capturedTexts()[1] == "-"; + } + if (dms.captureCount() >= 3) { + d = match.capturedTexts()[2].toFloat(); + } + float m = 0.0f; + if (dms.captureCount() >= 5) { + m = match.capturedTexts()[4].toFloat(); + } + float s = 0.0f; + if (dms.captureCount() >= 7) { + s = match.capturedTexts()[6].toFloat(); + } + degrees = d + m/60.0 + s/(60.0*60.0); + if (neg) { + degrees = -degrees; + } + return true; + } + return false; + } + + static QString decimalDegreesToDegreeMinutesAndSeconds(float decimal, int secondsFieldWidth=5) + { + double v, d, m, s; + int neg; + + v = decimal; + neg = v < 0.0f; + v = fabs(v); + d = floor(v); + v -= d; + v *= 60.0; + m = floor(v); + v -= m; + v *= 60.0; + s = v; + return QString("%1%2%3%4'%5\"").arg(neg ? "-" : "").arg((int)d).arg(QChar(0xb0)).arg((int)m, 2, 10, QChar('0')).arg(s, secondsFieldWidth, 'f', 2, QChar('0')); + } + + static QString decimalDegreesToDegreesAndMinutes(float decimal) + { + double v, d, m; + int neg; + + v = decimal; + neg = v < 0.0f; + v = fabs(v); + d = floor(v); + v -= d; + v *= 60.0; + m = round(v); + if (m == 60) + { + if (neg) { + d--; + } else { + d++; + } + m = 0; + } + return QString("%1%2%3%4'").arg(neg ? "-" : "").arg((int)d).arg(QChar(0xb0)).arg((int)m, 2, 10, QChar('0')); + } + + static QString decimalDegreesToDegrees(float decimal) + { + double v, d; + int neg; + + v = decimal; + neg = v < 0.0f; + v = fabs(v); + d = round(v); + return QString("%1%2%3").arg(neg ? "-" : "").arg((int)d).arg(QChar(0xb0)); + } + + static QString decimalHoursToHoursMinutesAndSeconds(float decimal, int precision=2) + { + double v, h, m, s; + + v = decimal; + v = fabs(v); + h = floor(v); + v -= h; + v *= 60.0; + m = floor(v); + v -= m; + v *= 60.0; + s = v; + return QString("%1h%2m%3s").arg((int)h).arg((int)m, 2, 10, QChar('0')).arg(s, 2, 'f', precision, QChar('0')); + } + + // Try to convert a string to latitude and longitude. Returns false if not recognised format. + // https://en.wikipedia.org/wiki/ISO_6709 specifies a standard syntax + // We support both decimal and DMS formats + static bool stringToLatitudeAndLongitude(const QString& string, float& latitude, float& longitude, bool exact=true) + { + QRegularExpressionMatch match; + + QString decimalPattern = "(-?[0-9]+(\\.[0-9]+)?) *[ ,] *(-?[0-9]+(\\.[0-9]+)?)"; + if (exact) { + decimalPattern = QRegularExpression::anchoredPattern(decimalPattern); + } + QRegularExpression decimal(decimalPattern); + match = decimal.match(string); + if (match.hasMatch()) + { + latitude = match.capturedTexts()[1].toFloat(); + longitude = match.capturedTexts()[3].toFloat(); + return true; + } + + QString dmsPattern = QString("([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([NS]) *,? *([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([EW])").arg(QChar(0xb0)); + if (exact) { + dmsPattern = QRegularExpression::anchoredPattern(dmsPattern); + } + QRegularExpression dms(dmsPattern); + match = dms.match(string); + if (match.hasMatch()) + { + float latD = match.capturedTexts()[1].toFloat(); + float latM = match.capturedTexts()[2].toFloat(); + float latS = match.capturedTexts()[3].toFloat(); + bool north = match.capturedTexts()[5] == "N"; + float lonD = match.capturedTexts()[6].toFloat(); + float lonM = match.capturedTexts()[7].toFloat(); + float lonS = match.capturedTexts()[8].toFloat(); + bool east = match.capturedTexts()[10] == "E"; + latitude = latD + latM/60.0 + latS/(60.0*60.0); + if (!north) + latitude = -latitude; + longitude = lonD + lonM/60.0 + lonS/(60.0*60.0); + if (!east) + longitude = -longitude; + return true; + } + + QString dms2Pattern = "([0-9]+)([NS])([0-9]{2})([0-9]{2}) *,?([0-9]+)([EW])([0-9]{2})([0-9]{2})"; + if (exact) { + dms2Pattern = QRegularExpression::anchoredPattern(dms2Pattern); + } + QRegularExpression dms2(dms2Pattern); + match = dms2.match(string); + if (match.hasMatch()) + { + float latD = match.capturedTexts()[1].toFloat(); + bool north = match.capturedTexts()[2] == "N"; + float latM = match.capturedTexts()[3].toFloat(); + float latS = match.capturedTexts()[4].toFloat(); + float lonD = match.capturedTexts()[5].toFloat(); + bool east = match.capturedTexts()[6] == "E"; + float lonM = match.capturedTexts()[7].toFloat(); + float lonS = match.capturedTexts()[8].toFloat(); + latitude = latD + latM/60.0 + latS/(60.0*60.0); + if (!north) + latitude = -latitude; + longitude = lonD + lonM/60.0 + lonS/(60.0*60.0); + if (!east) + longitude = -longitude; + return true; + } + + // 512255.5900N 0024400.6105W as used on aviation charts + QString dms3Pattern = "(\\d{2})(\\d{2})((\\d{2})(\\.\\d+)?)([NS]) *,?(\\d{3})(\\d{2})((\\d{2})(\\.\\d+)?)([EW])"; + if (exact) { + dms3Pattern = QRegularExpression::anchoredPattern(dms3Pattern); + } + QRegularExpression dms3(dms3Pattern); + match = dms3.match(string); + if (match.hasMatch()) + { + float latD = match.capturedTexts()[1].toFloat(); + float latM = match.capturedTexts()[2].toFloat(); + float latS = match.capturedTexts()[3].toFloat(); + bool north = match.capturedTexts()[6] == "N"; + float lonD = match.capturedTexts()[7].toFloat(); + float lonM = match.capturedTexts()[8].toFloat(); + float lonS = match.capturedTexts()[9].toFloat(); + bool east = match.capturedTexts()[12] == "E"; + latitude = latD + latM/60.0 + latS/(60.0*60.0); + if (!north) + latitude = -latitude; + longitude = lonD + lonM/60.0 + lonS/(60.0*60.0); + if (!east) + longitude = -longitude; + return true; + } + return false; + } + + // Try to convert a string to Right Ascension (RA) and Declination. Returns false if not recognised format. + // This supports HMS/DMS or decimal. + // E.g.: + // 12 05 12.23 +17 06 21.0 + // 12:05:12.23 -17:06:21.0 + // 12h05m12.23s +17d06m21.0s + // 107.1324 -34.233 + static bool stringToRADec(const QString& string, float& ra, float& dec) + { + QRegularExpressionMatch match; + QRegularExpression dms(QRegularExpression::anchoredPattern("([0-9]+)[ :h]([0-9]+)[ :m]([0-9]+(\\.[0-9]+)?)s? *,? *([+-]?[0-9]+)[ :d]([0-9]+)[ :m]([0-9]+(\\.[0-9]+)?)s?")); + match = dms.match(string); + if (match.hasMatch()) + { + int raHours = match.capturedTexts()[1].toInt(); + int raMins = match.capturedTexts()[2].toInt(); + float raSecs = match.capturedTexts()[3].toFloat(); + ra = raHours + raMins / 60.0f + raSecs / (60.0f * 60.0f); + qDebug() << ra << raHours << raMins << raSecs; + int decDegs = match.capturedTexts()[5].toInt(); + int decMins = match.capturedTexts()[6].toInt(); + float decSecs = match.capturedTexts()[7].toFloat(); + bool neg = decDegs < 0; + dec = abs(decDegs) + decMins / 60.0f + decSecs / (60.0f * 60.0f); + if (neg) { + dec = -dec; + } + return true; + } + + QRegularExpression decimal(QRegularExpression::anchoredPattern("([0-9]+(\\.[0-9]+)?) *,? *([+-]?[0-9]+(\\.[0-9]+)?)")); + match = decimal.match(string); + if (match.hasMatch()) + { + ra = match.capturedTexts()[1].toFloat(); + dec = match.capturedTexts()[3].toFloat(); + return true; + } + return false; + } + + static double raToDecimal(const QString& value) + { + QRegularExpression decimal(QRegularExpression::anchoredPattern("^([0-9]+(\\.[0-9]+)?)")); + QRegularExpression hms(QRegularExpression::anchoredPattern("^([0-9]+)[ h]([0-9]+)[ m]([0-9]+(\\.[0-9]+)?)s?")); + QRegularExpressionMatch decimalMatch = decimal.match(value); + QRegularExpressionMatch hmsMatch = hms.match(value); + + if (decimalMatch.hasMatch()) + return decimalMatch.capturedTexts()[0].toDouble(); + else if (hmsMatch.hasMatch()) + { + return Units::hoursMinutesSecondsToDecimal( + hmsMatch.capturedTexts()[1].toDouble(), + hmsMatch.capturedTexts()[2].toDouble(), + hmsMatch.capturedTexts()[3].toDouble()); + } + return 0.0; + } + + static double decToDecimal(const QString& value) + { + QRegularExpression decimal(QRegularExpression::anchoredPattern("^(-?[0-9]+(\\.[0-9]+)?)")); + QRegularExpression dms(QRegularExpression::anchoredPattern(QString("^(-?[0-9]+)[ %1d]([0-9]+)[ 'm]([0-9]+(\\.[0-9]+)?)[\"s]?").arg(QChar(0xb0)))); + QRegularExpressionMatch decimalMatch = decimal.match(value); + QRegularExpressionMatch dmsMatch = dms.match(value); + + if (decimalMatch.hasMatch()) + return decimalMatch.capturedTexts()[0].toDouble(); + else if (dmsMatch.hasMatch()) + { + return Units::degreesMinutesSecondsToDecimal( + dmsMatch.capturedTexts()[1].toDouble(), + dmsMatch.capturedTexts()[2].toDouble(), + dmsMatch.capturedTexts()[3].toDouble()); + } + return 0.0; + } + + static float solarFluxUnitsToJansky(float sfu) + { + return sfu * 10000.0f; + } + + template + static T solarFluxUnitsToWattsPerMetrePerHertz(T sfu) + { + return sfu * 1e-22f; + } + + template + static T wattsPerMetrePerHertzToSolarFluxUnits(T w) + { + return w / 1e-22f; + } + + template + static T wattsPerMetrePerHertzToJansky(T w) + { + return w / 1e-26f; + } + + template + static T noiseFigureToNoiseTemp(T nfdB, T refTempK=T(290.0)) + { + return refTempK * (std::pow(T(10.0), nfdB/T(10.0)) - T(1.0)); + } + + template + static T noiseTempToNoiseFigureTo(T tempK, T refTempK=T(290.0)) + { + return T(10.0) * std::log10(tempK/refTempK+T(1.0)); + } + +}; + +#endif // INCLUDE_UNITS_H diff --git a/android/app/src/main/cpp/util/visa.cpp b/android/app/src/main/cpp/util/visa.cpp new file mode 100644 index 0000000..e1d2266 --- /dev/null +++ b/android/app/src/main/cpp/util/visa.cpp @@ -0,0 +1,281 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "visa.h" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +VISA::VISA() : + m_defaultRM(0), + viOpenDefaultRM(nullptr), + viOpen(nullptr), + viClose(nullptr), + viPrintf(nullptr), + viScanf(nullptr), + m_available(false) +{ +#ifdef _MSC_VER + const char *visaName = "visa32.dll"; // Loads visa64.dll on WIN64 +#else + const char *visaName = "libktvisa32.so"; // Keysight library +#endif + + visaLibrary = libraryOpen(visaName); + if (visaLibrary) + { + viOpenDefaultRM = (ViStatus (*)(ViPSession)) libraryFunc(visaLibrary, "viOpenDefaultRM"); + viOpen = (ViStatus (*)(ViSession sesn, ViRsrc name, ViAccessMode mode, ViUInt32 timeout, ViPSession vi)) libraryFunc(visaLibrary, "viOpen"); + viClose = (ViStatus (*)(ViObject vi)) libraryFunc(visaLibrary, "viClose"); + viPrintf = (ViStatus (*) (ViSession vi, ViString writeFmt, ...)) libraryFunc(visaLibrary, "viPrintf"); + viScanf = (ViStatus (*) (ViSession vi, ViString writeFmt, ...)) libraryFunc(visaLibrary, "viScanf"); + viFindRsrc = (ViStatus (*) (ViSession vi, ViString expr, ViPFindList li, ViPUInt32 retCnt, ViChar desc[])) libraryFunc(visaLibrary, "viFindRsrc"); + viFindNext = (ViStatus (*) (ViSession vi, ViChar desc[])) libraryFunc(visaLibrary, "viFindNext"); + + if (viOpenDefaultRM && viOpen && viClose && viPrintf && viFindRsrc && viFindNext) { + m_available = true; + } + } + else + { + qDebug() << "VISA::VISA: Unable to load " << visaName; + } +} + +ViSession VISA::openDefault() +{ + if (isAvailable() && (m_defaultRM == 0)) + { + viOpenDefaultRM(&m_defaultRM); + return m_defaultRM; + } + else + { + return m_defaultRM; + } +} + +void VISA::closeDefault() +{ + if (isAvailable()) + { + viClose(m_defaultRM); + m_defaultRM = 0; + } +} + +ViSession VISA::open(const QString& device) +{ + ViSession session; + if (isAvailable()) + { + if (VI_SUCCESS == viOpen(m_defaultRM, device.toLatin1().data(), VI_NULL, VI_NULL, &session)) + { + qDebug() << "VISA::open: Opened VISA device: " << device; + return session; + } + else + { + qDebug() << "VISA::open: Failed to open VISA device: " << device; + } + } + return 0; +} + +void VISA::close(ViSession session) +{ + if (isAvailable()) { + viClose(session); + } +} + +QStringList VISA::processCommands(ViSession session, const QString& commands, bool *error) +{ + QStringList results; + + if (isAvailable()) + { + QStringList list = commands.split("\n"); + ViStatus status; + + if (error) { + *error = false; + } + for (int i = 0; i < list.size(); i++) + { + QString command = list[i].trimmed(); + if (!command.isEmpty() && !command.startsWith("#")) // Allow # to comment out lines + { + if (m_debugIO) { + qDebug() << "VISA ->: " << command; + } + QByteArray bytes = QString("%1\n").arg(command).toLatin1(); + char *cmd = bytes.data(); + status = viPrintf(session, cmd); + if (error && status) { + *error = true; + } + if (command.contains("?")) + { + char buf[1024] = ""; + char format[] = "%t"; + status = viScanf(session, format, buf); + if (error && status) { + *error = true; + } + results.append(buf); + if (m_debugIO) { + qDebug() << "VISA <-: " << QString(buf).trimmed(); + } + } + } + } + } + else if (error) + { + *error = true; + } + return results; +} + +QStringList VISA::findResources() +{ + QStringList resources; + + if (isAvailable()) + { + ViChar rsrc[VI_FIND_BUFLEN]; + ViFindList list; + ViRsrc matches = rsrc; + ViUInt32 nMatches = 0; + ViChar expr[] = "?*INSTR"; + + if (VI_SUCCESS == viFindRsrc(m_defaultRM, expr, &list, &nMatches, matches)) + { + if (nMatches > 0) + { + resources.append(QString(rsrc)); + while (VI_SUCCESS == viFindNext(list, matches)) + { + resources.append(QString(rsrc)); + } + } + } + } + return resources; +} + +bool VISA::identification(ViSession session, QString &manufacturer, QString &model, QString &serialNumber, QString &revision) +{ + if (isAvailable()) + { + QStringList result = processCommands(session, "*IDN?"); + if ((result.size() == 1) && (!result[0].isEmpty())) + { + QStringList details = result[0].trimmed().split(','); + manufacturer = details[0]; + // Some serial devices (ASRLn) loop back the the command if not connected + if (manufacturer == "*IDN?") { + return false; + } + if (details.size() >= 2) { + model = details[1]; + } + if (details.size() >= 3) { + serialNumber = details[2]; + } + if (details.size() >= 4) { + revision = details[3]; + } + qDebug() << "VISA::identification: " + << "manufacturer: " << manufacturer + << "model: " << model + << "serialNumber: " << serialNumber + << "revision: " << revision; + return true; + } + } + return false; +} + +// Filter is a list of resources not to try to open +QList VISA::instruments(QRegularExpression *filter) +{ + QList instruments; + + if (isAvailable()) + { + QStringList resourceList = findResources(); + + for (auto const &resource : resourceList) + { + if (filter) + { + if (filter->match(resource).hasMatch()) { + continue; + } + } + ViSession session = open(resource); + if (session) + { + Instrument instrument; + QString manufacturer, model, serialNumber, revision; + if (identification(session, instrument.m_manufacturer, instrument.m_model, instrument.m_serial, instrument.m_revision)) + { + instrument.m_resource = resource; + instruments.append(instrument); + } + close(session); + } + } + } + return instruments; +} + +#ifdef _MSC_VER + +void *VISA::libraryOpen(const char *filename) +{ + HMODULE module; + module = LoadLibraryA ((LPCSTR)filename); + return module; +} + +void *VISA::libraryFunc(void *library, const char *function) +{ + return GetProcAddress ((HMODULE)library, function); +} + +#else + +void *VISA::libraryOpen(const char *filename) +{ + return dlopen (filename, RTLD_LAZY); +} + +void *VISA::libraryFunc(void *library, const char *function) +{ + return dlsym (library, function); +} + +#endif diff --git a/android/app/src/main/cpp/util/visa.h b/android/app/src/main/cpp/util/visa.h new file mode 100644 index 0000000..5beeb2a --- /dev/null +++ b/android/app/src/main/cpp/util/visa.h @@ -0,0 +1,105 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_VISA_H +#define INCLUDE_VISA_H + +// Minimal implementation of VISA specification (just the bits we need so far) +// https://www.ivifoundation.org/docs/vpp432_2016-02-26.pdf + +#include "export.h" + +#include + +class QRegularExpression; + +typedef char ViChar; +typedef ViChar * ViPChar; +typedef signed long ViInt32; +typedef unsigned long ViUInt32; +typedef ViPChar ViString; + +typedef ViInt32 ViStatus; +typedef ViUInt32 ViObject; +typedef ViObject ViSession; +typedef ViSession * ViPSession; +typedef ViString ViRsrc; +typedef ViUInt32 ViAccessMode; +typedef ViUInt32 * ViPUInt32; +typedef ViObject ViFindList; +typedef ViFindList * ViPFindList; + +#define VI_SUCCESS 0 +#define VI_TRUE 1 +#define VI_FALSE 0 +#define VI_NULL 0 +#define VI_FIND_BUFLEN 256 + +// We dynamically load the visa dll, as most users probably do not have it +// Note: Can't seem to call viOpenDefaultRM/viClose in constructor / destructor of global instance +class SDRBASE_API VISA { +public: + + struct Instrument { + QString m_resource; + QString m_manufacturer; + QString m_model; + QString m_serial; + QString m_revision; + }; + + // Default session + ViSession m_defaultRM; + // Function pointers to VISA API for direct calls + ViStatus (*viOpenDefaultRM) (ViPSession vi); + ViStatus (*viOpen) (ViSession sesn, ViRsrc name, ViAccessMode mode, ViUInt32 timeout, ViPSession vi); + ViStatus (*viClose) (ViObject vi); + ViStatus (*viPrintf) (ViSession vi, ViString writeFmt, ...); + ViStatus (*viScanf) (ViSession vi, ViString readFmt, ...); + ViStatus (*viFindRsrc) (ViSession vi, ViString expr, ViPFindList li, ViPUInt32 retCnt, ViChar desc[]); + ViStatus (*viFindNext) (ViSession vi, ViChar desc[]); + + VISA(); + + ViSession openDefault(); + void closeDefault(); + ViSession open(const QString& device); + void close(ViSession session); + QStringList processCommands(ViSession session, const QString& commands, bool *error=nullptr); + QStringList findResources(); + bool identification(ViSession session, QString &manufacturer, QString &model, QString &serialNumber, QString &revision); + QList instruments(QRegularExpression *filter); + void setDebugIO(bool debugIO) { m_debugIO = debugIO; } + + // Is the VISA library available + bool isAvailable() const + { + return m_available; + } + +private: + bool m_available; + bool m_debugIO; + +protected: + void *visaLibrary; + + void *libraryOpen(const char *filename); + void *libraryFunc(void *library, const char *function); +}; + +#endif // INCLUDE_VISA_H diff --git a/android/app/src/main/cpp/util/vlftransmitters.cpp b/android/app/src/main/cpp/util/vlftransmitters.cpp new file mode 100644 index 0000000..8b8b735 --- /dev/null +++ b/android/app/src/main/cpp/util/vlftransmitters.cpp @@ -0,0 +1,116 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "util/csv.h" + +#include "vlftransmitters.h" + +// https://sidstation.loudet.org/stations-list-en.xhtml +// https://core.ac.uk/download/pdf/224769021.pdf -- Table 1 +// GQD/GQZ callsigns: https://groups.io/g/VLF/message/19212?p=%2C%2C%2C20%2C0%2C0%2C0%3A%3Arecentpostdate%2Fsticky%2C%2C19.6%2C20%2C2%2C0%2C38924431 +QList VLFTransmitters::m_transmitters = { + {QStringLiteral("JXN"), 16400, 66.974353, 13.873617, -1}, // Novik, Norway (Only transmits 6 times a day) + {QStringLiteral("VTX2"), 17000, 8.387015, 77.752762, -1}, // South Vijayanarayanam, India + {QStringLiteral("RDL"), 18100, 44.773333, 39.547222, -1}, // Krasnodar, Russia (Transmits short bursts, possibly FSK) + {QStringLiteral("GQD"), 19580, 54.911643, -3.278456, 100}, // Anthorn, UK, Often referred to as GBZ + {QStringLiteral("NWC"), 19800, -21.816325, 114.16546, 1000}, // Exmouth, Aus + {QStringLiteral("ICV"), 20270, 40.922946, 9.731881, 50}, // Isola di Tavolara, Italy (Can be distorted on 3D map if terrain used) + {QStringLiteral("FTA"), 20900, 48.544632, 2.579429, 50}, // Sainte-Assise, France (Satellite imagary obfuscated) + {QStringLiteral("NPM"), 21400, 21.420166, -158.151140, 600}, // Pearl Harbour, Lualuahei, USA (Not seen?) + {QStringLiteral("HWU"), 21750, 46.713129, 1.245248, 200}, // Rosnay, France + {QStringLiteral("GQZ"), 22100, 54.731799, -2.883033, 100}, // Skelton, UK (GVT in paper) + {QStringLiteral("DHO38"), 23400, 53.078900, 7.615000, 300}, // Rhauderfehn, Germany - Off air 7-8 UTC + {QStringLiteral("NAA"), 24000, 44.644506, -67.284565, 1000}, // Cutler, Maine, USA + {QStringLiteral("TBB"), 26700, 37.412725, 27.323342, -1}, // Bafa, Turkey + {QStringLiteral("TFK/NRK"), 37500, 63.850365, -22.466773, 100}, // Grindavik, Iceland + {QStringLiteral("SRC"), 40400, 57.120328, 16.153083, -1}, // Grimeton, Sweden + {QStringLiteral("NSY"), 45900, 37.125660, 14.436416, -1}, // Niscemi, Italy + {QStringLiteral("SXA"), 49000, 38.145155, 24.019718, -1}, // Marathon, Greece + {QStringLiteral("GYW1"), 51950, 57.617463, -1.887589, -1}, // Crimond, UK + {QStringLiteral("FUE"), 65800, 48.637673, -4.350758, -1}, // Kerlouan, France +}; + +QHash VLFTransmitters::m_callsignHash; + +VLFTransmitters::Init VLFTransmitters::m_init; + +VLFTransmitters::Init::Init() +{ + // Get directory to store app data in + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + // First dir is writable + QString dir = locations[0]; + + // Try reading transmitters from .csv file + QString filename = QString("%1/%2/%3/vlftransmitters.csv").arg(dir).arg(COMPANY).arg(APPLICATION_NAME); // Because this method is called before main(), we need to add f4exb/SDRangel + QFile file(filename); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QTextStream in(&file); + + QString error; + QHash colIndexes = CSV::readHeader(in, { + QStringLiteral("Callsign"), + QStringLiteral("Frequency"), + QStringLiteral("Latitude"), + QStringLiteral("Longitude"), + QStringLiteral("Power") + }, error); + if (error.isEmpty()) + { + QStringList cols; + int callsignCol = colIndexes.value(QStringLiteral("Callsign")); + int frequencyCol = colIndexes.value(QStringLiteral("Frequency")); + int latitudeCol = colIndexes.value(QStringLiteral("Latitude")); + int longitudeCol = colIndexes.value(QStringLiteral("Longitude")); + int powerCol = colIndexes.value(QStringLiteral("Power")); + int maxCol = std::max({callsignCol, frequencyCol, latitudeCol, longitudeCol, powerCol}); + + m_transmitters.clear(); // Replace builtin list + + while(CSV::readRow(in, &cols)) + { + if (cols.size() > maxCol) + { + Transmitter transmitter; + + transmitter.m_callsign = cols[callsignCol]; + transmitter.m_frequency = cols[frequencyCol].toLongLong(); + transmitter.m_latitude = cols[latitudeCol].toFloat(); + transmitter.m_longitude = cols[longitudeCol].toFloat(); + transmitter.m_power = cols[powerCol].toInt(); + + m_transmitters.append(transmitter); + } + } + } + else + { + qWarning() << filename << "did not contain expected headers."; + } + } + + // Create hash table for faster searching + for (const auto& transmitter : VLFTransmitters::m_transmitters) { + VLFTransmitters::m_callsignHash.insert(transmitter.m_callsign, &transmitter); + } +} diff --git a/android/app/src/main/cpp/util/vlftransmitters.h b/android/app/src/main/cpp/util/vlftransmitters.h new file mode 100644 index 0000000..6c8c2d2 --- /dev/null +++ b/android/app/src/main/cpp/util/vlftransmitters.h @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_VLFTRANSMITTERS_H +#define INCLUDE_VLFTRANSMITTERS_H + +#include +#include +#include + +#include "export.h" + +// List of VLF transmitters +// Built-in list can be overridden by user supplied vlftransmitters.csv file, that is read at startup, from the app data dir +class SDRBASE_API VLFTransmitters +{ + +public: + + struct Transmitter { + QString m_callsign; + qint64 m_frequency; // In Hz + float m_latitude; + float m_longitude; + int m_power; // In kW + }; + + static QList m_transmitters; + + static QHash m_callsignHash; + +private: + + friend struct Init; + struct Init { + Init(); + }; + static Init m_init; + +}; + +#endif /* VLFTransmitters */ diff --git a/android/app/src/main/cpp/util/waypoints.cpp b/android/app/src/main/cpp/util/waypoints.cpp new file mode 100644 index 0000000..00835e8 --- /dev/null +++ b/android/app/src/main/cpp/util/waypoints.cpp @@ -0,0 +1,141 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "waypoints.h" +#include "csv.h" + +QHash *Waypoint::readCSV(const QString &filename) +{ + QHash *waypoints = new QHash(); + QFile file(filename); + + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QTextStream in(&file); + QString error; + + QStringList cols; + while(CSV::readRow(in, &cols)) + { + Waypoint *waypoint = new Waypoint(); + waypoint->m_name = cols[0]; + waypoint->m_latitude = cols[1].toFloat(); + waypoint->m_longitude = cols[2].toFloat(); + waypoints->insert(waypoint->m_name, waypoint); + } + + file.close(); + } + else + { + qDebug() << "Waypoint::readCSV: Could not open " << filename << " for reading."; + } + return waypoints; +} + +QSharedPointer> Waypoints::m_waypoints; + +QDateTime Waypoints::m_waypointsModifiedDateTime; + +Waypoints::Waypoints(QObject *parent) : + QObject(parent) +{ + connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &Waypoints::downloadFinished); +} + +Waypoints::~Waypoints() +{ + disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &Waypoints::downloadFinished); +} + +QString Waypoints::getDataDir() +{ + // Get directory to store app data in + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + // First dir is writable + return locations[0]; +} + +QString Waypoints::getWaypointsFilename() +{ + return getDataDir() + "/" + "waypoints.csv"; +} + +void Waypoints::downloadWaypoints() +{ + QString filename = getWaypointsFilename(); + QString urlString = WAYPOINTS_URL; + QUrl dbURL(urlString); + qDebug() << "Waypoints::downloadWaypoints: Downloading " << urlString; + emit downloadingURL(urlString); + m_dlm.download(dbURL, filename); +} + +void Waypoints::downloadFinished(const QString& filename, bool success) +{ + if (!success) { + qDebug() << "Waypoints::downloadFinished: Failed: " << filename; + } + + if (filename == getWaypointsFilename()) + { + emit downloadWaypointsFinished(); + } + else + { + qDebug() << "Waypoints::downloadFinished: Unexpected filename: " << filename; + emit downloadError(QString("Unexpected filename: %1").arg(filename)); + } +} + +// Read waypoints +QHash *Waypoints::readWaypoints() +{ + return Waypoint::readCSV(getWaypointsFilename()); +} + +QSharedPointer> Waypoints::getWaypoints() +{ + QDateTime filesDateTime = getWaypointsModifiedDateTime(); + + if (!m_waypoints || (filesDateTime > m_waypointsModifiedDateTime)) + { + // Using shared pointer, so old object, if it exists, will be deleted, when no longer used + m_waypoints = QSharedPointer>(readWaypoints()); + m_waypointsModifiedDateTime = filesDateTime; + } + return m_waypoints; +} + +// Gets the date and time the waypoint file was most recently modified +QDateTime Waypoints::getWaypointsModifiedDateTime() +{ + QFileInfo fileInfo(getWaypointsFilename()); + return fileInfo.lastModified(); +} + +// Find a waypoint by name +const Waypoint *Waypoints::findWayPoint(const QString& name) +{ + if (m_waypoints->contains(name)) { + return m_waypoints->value(name); + } else { + return nullptr; + } +} diff --git a/android/app/src/main/cpp/util/waypoints.h b/android/app/src/main/cpp/util/waypoints.h new file mode 100644 index 0000000..3df2c6e --- /dev/null +++ b/android/app/src/main/cpp/util/waypoints.h @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021-2024 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_WAYPOINTS_H +#define INCLUDE_WAYPOINTS_H + +#include +#include +#include + +#include +#include + +#include "export.h" + +#include "util/units.h" +#include "util/httpdownloadmanager.h" + +#define WAYPOINTS_URL "https://github.com/srcejon/aviationwaypoints/waypoints.csv" + +// Aviation waypoints +struct SDRBASE_API Waypoint { + + QString m_name; // Typically 5 characters + float m_latitude; + float m_longitude; + + static QHash *readCSV(const QString &filename); +}; + +class SDRBASE_API Waypoints : public QObject { + Q_OBJECT + +public: + Waypoints(QObject* parent = nullptr); + ~Waypoints(); + + void downloadWaypoints(); + + static const Waypoint* findWayPoint(const QString& name); + static QSharedPointer> getWaypoints(); + +private: + HttpDownloadManager m_dlm; + + static QSharedPointer> m_waypoints; + + static QDateTime m_waypointsModifiedDateTime; + + static QHash *readWaypoints(); + + static QString getDataDir(); + static QString getWaypointsFilename(); + static QDateTime getWaypointsModifiedDateTime(); + +public slots: + void downloadFinished(const QString& filename, bool success); + +signals: + void downloadingURL(const QString& url); + void downloadError(const QString& error); + void downloadWaypointsFinished(); + +}; + +#endif // INCLUDE_WAYPOINTS_H diff --git a/android/app/src/main/cpp/util/weather.cpp b/android/app/src/main/cpp/util/weather.cpp new file mode 100644 index 0000000..83337ca --- /dev/null +++ b/android/app/src/main/cpp/util/weather.cpp @@ -0,0 +1,151 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "weather.h" + +#include +#include +#include +#include +#include +#include + +Weather::Weather() +{ + connect(&m_timer, &QTimer::timeout, this, &Weather::update); +} + +Weather* Weather::create(const QString& apiKey, const QString& service) +{ + if (service == "openweathermap.org") + { + if (!apiKey.isEmpty()) + { + return new OpenWeatherMap(apiKey); + } + else + { + qDebug() << "Weather::create: An API key is required for: " << service; + return nullptr; + } + } + else + { + qDebug() << "Weather::create: Unsupported service: " << service; + return nullptr; + } +} + +void Weather::getWeatherPeriodically(float latitude, float longitude, int periodInMins) +{ + m_latitude = latitude; + m_longitude = longitude; + m_timer.setInterval(periodInMins*60*1000); + m_timer.start(); + update(); +} + +void Weather::update() +{ + getWeather(m_latitude, m_longitude); +} + +OpenWeatherMap::OpenWeatherMap(const QString& apiKey) : + m_apiKey(apiKey) +{ + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &OpenWeatherMap::handleReply + ); +} + +OpenWeatherMap::~OpenWeatherMap() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &OpenWeatherMap::handleReply + ); + delete m_networkManager; +} + +void OpenWeatherMap::getWeather(float latitude, float longitude) +{ + QUrl url(QString("http://api.openweathermap.org/data/2.5/weather")); + QUrlQuery query; + query.addQueryItem("lat", QString::number(latitude)); + query.addQueryItem("lon", QString::number(longitude)); + query.addQueryItem("mode", "json"); + query.addQueryItem("units", "metric"); + query.addQueryItem("appid", m_apiKey); + url.setQuery(query); + + m_networkManager->get(QNetworkRequest(url)); +} + +void OpenWeatherMap::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + QJsonDocument document = QJsonDocument::fromJson(reply->readAll()); + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("main"))) + { + QJsonValue val = obj.value(QStringLiteral("main")); + QJsonObject mainObj = val.toObject(); + float temp = NAN, pressure = NAN, humidity = NAN; + if (mainObj.contains(QStringLiteral("temp"))) { + temp = mainObj.value(QStringLiteral("temp")).toDouble(); + } + if (mainObj.contains(QStringLiteral("pressure"))) { + pressure = mainObj.value(QStringLiteral("pressure")).toDouble(); + } + if (mainObj.contains(QStringLiteral("humidity"))) { + humidity = mainObj.value(QStringLiteral("humidity")).toDouble(); + } + emit weatherUpdated(temp, pressure, humidity); + } + else + { + qDebug() << "OpenWeatherMap::handleReply: Object doesn't contain a main: " << obj; + } + } + else + { + qDebug() << "OpenWeatherMap::handleReply: Document is not an object: " << document; + } + } + else + { + qDebug() << "OpenWeatherMap::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "OpenWeatherMap::handleReply: reply is null"; + } +} diff --git a/android/app/src/main/cpp/util/weather.h b/android/app/src/main/cpp/util/weather.h new file mode 100644 index 0000000..62b8391 --- /dev/null +++ b/android/app/src/main/cpp/util/weather.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_WEATHER_H +#define INCLUDE_WEATHER_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +// Weather API wrapper +// Allows temperature, pressure and humidity to be obtained for a given latitude and longitude +// Currently supports openweathermap.org +class SDRBASE_API Weather : public QObject +{ + Q_OBJECT +protected: + Weather(); + +public: + static Weather* create(const QString& apiKey, const QString& service="openweathermap.org"); + + virtual void getWeather(float latitude, float longitude) = 0; + void getWeatherPeriodically(float latitude, float longitude, int periodInMins); + +public slots: + void update(); + +signals: + void weatherUpdated(float temperature, float pressure, float humidity); // Called when new data available. If no value is available, parameter will be NAN + +private: + QTimer m_timer; // Timer for periodic updates + float m_latitude; // Saved latitude for periodic updates + float m_longitude; // Saved longitude for periodic updates + +}; + +class SDRBASE_API OpenWeatherMap : public Weather { + Q_OBJECT +public: + + OpenWeatherMap(const QString& apiKey); + ~OpenWeatherMap(); + virtual void getWeather(float latitude, float longitude) override; + +private: + + QString m_apiKey; + QNetworkAccessManager *m_networkManager; + + +public slots: + void handleReply(QNetworkReply* reply); + +}; + +#endif /* INCLUDE_WEATHER_H */ diff --git a/android/app/src/main/cpp/util/whittakereilers.cpp b/android/app/src/main/cpp/util/whittakereilers.cpp new file mode 100644 index 0000000..c04c42c --- /dev/null +++ b/android/app/src/main/cpp/util/whittakereilers.cpp @@ -0,0 +1,233 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2025 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "whittakereilers.h" + +WhittakerEilers::WhittakerEilers() : + v1a(nullptr), + v2a(nullptr), + da(nullptr), + dtd(nullptr), + ca(nullptr), + za(nullptr), + zb(nullptr), + b(nullptr), + m_length(0) +{ +} + +WhittakerEilers::~WhittakerEilers() +{ + dealloc(); +} + +void WhittakerEilers::alloc(int length) +{ + v1a = new double[length]; + v2a = new double[length]; + da = new double[length * 3]; + dtd = new double[length * 3]; + ca = new double[length * 3]; + za = new double[length]; + zb = new double[length]; + b = new double[length]; + m_length = length; +} + +void WhittakerEilers::dealloc() +{ + delete v1a; + delete v2a; + delete da; + delete dtd; + delete ca; + delete za; + delete zb; + delete b; + m_length = 0; +} + +void WhittakerEilers::filter(const double *xi, double *yi, const double *w, const int length, const double lambda) +{ + int i; + int j; + int k; + const int m = length; + + if (m_length < length) + { + dealloc(); + alloc(length); + } + + for (i = 0; i < m - 1; i++) { + v1a[i] = 1.0 / (xi[i + 1] - xi[i]); + } + v1a[m - 1] = 0.0; + for (i = 0; i < m - 2; i++) { + v2a[i] = 1.0 / (xi[i + 2] - xi[i]); + } + v2a[m - 1] = 0.0; + v2a[m - 2] = 0.0; + + //Wa = w; + // D1 = V1 * diff(I) + for (i = 0; i < m - 1; i++) { + da[i*3] = -v1a[i]; + da[i*3+1] = v1a[i]; + } + + // D2 = V2 * diff(D1) + for (i = 0; i < m - 2; i++) { + da[i*3] = v2a[i] * -da[i*3]; + da[i*3+1] = v2a[i] * (da[(i+1)*3] - da[i*3+1]); + da[i*3+2] = v2a[i] * da[(i+1)*3+1]; + } + for (i = 1; i <= 6; i++) { + da[m * 3 - i] = 0; + } + + dtd[0 * 3] = lambda * da[0 * 3] * da[0 * 3]; + dtd[0 * 3 + 1] = lambda * da[0 * 3] * da[0 * 3 + 1]; + dtd[0 * 3 + 2] = lambda * da[0 * 3] * da[0 * 3 + 2]; + + dtd[1 * 3] = lambda * (da[0 * 3 + 1] * da[0 * 3 + 1] + da[1 * 3] * da[1 * 3]); + dtd[1 * 3 + 1] = lambda * (da[0 * 3 + 1] * da[0 * 3 + 2] + da[1 * 3] * da[1 * 3 + 1]); + dtd[1 * 3 + 2] = lambda * (da[1 * 3] * da[1 * 3 + 2]); + + for (int row = 2; row < m; row++) { + dtd[row * 3] = lambda * (da[(row - 2) * 3 + 2] * da[(row - 2) * 3 + 2] + da[(row - 1) * 3 + 1] * da[(row - 1) * 3 + 1] + da[row * 3] * da[row * 3]); + dtd[row * 3 + 1] = lambda * (da[(row - 1) * 3 + 1] * da[(row - 1) * 3 + 2] + da[(row) * 3] * da[(row) * 3 + 1]); + dtd[row * 3 + 2] = lambda * (da[(row) * 3] * da[(row) * 3 + 2]); + } + + // Add in W + for (i = 0; i < m; i++) { + dtd[i * 3] = w[i] + dtd[i * 3]; + } + + // Cholesky Decomposition + + i = 1; + j = i - 1; + ca[j * 3] = sqrt(dtd[j * 3]); + + i = 2; + j = i - 2; + ca[j * 3 + 1] = 1.0 / ca[j * 3] * dtd[j * 3 + 1]; + + i = 2; + j = i - 1; + k = i - 2; + double sum = ca[k * 3 + 1] * ca[k * 3 + 1]; + ca[j * 3] = sqrt(dtd[j * 3] - sum); + + for (i = 3; i <= m; i++) { + j = i - 3; + ca[j * 3 + 2] = 1.0 / ca[j * 3] * dtd[j * 3 + 2]; + + k = i - 3; + sum = ca[k * 3 + 2] * ca[k * 3 + 1]; + j = i - 2; + ca[j * 3 + 1] = 1.0 / ca[j * 3] * (dtd[j * 3 + 1] - sum); + + k = i - 3; + sum = ca[k * 3 + 2] * ca[k * 3 + 2]; + k = i - 2; + sum = sum + ca[k * 3 + 1] * ca[k * 3 + 1]; + j = i - 1; + ca[j * 3] = sqrt(dtd[j * 3] - sum); + } + ca[m * 3 - 1] = 0; + ca[m * 3 - 2] = 0; + ca[m * 3 - 4] = 0; + + // % Forward substitution(C' \ (w .* y)) + for (i = 0; i < m; i++) { + b[i] = w[i] * yi[i]; + } + + za[0] = b[0] / ca[0]; + + sum = ca[0 * 3 + 1] * za[0]; + za[1] = (b[1] - sum) / ca[1 * 3]; + + for (i = 3; i <= m; i++) { + sum = ca[(i - 3) * 3 + 2] * za[i - 3] + ca[(i - 2) * 3 + 1] * za[i - 2]; + za[i - 1] = (b[i - 1] - sum) / ca[(i - 1) * 3]; + } + + // Backward substituion C \ (C' \ (w .* y)); + b = za; + + i = m - 1; + zb[i] = b[i] / ca[i * 3]; + + i = m - 2; + sum = ca[i * 3 + 1] * zb[i + 1]; + zb[i] = (b[i] - sum) / ca[i * 3]; + + for (i = m - 2; i >= 1; i--) { + sum = ca[(i - 1) * 3 + 2] * zb[i + 1] + ca[(i - 1) * 3 + 1] * zb[i]; + zb[i-1] = (b[i - 1] - sum) / ca[(i - 1) * 3]; + } + + if (std::isnan(zb[0])) { + qDebug() << "lambda" << lambda; + for (int i = 0; i < m; i++) { + qDebug() << "xi[" << i << "]" << qSetRealNumberPrecision(12) << xi[i]; + } + for (int i = 0; i < m; i++) { + qDebug() << "yi[" << i << "]" << qSetRealNumberPrecision(12) << yi[i]; + } + for (int i = 0; i < m; i++) { + qDebug() << "w[" << i << "]" << qSetRealNumberPrecision(12) << w[i]; + } + for (int i = 0; i < m; i++) { + qDebug() << "v1a[" << i << "]" << qSetRealNumberPrecision(12) << v1a[i]; + } + for (int i = 0; i < m; i++) { + qDebug() << "v2a[" << i << "]" << qSetRealNumberPrecision(12) << v2a[i]; + } + for (int i = 0; i < m * 3; i++) { + qDebug() << "da[" << i << "]" << qSetRealNumberPrecision(12) << da[i]; + } + for (int i = 0; i < m * 3; i++) { + qDebug() << "dtd[" << i << "]" << qSetRealNumberPrecision(12) << dtd[i]; + } + for (int i = 0; i < m * 3; i++) { + qDebug() << "ca[" << i << "]" << qSetRealNumberPrecision(12) << ca[i]; + } + for (int i = 0; i < m; i++) { + qDebug() << "za[" << i << "]" << qSetRealNumberPrecision(12) << za[i]; + } + for (int i = 0; i < m; i++) { + qDebug() << "zb[" << i << "]" << qSetRealNumberPrecision(12) << zb[i]; + } + // Don't put NaNs in output + return; + } + + // Copy result back to input + for (i = 0; i < m; i++) { + yi[i] = zb[i]; + } + + /*for (i = 0; i < m; i++) { + qDebug() << "zb[" << i << "]=" << zb[i]; + }*/ +} diff --git a/android/app/src/main/cpp/util/whittakereilers.h b/android/app/src/main/cpp/util/whittakereilers.h new file mode 100644 index 0000000..93d94c2 --- /dev/null +++ b/android/app/src/main/cpp/util/whittakereilers.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2025 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_WHITTAKEREILERS_H +#define INCLUDE_WHITTAKEREILERS_H + +#include + +#include "export.h" + +// Whittaker-Eilers smoother +// https://pubs.acs.org/doi/10.1021/ac034173t +class SDRBASE_API WhittakerEilers +{ + +public: + WhittakerEilers(); + ~WhittakerEilers(); + void filter(const double *xi, double *yi, const double *w, const int length, const double lambda=2000); + +private: + void alloc(int length); + void dealloc(); + + double *v1a; + double *v2a; + double *da; + double *dtd; + double *ca; + double *za; + double *zb; + double *b; + + int m_length; + +}; + +#endif /* INCLUDE_WHITTAKEREILERS_H */ diff --git a/android/app/src/main/kotlin/org/noxylva/lbjconsole/flutter/MainActivity.kt b/android/app/src/main/kotlin/org/noxylva/lbjconsole/flutter/MainActivity.kt index 625fb1b..2ae3f81 100644 --- a/android/app/src/main/kotlin/org/noxylva/lbjconsole/flutter/MainActivity.kt +++ b/android/app/src/main/kotlin/org/noxylva/lbjconsole/flutter/MainActivity.kt @@ -1,5 +1,11 @@ package org.noxylva.lbjconsole.flutter import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine -class MainActivity: FlutterActivity() +class MainActivity: FlutterActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + RtlTcpChannelHandler.registerWith(flutterEngine) + } +} diff --git a/android/app/src/main/kotlin/org/noxylva/lbjconsole/flutter/RtlTcpChannelHandler.kt b/android/app/src/main/kotlin/org/noxylva/lbjconsole/flutter/RtlTcpChannelHandler.kt new file mode 100644 index 0000000..b6dd5b8 --- /dev/null +++ b/android/app/src/main/kotlin/org/noxylva/lbjconsole/flutter/RtlTcpChannelHandler.kt @@ -0,0 +1,118 @@ +package org.noxylva.lbjconsole.flutter + +import android.os.Handler +import android.os.Looper +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.EventChannel +import java.nio.charset.Charset + +class RtlTcpChannelHandler : EventChannel.StreamHandler { + + private external fun startClientAsync(host: String, port: String) + private external fun pollMessages(): String + private external fun nativeStopClient() + private external fun getSignalStrength(): Double + private external fun isConnected(): Boolean + + private val handler = Handler(Looper.getMainLooper()) + private var eventSink: EventChannel.EventSink? = null + private var lastConnectedState: Boolean = false + + companion object { + private const val METHOD_CHANNEL_NAME = "org.noxylva.lbjconsole/rtl_tcp_method" + private const val EVENT_CHANNEL_NAME = "org.noxylva.lbjconsole/rtl_tcp_event" + + init { + System.loadLibrary("railwaypagerdemod") + } + + fun registerWith(flutterEngine: FlutterEngine) { + val handler = RtlTcpChannelHandler() + + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL_NAME).setMethodCallHandler { + call, result -> + when (call.method) { + "connect" -> { + val host = call.argument("host")!! + val port = call.argument("port")!! + android.util.Log.d("RTL-TCP", "conn_req: $host:$port") + try { + handler.startClientAsync(host, port) + android.util.Log.d("RTL-TCP", "conn_sent") + result.success("Connect command sent.") + } catch (e: Exception) { + android.util.Log.e("RTL-TCP", "conn_fail", e) + result.error("CONNECT_ERROR", "连接失败: ${e.message}", null) + } + } + "disconnect" -> { + android.util.Log.d("RTL-TCP", "disc_req") + handler.nativeStopClient() + result.success("Disconnect command completed.") + } + else -> result.notImplemented() + } + } + EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL_NAME).setStreamHandler(handler) + } + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + android.util.Log.d("RTL-TCP", "evt_listen") + this.eventSink = events + startPolling() + } + + override fun onCancel(arguments: Any?) { + android.util.Log.d("RTL-TCP", "evt_cancel") + handler.removeCallbacksAndMessages(null) + this.eventSink = null + } + + private fun startPolling() { + handler.post(object : Runnable { + override fun run() { + if (eventSink == null) { + android.util.Log.w("RTL-TCP", "evt_null"); + return; + } + + val connected = isConnected() + val strength = getSignalStrength() + val logs = pollMessages() + val regex = "\\[MSG\\]\\s*(\\d+)\\|(-?\\d+)\\|(.*)".toRegex() + + if (connected != lastConnectedState || connected) { + val statusMap = mutableMapOf() + statusMap["connected"] = connected + statusMap["magsqRaw"] = strength + eventSink?.success(statusMap) + lastConnectedState = connected + } + + if (logs.isNotEmpty()) { + regex.findAll(logs).forEach { match -> + try { + val dataMap = mutableMapOf() + dataMap["address"] = match.groupValues[1] + dataMap["func"] = match.groupValues[2] + + val gbkBytes = match.groupValues[3].toByteArray(Charsets.ISO_8859_1) + val utf8String = String(gbkBytes, Charset.forName("GBK")) + dataMap["numeric"] = utf8String + + dataMap["magsqRaw"] = strength + + eventSink?.success(dataMap) + } catch (e: Exception) { + android.util.Log.e("RTL-TCP", "decode_fail", e) + } + } + } + + handler.postDelayed(this, 200) + } + }) + } +} \ No newline at end of file diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 0c6cb67..b07d731 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:async'; +import 'dart:developer' as developer; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:lbjconsole/models/merged_record.dart'; import 'package:lbjconsole/models/train_record.dart'; @@ -13,15 +14,25 @@ import 'package:lbjconsole/services/ble_service.dart'; import 'package:lbjconsole/services/database_service.dart'; import 'package:lbjconsole/services/notification_service.dart'; import 'package:lbjconsole/services/background_service.dart'; +import 'package:lbjconsole/services/rtl_tcp_service.dart'; import 'package:lbjconsole/themes/app_theme.dart'; +import 'dart:convert'; class _ConnectionStatusWidget extends StatefulWidget { final BLEService bleService; + final RtlTcpService rtlTcpService; final DateTime? lastReceivedTime; + final DateTime? rtlTcpLastReceivedTime; + final bool rtlTcpEnabled; + final bool rtlTcpConnected; const _ConnectionStatusWidget({ required this.bleService, + required this.rtlTcpService, required this.lastReceivedTime, + required this.rtlTcpLastReceivedTime, + required this.rtlTcpEnabled, + required this.rtlTcpConnected, }); @override @@ -58,19 +69,32 @@ class _ConnectionStatusWidgetState extends State<_ConnectionStatusWidget> { @override Widget build(BuildContext context) { + final isRtlTcpMode = widget.rtlTcpEnabled; + final rtlTcpConnected = widget.rtlTcpConnected; + + final isConnected = isRtlTcpMode ? rtlTcpConnected : _isConnected; + final statusColor = isRtlTcpMode + ? (rtlTcpConnected ? Colors.green : Colors.red) + : (_isConnected ? Colors.green : Colors.red); + final statusText = isRtlTcpMode + ? (rtlTcpConnected ? '已连接' : '未连接') + : _deviceStatus; + + final lastReceivedTime = isRtlTcpMode ? widget.rtlTcpLastReceivedTime : widget.lastReceivedTime; + return Row( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.lastReceivedTime == null || !_isConnected) ...[ - Text(_deviceStatus, + if (lastReceivedTime == null || !isConnected) ...[ + Text(statusText, style: const TextStyle(color: Colors.white70, fontSize: 12)), ], _LastReceivedTimeWidget( - lastReceivedTime: widget.lastReceivedTime, - isConnected: _isConnected, + lastReceivedTime: lastReceivedTime, + isConnected: isConnected, ), ], ), @@ -79,7 +103,7 @@ class _ConnectionStatusWidgetState extends State<_ConnectionStatusWidget> { width: 8, height: 8, decoration: BoxDecoration( - color: _isConnected ? Colors.green : Colors.red, + color: statusColor, shape: BoxShape.circle, ), ), @@ -179,14 +203,23 @@ class _MainScreenState extends State with WidgetsBindingObserver { String _mapType = 'webview'; late final BLEService _bleService; + late final RtlTcpService _rtlTcpService; final NotificationService _notificationService = NotificationService(); + final DatabaseService _databaseService = DatabaseService.instance; StreamSubscription? _connectionSubscription; + StreamSubscription? _rtlTcpConnectionSubscription; StreamSubscription? _dataSubscription; + StreamSubscription? _rtlTcpDataSubscription; StreamSubscription? _lastReceivedTimeSubscription; + StreamSubscription? _rtlTcpLastReceivedTimeSubscription; StreamSubscription? _settingsSubscription; DateTime? _lastReceivedTime; + DateTime? _rtlTcpLastReceivedTime; bool _isHistoryEditMode = false; + bool _rtlTcpEnabled = false; + bool _rtlTcpConnected = false; + bool _isConnected = false; final GlobalKey _historyScreenKey = GlobalKey(); final GlobalKey _realtimeScreenKey = @@ -197,23 +230,47 @@ class _MainScreenState extends State with WidgetsBindingObserver { super.initState(); WidgetsBinding.instance.addObserver(this); _bleService = BLEService(); + _rtlTcpService = RtlTcpService(); _bleService.initialize(); + _loadRtlTcpSettings(); _initializeServices(); _checkAndStartBackgroundService(); + _setupConnectionListener(); _setupLastReceivedTimeListener(); _setupSettingsListener(); _loadMapType(); } Future _loadMapType() async { - final settings = await DatabaseService.instance.getAllSettings() ?? {}; + final settings = await DatabaseService.instance.getAllSettings(); if (mounted) { setState(() { - _mapType = settings['mapType']?.toString() ?? 'webview'; + _mapType = settings?['mapType']?.toString() ?? 'webview'; }); } } + void _loadRtlTcpSettings() async { + developer.log('rtl_tcp: load_settings'); + final settings = await _databaseService.getAllSettings(); + developer.log('rtl_tcp: settings_loaded: enabled=${(settings?['rtlTcpEnabled'] ?? 0) == 1}, host=${settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'}, port=${settings?['rtlTcpPort']?.toString() ?? '14423'}'); + if (mounted) { + setState(() { + _rtlTcpEnabled = (settings?['rtlTcpEnabled'] ?? 0) == 1; + _rtlTcpConnected = _rtlTcpService.isConnected; + }); + + if (_rtlTcpEnabled && !_rtlTcpConnected) { + final host = settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'; + final port = settings?['rtlTcpPort']?.toString() ?? '14423'; + developer.log('rtl_tcp: auto_connect'); + _connectToRtlTcp(host, port); + } else { + developer.log('rtl_tcp: skip_connect: enabled=$_rtlTcpEnabled, connected=$_rtlTcpConnected'); + } + } + } + Future _checkAndStartBackgroundService() async { final settings = await DatabaseService.instance.getAllSettings() ?? {}; final backgroundServiceEnabled = @@ -233,22 +290,92 @@ class _MainScreenState extends State with WidgetsBindingObserver { }); } }); + + _rtlTcpLastReceivedTimeSubscription = + _rtlTcpService.lastReceivedTimeStream.listen((time) { + if (mounted) { + if (_rtlTcpEnabled) { + setState(() { + _rtlTcpLastReceivedTime = time; + }); + } + } + }); } void _setupSettingsListener() { + developer.log('rtl_tcp: setup_listener'); _settingsSubscription = DatabaseService.instance.onSettingsChanged((settings) { - if (mounted && _currentIndex == 1) { - _realtimeScreenKey.currentState?.loadRecords(scrollToTop: false); + developer.log('rtl_tcp: settings_changed: enabled=${(settings?['rtlTcpEnabled'] ?? 0) == 1}, host=${settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'}, port=${settings?['rtlTcpPort']?.toString() ?? '14423'}'); + if (mounted) { + final rtlTcpEnabled = (settings?['rtlTcpEnabled'] ?? 0) == 1; + if (rtlTcpEnabled != _rtlTcpEnabled) { + setState(() { + _rtlTcpEnabled = rtlTcpEnabled; + }); + + if (rtlTcpEnabled) { + final host = settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'; + final port = settings?['rtlTcpPort']?.toString() ?? '14423'; + _connectToRtlTcp(host, port); + } else { + _rtlTcpConnectionSubscription?.cancel(); + _rtlTcpDataSubscription?.cancel(); + _rtlTcpLastReceivedTimeSubscription?.cancel(); + _rtlTcpService.disconnect(); + setState(() { + _rtlTcpConnected = false; + _rtlTcpLastReceivedTime = null; + }); + } + } + + if (_currentIndex == 1) { + _realtimeScreenKey.currentState?.loadRecords(scrollToTop: false); + } } }); } + void _setupConnectionListener() { + _connectionSubscription = _bleService.connectionStream.listen((connected) { + if (mounted) { + setState(() { + _isConnected = connected; + }); + } + }); + + _rtlTcpConnectionSubscription = _rtlTcpService.connectionStream.listen((connected) { + if (mounted) { + if (_rtlTcpEnabled) { + setState(() { + _rtlTcpConnected = connected; + }); + } + } + }); + } + + Future _connectToRtlTcp(String host, String port) async { + developer.log('rtl_tcp: connect: $host:$port'); + try { + await _rtlTcpService.connect(host: host, port: port); + developer.log('rtl_tcp: connect_req_sent'); + } catch (e) { + developer.log('rtl_tcp: connect_fail: $e'); + } + } + @override void dispose() { _connectionSubscription?.cancel(); + _rtlTcpConnectionSubscription?.cancel(); _dataSubscription?.cancel(); + _rtlTcpDataSubscription?.cancel(); _lastReceivedTimeSubscription?.cancel(); + _rtlTcpLastReceivedTimeSubscription?.cancel(); _settingsSubscription?.cancel(); WidgetsBinding.instance.removeObserver(this); super.dispose(); @@ -274,6 +401,18 @@ class _MainScreenState extends State with WidgetsBindingObserver { _realtimeScreenKey.currentState!.addNewRecord(record); } }); + + _rtlTcpDataSubscription = _rtlTcpService.dataStream.listen((record) { + developer.log('rtl_tcp: recv_data: train=${record.train}'); + developer.log('rtl_tcp: recv_json: ${jsonEncode(record.toJson())}'); + _notificationService.showTrainNotification(record); + if (_historyScreenKey.currentState != null) { + _historyScreenKey.currentState!.addNewRecord(record); + } + if (_realtimeScreenKey.currentState != null) { + _realtimeScreenKey.currentState!.addNewRecord(record); + } + }); } void _showConnectionDialog() { @@ -282,7 +421,7 @@ class _MainScreenState extends State with WidgetsBindingObserver { context: context, barrierDismissible: true, builder: (context) => - _PixelPerfectBluetoothDialog(bleService: _bleService), + _PixelPerfectBluetoothDialog(bleService: _bleService, rtlTcpEnabled: _rtlTcpEnabled), ).then((_) { _bleService.setAutoConnectBlocked(false); if (!_bleService.isManualDisconnect) { @@ -325,19 +464,26 @@ class _MainScreenState extends State with WidgetsBindingObserver { ), centerTitle: false, actions: [ - Row( - children: [ - _ConnectionStatusWidget( - bleService: _bleService, - lastReceivedTime: _lastReceivedTime, - ), - IconButton( - icon: const Icon(Icons.bluetooth, color: Colors.white), - onPressed: _showConnectionDialog, - ), - ], - ), - ], + Row( + children: [ + _ConnectionStatusWidget( + bleService: _bleService, + rtlTcpService: _rtlTcpService, + lastReceivedTime: _lastReceivedTime, + rtlTcpLastReceivedTime: _rtlTcpLastReceivedTime, + rtlTcpEnabled: _rtlTcpEnabled, + rtlTcpConnected: _rtlTcpConnected, + ), + IconButton( + icon: Icon( + _rtlTcpEnabled ? Icons.wifi : Icons.bluetooth, + color: Colors.white, + ), + onPressed: _showConnectionDialog, + ), + ], + ), + ], ); } @@ -418,6 +564,7 @@ class _MainScreenState extends State with WidgetsBindingObserver { SettingsScreen( onSettingsChanged: () { _loadMapType(); + _loadRtlTcpSettings(); }, ), ]; @@ -464,7 +611,8 @@ enum _ScanState { initial, scanning, finished } class _PixelPerfectBluetoothDialog extends StatefulWidget { final BLEService bleService; - const _PixelPerfectBluetoothDialog({required this.bleService}); + final bool rtlTcpEnabled; + const _PixelPerfectBluetoothDialog({required this.bleService, required this.rtlTcpEnabled}); @override State<_PixelPerfectBluetoothDialog> createState() => _PixelPerfectBluetoothDialogState(); @@ -477,13 +625,28 @@ class _PixelPerfectBluetoothDialogState StreamSubscription? _connectionSubscription; StreamSubscription? _lastReceivedTimeSubscription; DateTime? _lastReceivedTime; + StreamSubscription? _rtlTcpConnectionSubscription; + bool _rtlTcpConnected = false; @override void initState() { super.initState(); _connectionSubscription = widget.bleService.connectionStream.listen((_) { if (mounted) setState(() {}); }); - if (!widget.bleService.isConnected) { + + _rtlTcpConnectionSubscription = widget.bleService.rtlTcpService?.connectionStream.listen((connected) { + if (mounted) { + setState(() { + _rtlTcpConnected = connected; + }); + } + }); + + if (widget.rtlTcpEnabled && widget.bleService.rtlTcpService != null) { + _rtlTcpConnected = widget.bleService.rtlTcpService!.isConnected; + } + + if (!widget.bleService.isConnected && !widget.rtlTcpEnabled) { _startScan(); } } @@ -491,6 +654,7 @@ class _PixelPerfectBluetoothDialogState @override void dispose() { _connectionSubscription?.cancel(); + _rtlTcpConnectionSubscription?.cancel(); _lastReceivedTimeSubscription?.cancel(); super.dispose(); } @@ -536,13 +700,15 @@ class _PixelPerfectBluetoothDialogState Widget build(BuildContext context) { final isConnected = widget.bleService.isConnected; return AlertDialog( - title: const Text('蓝牙设备'), + title: Text(widget.rtlTcpEnabled ? 'RTL-TCP 模式' : '蓝牙设备'), content: SizedBox( width: double.maxFinite, child: SingleChildScrollView( - child: isConnected - ? _buildConnectedView(context, widget.bleService.connectedDevice) - : _buildDisconnectedView(context), + child: widget.rtlTcpEnabled + ? _buildRtlTcpView(context) + : (isConnected + ? _buildConnectedView(context, widget.bleService.connectedDevice) + : _buildDisconnectedView(context)), ), ), actions: [ @@ -605,6 +771,31 @@ class _PixelPerfectBluetoothDialogState ]); } + Widget _buildRtlTcpView(BuildContext context) { + final isConnected = _rtlTcpConnected; + final currentAddress = widget.bleService.rtlTcpService?.currentAddress ?? '未配置'; + + return Column(mainAxisSize: MainAxisSize.min, children: [ + Icon(Icons.wifi, size: 48, color: isConnected ? Colors.green : Colors.red), + const SizedBox(height: 16), + Text(isConnected ? '已连接' : '未连接', + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Text('$currentAddress', + style: TextStyle(color: isConnected ? Colors.green : Colors.grey)), + const SizedBox(height: 16), + if (_lastReceivedTime != null && isConnected) ...[ + _LastReceivedTimeWidget( + lastReceivedTime: _lastReceivedTime, + isConnected: isConnected, + ), + ], + ]); + } + Widget _buildDeviceListView() { return SizedBox( height: 200, diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 5645f48..45f8d20 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -28,6 +28,8 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { late DatabaseService _databaseService; late TextEditingController _deviceNameController; + late TextEditingController _rtlTcpHostController; + late TextEditingController _rtlTcpPortController; String _deviceName = ''; bool _backgroundServiceEnabled = false; @@ -39,19 +41,143 @@ class _SettingsScreenState extends State { GroupBy _groupBy = GroupBy.trainAndLoco; TimeWindow _timeWindow = TimeWindow.unlimited; String _mapType = 'map'; + bool _rtlTcpEnabled = false; + String _rtlTcpHost = '127.0.0.1'; + String _rtlTcpPort = '14423'; @override void initState() { super.initState(); _databaseService = DatabaseService.instance; _deviceNameController = TextEditingController(); + _rtlTcpHostController = TextEditingController(); + _rtlTcpPortController = TextEditingController(); _loadSettings(); _loadRecordCount(); } + Widget _buildRtlTcpSettings() { + return Card( + color: AppTheme.tertiaryBlack, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.wifi, + color: Theme.of(context).colorScheme.primary), + const SizedBox(width: 12), + Text('RTL-TCP 接收', style: AppTheme.titleMedium), + ], + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('启用RTL-TCP接收', style: AppTheme.bodyLarge), + ], + ), + Switch( + value: _rtlTcpEnabled, + onChanged: (value) { + setState(() { + _rtlTcpEnabled = value; + }); + _saveSettings(); + }, + activeColor: Theme.of(context).colorScheme.primary, + ), + ], + ), + Visibility( + visible: _rtlTcpEnabled, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + TextField( + decoration: InputDecoration( + labelText: '服务器地址', + hintText: '输入RTL-TCP服务器地址', + labelStyle: const TextStyle(color: Colors.white70), + hintStyle: const TextStyle(color: Colors.white54), + border: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.white54), + borderRadius: BorderRadius.circular(12.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.white54), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).colorScheme.primary), + borderRadius: BorderRadius.circular(12.0), + ), + ), + style: const TextStyle(color: Colors.white), + controller: _rtlTcpHostController, + onChanged: (value) { + setState(() { + _rtlTcpHost = value; + }); + _saveSettings(); + }, + ), + const SizedBox(height: 16), + TextField( + decoration: InputDecoration( + labelText: '服务器端口', + hintText: '输入RTL-TCP服务器端口', + labelStyle: const TextStyle(color: Colors.white70), + hintStyle: const TextStyle(color: Colors.white54), + border: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.white54), + borderRadius: BorderRadius.circular(12.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.white54), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).colorScheme.primary), + borderRadius: BorderRadius.circular(12.0), + ), + ), + style: const TextStyle(color: Colors.white), + controller: _rtlTcpPortController, + keyboardType: TextInputType.number, + onChanged: (value) { + setState(() { + _rtlTcpPort = value; + }); + _saveSettings(); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } + @override void dispose() { _deviceNameController.dispose(); + _rtlTcpHostController.dispose(); + _rtlTcpPortController.dispose(); super.dispose(); } @@ -71,6 +197,11 @@ class _SettingsScreenState extends State { _groupBy = settings.groupBy; _timeWindow = settings.timeWindow; _mapType = settingsMap['mapType']?.toString() ?? 'webview'; + _rtlTcpEnabled = (settingsMap['rtlTcpEnabled'] ?? 0) == 1; + _rtlTcpHost = settingsMap['rtlTcpHost']?.toString() ?? '127.0.0.1'; + _rtlTcpPort = settingsMap['rtlTcpPort']?.toString() ?? '14423'; + _rtlTcpHostController.text = _rtlTcpHost; + _rtlTcpPortController.text = _rtlTcpPort; }); } } @@ -95,6 +226,9 @@ class _SettingsScreenState extends State { 'groupBy': _groupBy.name, 'timeWindow': _timeWindow.name, 'mapType': _mapType, + 'rtlTcpEnabled': _rtlTcpEnabled ? 1 : 0, + 'rtlTcpHost': _rtlTcpHost, + 'rtlTcpPort': _rtlTcpPort, }); widget.onSettingsChanged?.call(); } @@ -110,6 +244,8 @@ class _SettingsScreenState extends State { const SizedBox(height: 20), _buildAppSettings(), const SizedBox(height: 20), + _buildRtlTcpSettings(), + const SizedBox(height: 20), _buildMergeSettings(), const SizedBox(height: 20), _buildDataManagement(), @@ -204,7 +340,6 @@ class _SettingsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('后台保活服务', style: AppTheme.bodyLarge), - Text('保持应用在后台运行', style: AppTheme.caption), ], ), Switch( @@ -232,8 +367,7 @@ class _SettingsScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('LBJ消息通知', style: AppTheme.bodyLarge), - Text('接收LBJ消息通知', style: AppTheme.caption), + Text('通知服务', style: AppTheme.bodyLarge), ], ), Switch( @@ -255,8 +389,7 @@ class _SettingsScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('地图显示方式', style: AppTheme.bodyLarge), - Text('选择地图组件类型', style: AppTheme.caption), + Text('地图组件类型', style: AppTheme.bodyLarge), ], ), DropdownButton( @@ -293,7 +426,6 @@ class _SettingsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('隐藏只有时间有效的记录', style: AppTheme.bodyLarge), - Text('不显示只有时间信息的记录', style: AppTheme.caption), ], ), Switch( @@ -342,7 +474,6 @@ class _SettingsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('启用记录合并', style: AppTheme.bodyLarge), - Text('合并相同内容的LBJ记录', style: AppTheme.caption), ], ), Switch( @@ -452,7 +583,6 @@ class _SettingsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('隐藏不可分组记录', style: AppTheme.bodyLarge), - Text('不显示无法分组的记录', style: AppTheme.caption), ], ), Switch( diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart index 3587606..5c76a3a 100644 --- a/lib/services/background_service.dart +++ b/lib/services/background_service.dart @@ -12,6 +12,7 @@ const String _notificationChannelName = 'LBJ Console 后台服务'; const String _notificationChannelDescription = '保持蓝牙连接稳定'; const int _notificationId = 114514; +@pragma('vm:entry-point') class BackgroundService { static final FlutterBackgroundService _service = FlutterBackgroundService(); static bool _isInitialized = false; diff --git a/lib/services/ble_service.dart b/lib/services/ble_service.dart index dd7887c..4bf41b7 100644 --- a/lib/services/ble_service.dart +++ b/lib/services/ble_service.dart @@ -5,11 +5,17 @@ import 'dart:math'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:lbjconsole/models/train_record.dart'; import 'package:lbjconsole/services/database_service.dart'; +import 'package:lbjconsole/services/rtl_tcp_service.dart'; class BLEService { static final BLEService _instance = BLEService._internal(); factory BLEService() => _instance; - BLEService._internal(); + BLEService._internal() { + _rtlTcpService = RtlTcpService(); + } + + late final RtlTcpService _rtlTcpService; + RtlTcpService? get rtlTcpService => _rtlTcpService; static const String TAG = "LBJ_BT_FLUTTER"; static final Guid serviceUuid = Guid("0000ffe0-0000-1000-8000-00805f9b34fb"); diff --git a/lib/services/database_service.dart b/lib/services/database_service.dart index 0950a7a..6e66947 100644 --- a/lib/services/database_service.dart +++ b/lib/services/database_service.dart @@ -13,7 +13,7 @@ class DatabaseService { DatabaseService._internal(); static const String _databaseName = 'train_database'; - static const _databaseVersion = 7; + static const _databaseVersion = 8; static const String trainRecordsTable = 'train_records'; static const String appSettingsTable = 'app_settings'; @@ -91,6 +91,14 @@ class DatabaseService { await db.execute( 'ALTER TABLE $appSettingsTable ADD COLUMN mapSettingsTimestamp INTEGER'); } + if (oldVersion < 8) { + await db.execute( + 'ALTER TABLE $appSettingsTable ADD COLUMN rtlTcpEnabled INTEGER NOT NULL DEFAULT 0'); + await db.execute( + 'ALTER TABLE $appSettingsTable ADD COLUMN rtlTcpHost TEXT NOT NULL DEFAULT "127.0.0.1"'); + await db.execute( + 'ALTER TABLE $appSettingsTable ADD COLUMN rtlTcpPort TEXT NOT NULL DEFAULT "14423"'); + } } Future _onCreate(Database db, int version) async { @@ -141,7 +149,10 @@ class DatabaseService { timeWindow TEXT NOT NULL DEFAULT 'unlimited', mapTimeFilter TEXT NOT NULL DEFAULT 'unlimited', hideUngroupableRecords INTEGER NOT NULL DEFAULT 0, - mapSettingsTimestamp INTEGER + mapSettingsTimestamp INTEGER, + rtlTcpEnabled INTEGER NOT NULL DEFAULT 0, + rtlTcpHost TEXT NOT NULL DEFAULT '127.0.0.1', + rtlTcpPort TEXT NOT NULL DEFAULT '14423' ) '''); @@ -168,8 +179,11 @@ class DatabaseService { 'groupBy': 'trainAndLoco', 'timeWindow': 'unlimited', 'mapTimeFilter': 'unlimited', - 'hideUngroupableRecords': 0, - 'mapSettingsTimestamp': null, + 'hideUngroupableRecords': 0, + 'mapSettingsTimestamp': null, + 'rtlTcpEnabled': 0, + 'rtlTcpHost': '127.0.0.1', + 'rtlTcpPort': '14423', }); } diff --git a/lib/services/rtl_tcp_service.dart b/lib/services/rtl_tcp_service.dart new file mode 100644 index 0000000..ae9b2f7 --- /dev/null +++ b/lib/services/rtl_tcp_service.dart @@ -0,0 +1,404 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer' as developer; +import 'dart:math'; +import 'package:flutter/services.dart'; +import 'package:gbk_codec/gbk_codec.dart'; +import 'package:lbjconsole/models/train_record.dart'; +import 'package:lbjconsole/services/database_service.dart'; + +const String _lbjInfoAddr = "1234000"; +const String _lbjInfo2Addr = "1234002"; +const String _lbjSyncAddr = "1234008"; +const int _functionDown = 1; +const int _functionUp = 3; + +class _LbJState { + String train = ""; + int direction = -1; + String speed = "NUL"; + String positionKm = " "; + String time = ""; + String lbjClass = "NA"; + String loco = ""; + String route = "********"; + + String posLonDeg = ""; + String posLonMin = ""; + String posLatDeg = ""; + String posLatMin = ""; + + String _info2Hex = ""; + + void reset() { + train = ""; + direction = -1; + speed = "NUL"; + positionKm = " "; + time = ""; + lbjClass = "NA"; + loco = ""; + route = "********"; + posLonDeg = ""; + posLonMin = ""; + posLatDeg = ""; + posLatMin = ""; + _info2Hex = ""; + } + + String _recodeBCD(String numericStr) { + return numericStr + .replaceAll('.', 'A') + .replaceAll('U', 'B') + .replaceAll(' ', 'C') + .replaceAll('-', 'D') + .replaceAll(')', 'E') + .replaceAll('(', 'F'); + } + + int _hexToChar(String hex1, String hex2) { + final String hex = "$hex1$hex2"; + return int.tryParse(hex, radix: 16) ?? 0; + } + + String _gbkToUtf8(List gbkBytes) { + try { + final validBytes = gbkBytes.where((b) => b != 0).toList(); + return gbk.decode(validBytes); + } catch (e) { + return ""; + } + } + + void updateFromRaw(String addr, int func, String numeric) { + if (func == _functionDown || func == _functionUp) { + direction = func; + } + switch (addr) { + case _lbjInfoAddr: + final RegExp infoRegex = RegExp(r'^\s*(\S+)\s+(\S+)\s+(\S+)'); + final match = infoRegex.firstMatch(numeric); + if (match != null) { + train = match.group(1) ?? ""; + speed = match.group(2) ?? "NUL"; + + String pos = match.group(3)?.trim() ?? ""; + + if (pos.isEmpty) { + positionKm = " "; + } else if (pos.length > 1) { + positionKm = + "${pos.substring(0, pos.length - 1)}.${pos.substring(pos.length - 1)}"; + } else { + positionKm = "0.$pos"; + } + } + break; + + case _lbjInfo2Addr: + String buffer = numeric; + if (buffer.length < 50) return; + _info2Hex = _recodeBCD(buffer); + + if (_info2Hex.length >= 4) { + try { + List classBytes = [ + _hexToChar(_info2Hex[0], _info2Hex[1]), + _hexToChar(_info2Hex[2], _info2Hex[3]), + ]; + lbjClass = String.fromCharCodes(classBytes + .where((b) => b > 0x1F && b < 0x7F && b != 0x22 && b != 0x2C)); + } catch (e) {} + } + if (buffer.length >= 12) loco = buffer.substring(4, 12); + + List routeBytes = List.filled(17, 0); + + if (_info2Hex.length >= 18) { + try { + routeBytes[0] = _hexToChar(_info2Hex[14], _info2Hex[15]); + routeBytes[1] = _hexToChar(_info2Hex[16], _info2Hex[17]); + } catch (e) {} + } + + if (_info2Hex.length >= 22) { + try { + routeBytes[2] = _hexToChar(_info2Hex[18], _info2Hex[19]); + routeBytes[3] = _hexToChar(_info2Hex[20], _info2Hex[21]); + } catch (e) {} + } + + if (_info2Hex.length >= 30) { + try { + routeBytes[4] = _hexToChar(_info2Hex[22], _info2Hex[23]); + routeBytes[5] = _hexToChar(_info2Hex[24], _info2Hex[25]); + routeBytes[6] = _hexToChar(_info2Hex[26], _info2Hex[27]); + routeBytes[7] = _hexToChar(_info2Hex[28], _info2Hex[29]); + } catch (e) {} + } + route = _gbkToUtf8(routeBytes); + + if (buffer.length >= 39) { + posLonDeg = buffer.substring(30, 33); + posLonMin = "${buffer.substring(33, 35)}.${buffer.substring(35, 39)}"; + } + if (buffer.length >= 47) { + posLatDeg = buffer.substring(39, 41); + posLatMin = "${buffer.substring(41, 43)}.${buffer.substring(43, 47)}"; + } + break; + + case _lbjSyncAddr: + if (numeric.length >= 5) + time = "${numeric.substring(1, 3)}:${numeric.substring(3, 5)}"; + break; + } + } + + double _convertMagSqToRssi(double magsqRaw) { + if (magsqRaw <= 0) return -120.0; + double rssi = 10 * log(magsqRaw) / log(10); + return (rssi - 30.0).clamp(-120.0, -20.0); + } + + Map toTrainRecordJson(double magsqRaw) { + final now = DateTime.now(); + final double finalRssi = _convertMagSqToRssi(magsqRaw); + + String gpsPosition = ""; + if (posLatDeg.isNotEmpty && posLatMin.isNotEmpty) { + gpsPosition = "${posLatDeg}°${posLatMin}′"; + } + if (posLonDeg.isNotEmpty && posLonMin.isNotEmpty) { + gpsPosition += + (gpsPosition.isEmpty ? "" : " ") + "${posLonDeg}°${posLonMin}′"; + } + + String kmPosition = positionKm.replaceAll(' ', ''); + + final jsonData = { + 'uniqueId': '${now.millisecondsSinceEpoch}_${Random().nextInt(9999)}', + 'receivedTimestamp': now.millisecondsSinceEpoch, + 'timestamp': now.millisecondsSinceEpoch, + 'rssi': finalRssi, + 'train': train.replaceAll('', ''), + 'loco': loco.replaceAll('', ''), + 'speed': speed.replaceAll('NUL', ''), + 'position': kmPosition, + 'positionInfo': gpsPosition, + 'route': route.replaceAll('********', ''), + 'lbjClass': lbjClass.replaceAll('NA', ''), + 'time': time.replaceAll('', ''), + 'direction': (direction == 1 || direction == 3) ? direction : 0, + 'locoType': "", + }; + return jsonData; + } +} + +class RtlTcpService { + static final RtlTcpService _instance = RtlTcpService._internal(); + factory RtlTcpService() => _instance; + RtlTcpService._internal(); + static const _methodChannel = + MethodChannel('org.noxylva.lbjconsole/rtl_tcp_method'); + static const _eventChannel = + EventChannel('org.noxylva.lbjconsole/rtl_tcp_event'); + final StreamController _statusController = + StreamController.broadcast(); + final StreamController _dataController = + StreamController.broadcast(); + final StreamController _connectionController = + StreamController.broadcast(); + final StreamController _lastReceivedTimeController = + StreamController.broadcast(); + + Stream get statusStream => _statusController.stream; + Stream get dataStream => _dataController.stream; + Stream get connectionStream => _connectionController.stream; + Stream get lastReceivedTimeStream => + _lastReceivedTimeController.stream; + String _deviceStatus = "未连接"; + bool _isConnected = false; + DateTime? _lastReceivedTime; + StreamSubscription? _eventChannelSubscription; + final _LbJState _state = _LbJState(); + Timer? _reconnectTimer; + String _lastHost = '127.0.0.1'; + String _lastPort = '14423'; + static const Duration _reconnectInterval = Duration(seconds: 2); + bool _isEnabled = false; + + static const bool _logRaw = true; + static const bool _logParsed = true; + String _lastRawMessage = ""; + + bool get isConnected => _isConnected; + String get deviceStatus => _deviceStatus; + String get currentAddress => '$_lastHost:$_lastPort'; + bool get isEnabled => _isEnabled; + + void _updateConnectionState(bool connected, String status) { + if (_isConnected == connected && _deviceStatus == status) return; + final wasConnected = _isConnected; + _isConnected = connected; + _deviceStatus = status; + if (!connected) { + _lastReceivedTime = null; + if (wasConnected) _state.reset(); + if (_isEnabled) _startAutoReconnect(); + } else { + _cancelAutoReconnect(); + } + _statusController.add(_deviceStatus); + _connectionController.add(_isConnected); + _lastReceivedTimeController.add(_lastReceivedTime); + } + + void _startAutoReconnect() { + if (!_isEnabled || _reconnectTimer != null) return; + developer.log('RTL-TCP: 启动自动重连定时器', name: 'RtlTcpService'); + _reconnectTimer = Timer.periodic(_reconnectInterval, (timer) { + if (_isConnected || !_isEnabled) { + _cancelAutoReconnect(); + return; + } + developer.log('RTL-TCP: 自动重连: 尝试连接...', name: 'RtlTcpService'); + _internalConnect(); + }); + } + + void _cancelAutoReconnect() { + if (_reconnectTimer != null) { + developer.log('RTL-TCP: 取消自动重连', name: 'RtlTcpService'); + _reconnectTimer!.cancel(); + _reconnectTimer = null; + } + } + + void _listenToEventChannel() { + if (_eventChannelSubscription != null) { + _eventChannelSubscription?.cancel(); + } + + _eventChannelSubscription = _eventChannel.receiveBroadcastStream().listen( + (dynamic event) { + try { + final map = event as Map; + + if (map.containsKey('connected')) { + final connected = map['connected'] as bool? ?? false; + if (_isConnected != connected) { + _updateConnectionState(connected, connected ? "已连接" : "已断开"); + } + } + + if (map.containsKey('address')) { + final addr = map['address'] as String; + + if (addr != _lbjInfoAddr && + addr != _lbjInfo2Addr && + addr != _lbjSyncAddr) { + return; + } + + final func = int.tryParse(map['func'] as String? ?? '-1') ?? -1; + final numeric = map['numeric'] as String; + final magsqRaw = (map['magsqRaw'] as num?)?.toDouble() ?? 0.0; + + final String currentRawMessage = "$addr|$func|$numeric"; + if (currentRawMessage == _lastRawMessage) { + return; + } + _lastRawMessage = currentRawMessage; + + if (_logRaw) { + developer.log('RTL-TCP-RAW: $currentRawMessage', + name: 'RTL-TCP-Data'); + } + + if (!_isConnected) { + _updateConnectionState(true, "已连接"); + } + _lastReceivedTime = DateTime.now(); + _lastReceivedTimeController.add(_lastReceivedTime); + + _state.updateFromRaw(addr, func, numeric); + + if (addr == _lbjInfoAddr || addr == _lbjInfo2Addr) { + final jsonData = _state.toTrainRecordJson(magsqRaw); + final trainRecord = TrainRecord.fromJson(jsonData); + + if (_logParsed) { + developer.log('RTL-TCP-PARSED: ${jsonEncode(jsonData)}', + name: 'RTL-TCP-Data'); + } + + _dataController.add(trainRecord); + DatabaseService.instance.insertRecord(trainRecord); + } + } + } catch (e, s) { + developer.log('RTL-TCP StateMachine Error: $e', + name: 'RTL-TCP', error: e, stackTrace: s); + _updateConnectionState(false, "数据解析错误"); + } + }, + onError: (dynamic error) { + _updateConnectionState(false, "数据通道错误"); + _eventChannelSubscription?.cancel(); + _eventChannelSubscription = null; + }, + onDone: () { + _updateConnectionState(false, "连接已断开"); + _eventChannelSubscription = null; + }, + ); + } + + Future _internalConnect() async { + if (_eventChannelSubscription == null) { + _listenToEventChannel(); + } + + try { + await _methodChannel + .invokeMethod('connect', {'host': _lastHost, 'port': _lastPort}); + } on PlatformException catch (e) { + _updateConnectionState(false, "连接失败: ${e.message}"); + } + } + + Future connect({String? host, String? port}) async { + final settings = await DatabaseService.instance.getAllSettings(); + final dbHost = host ?? settings?['rtl_tcp_host'] ?? '127.0.0.1'; + final dbPort = port ?? settings?['rtl_tcp_port'] ?? '14423'; + _isEnabled = true; + _lastHost = dbHost; + _lastPort = dbPort; + if (_isConnected) return; + _updateConnectionState(false, "正在连接..."); + _internalConnect(); + } + + Future disconnect() async { + _isEnabled = false; + _cancelAutoReconnect(); + await _eventChannelSubscription?.cancel(); + _eventChannelSubscription = null; + try { + await _methodChannel.invokeMethod('disconnect'); + } catch (e) { + developer.log('RTL-TCP: 原生断开出错: $e', name: 'RTL-TCP'); + } + _updateConnectionState(false, "已禁用"); + } + + void dispose() { + disconnect(); + _statusController.close(); + _dataController.close(); + _connectionController.close(); + _lastReceivedTimeController.close(); + } +} diff --git a/pubspec.lock b/pubspec.lock index 175eb98..ee6aece 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -201,6 +201,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -440,6 +448,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" + gbk_codec: + dependency: "direct main" + description: + name: gbk_codec + sha256: "3af5311fc9393115e3650ae6023862adf998051a804a08fb804f042724999f61" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.0" geolocator: dependency: "direct main" description: @@ -536,6 +552,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.15.6" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6e782f1..ac44c27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.7.0-flutter+70 +version: 0.8.0-flutter+80 environment: sdk: ^3.5.4 @@ -57,6 +57,7 @@ dependencies: vector_map_tiles: ^8.0.0 maplibre_gl: ^0.22.0 webview_flutter: ^4.8.0 + gbk_codec: ^0.4.0 dev_dependencies: flutter_test: