Compare commits
9 Commits
v0.5.1-flu
...
v0.8.0-flu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aa19ada14 | ||
|
|
356738ac10 | ||
|
|
06aa8491b4 | ||
|
|
6073ea615e | ||
|
|
5533df92b5 | ||
|
|
24c6abd4f3 | ||
|
|
5b3960f7d6 | ||
|
|
33e790957e | ||
|
|
88e3636c3f |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
assets/* linguist-vendored
|
||||
83
.vscode/settings.json
vendored
Normal file
83
.vscode/settings.json
vendored
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
17
README.md
17
README.md
@@ -3,21 +3,36 @@
|
||||
LBJ Console 是一款应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
||||
|
||||
- 接收列车预警消息,支持可选的手机推送通知。
|
||||
- 监控指定列车的轨迹,在地图上显示。
|
||||
- 在地图上显示预警消息的 GPS 信息。
|
||||
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
||||
- [WIP] 从 RTL-TCP 获取数据。
|
||||
|
||||
[android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。
|
||||
|
||||
|
||||
## 数据文件
|
||||
|
||||
LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示:
|
||||
|
||||
- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。
|
||||
- `loco_type_info.csv`:包含机车类型编码信息,格式为 `机车类型编码前缀,机车类型`。
|
||||
- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。
|
||||
|
||||
数据来源于网络,可能存在错误或不完整,欢迎通过提交 Pull Request 共同完善数据准确性。
|
||||
|
||||
# 计划实现的功能
|
||||
|
||||
- 集成 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)授权。
|
||||
|
||||
|
||||
@@ -72,6 +72,12 @@ android {
|
||||
universalApk true
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
version "3.22.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
22
android/app/src/main/cpp/CMakeLists.txt
Normal file
22
android/app/src/main/cpp/CMakeLists.txt
Normal file
@@ -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
|
||||
)
|
||||
410
android/app/src/main/cpp/demod.cpp
Normal file
410
android/app/src/main/cpp/demod.cpp
Normal file
@@ -0,0 +1,410 @@
|
||||
#include "demod.h"
|
||||
|
||||
bool is_message_ready = false;
|
||||
|
||||
PhaseDiscriminators phaseDiscri;
|
||||
Lowpass<double> lowpassBaud;
|
||||
MovingAverageUtil<double, double, 2048> 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<float> 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;
|
||||
}
|
||||
39
android/app/src/main/cpp/demod.h
Normal file
39
android/app/src/main/cpp/demod.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef DEMOD_H
|
||||
#define DEMOD_H
|
||||
|
||||
#include <complex>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#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<double> lowpassBaud;
|
||||
extern MovingAverageUtil<double, double, 2048> preambleMovingAverage;
|
||||
extern double magsqRaw;
|
||||
|
||||
void processOneSample(int8_t i, int8_t q);
|
||||
|
||||
#endif
|
||||
278
android/app/src/main/cpp/dsp/afsquelch.cpp
Normal file
278
android/app/src/main/cpp/dsp/afsquelch.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<double>(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<double>(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();
|
||||
}
|
||||
91
android/app/src/main/cpp/dsp/afsquelch.h
Normal file
91
android/app/src/main/cpp/dsp/afsquelch.h
Normal file
@@ -0,0 +1,91 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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<MovingAverage<double> > m_movingAverages;
|
||||
};
|
||||
|
||||
|
||||
#endif /* INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ */
|
||||
191
android/app/src/main/cpp/dsp/agc.cpp
Normal file
191
android/app/src/main/cpp/dsp/agc.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <algorithm>
|
||||
#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
|
||||
}
|
||||
}
|
||||
137
android/app/src/main/cpp/dsp/agc.h
Normal file
137
android/app/src/main/cpp/dsp/agc.h
Normal file
@@ -0,0 +1,137 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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<double> 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<uint32_t AvgSize>
|
||||
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<double> m_moving_average; // Averaging engine. The stack length conditions the smoothness of AGC.
|
||||
//MovingAverageUtil<Real, double, AvgSize> m_moving_average;
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_GPL_DSP_AGC_H_ */
|
||||
106
android/app/src/main/cpp/dsp/autocorrector.h
Normal file
106
android/app/src/main/cpp/dsp/autocorrector.h
Normal file
@@ -0,0 +1,106 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_AUTOCORRECTOR_H_
|
||||
#define SDRBASE_DSP_AUTOCORRECTOR_H_
|
||||
|
||||
#include "util/movingaverage.h"
|
||||
|
||||
template<typename T>
|
||||
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<int32_t, int64_t, 1024> m_iBeta;
|
||||
MovingAverageUtil<int32_t, int64_t, 1024> m_qBeta;
|
||||
MovingAverageUtil<float, double, 128> m_avgII;
|
||||
MovingAverageUtil<float, double, 128> m_avgIQ;
|
||||
MovingAverageUtil<float, double, 128> m_avgII2;
|
||||
MovingAverageUtil<float, double, 128> m_avgQQ2;
|
||||
MovingAverageUtil<double, double, 128> m_avgPhi;
|
||||
MovingAverageUtil<double, double, 128> m_avgAmp;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
AutoCorrector<T>::AutoCorrector(qint32 intBits) :
|
||||
m_iqCorrection(false),
|
||||
m_scalef((float) (1<<(intBits-1)))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void AutoCorrector<T>::process(const T& inreal, const T& inimag, qint32& outreal, qint32& outimag)
|
||||
{
|
||||
outreal = inreal;
|
||||
outimag = inimag;
|
||||
process(outreal, outimag);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void AutoCorrector<T>::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); // <I", I">
|
||||
m_avgIQ(xi*xq); // <I", Q">
|
||||
|
||||
|
||||
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); // <I, I>
|
||||
m_avgQQ2(yq*yq); // <Q, Q>
|
||||
|
||||
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_ */
|
||||
28
android/app/src/main/cpp/dsp/basebandsamplesink.cpp
Normal file
28
android/app/src/main/cpp/dsp/basebandsamplesink.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "basebandsamplesink.h"
|
||||
|
||||
BasebandSampleSink::BasebandSampleSink()
|
||||
{
|
||||
}
|
||||
|
||||
BasebandSampleSink::~BasebandSampleSink()
|
||||
{
|
||||
}
|
||||
44
android/app/src/main/cpp/dsp/basebandsamplesink.h
Normal file
44
android/app/src/main/cpp/dsp/basebandsamplesink.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_SAMPLESINK_H
|
||||
#define INCLUDE_SAMPLESINK_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
28
android/app/src/main/cpp/dsp/basebandsamplesource.cpp
Normal file
28
android/app/src/main/cpp/dsp/basebandsamplesource.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/basebandsamplesource.h"
|
||||
|
||||
BasebandSampleSource::BasebandSampleSource()
|
||||
{
|
||||
}
|
||||
|
||||
BasebandSampleSource::~BasebandSampleSource()
|
||||
{
|
||||
}
|
||||
40
android/app/src/main/cpp/dsp/basebandsamplesource.h
Normal file
40
android/app/src/main/cpp/dsp/basebandsamplesource.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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_ */
|
||||
260
android/app/src/main/cpp/dsp/channelmarker.cpp
Normal file
260
android/app/src/main/cpp/dsp/channelmarker.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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<SWGSDRangel::SWGChannelMarker *>(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<SWGSDRangel::SWGChannelMarker *>(const_cast<SWGSDRangel::SWGObject *>(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<<streamIndex);
|
||||
}
|
||||
|
||||
void ChannelMarker::removeStreamIndex(int streamIndex)
|
||||
{
|
||||
m_enabledStreamsBits &= ~(1<<streamIndex);
|
||||
}
|
||||
|
||||
void ChannelMarker::clearStreamIndexes()
|
||||
{
|
||||
m_enabledStreamsBits = 0;
|
||||
}
|
||||
142
android/app/src/main/cpp/dsp/channelmarker.h
Normal file
142
android/app/src/main/cpp/dsp/channelmarker.h
Normal file
@@ -0,0 +1,142 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// Copyright (C) 2015-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_CHANNELMARKER_H
|
||||
#define INCLUDE_CHANNELMARKER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QColor>
|
||||
#include <QByteArray>
|
||||
|
||||
#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<<streamIndex);
|
||||
}
|
||||
|
||||
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);
|
||||
void updateSettings(const ChannelMarker *channelMarker);
|
||||
|
||||
protected:
|
||||
static QRgb m_colorTable[];
|
||||
static int m_nextColor;
|
||||
|
||||
QString m_title;
|
||||
QString m_displayAddressSend;
|
||||
QString m_displayAddressReceive;
|
||||
int m_centerFrequency;
|
||||
int m_bandwidth;
|
||||
int m_oppositeBandwidth;
|
||||
int m_lowCutoff;
|
||||
int m_shift;
|
||||
sidebands_t m_sidebands;
|
||||
bool m_visible;
|
||||
bool m_highlighted;
|
||||
QColor m_color;
|
||||
bool m_movable;
|
||||
frequencyScaleDisplay_t m_fScaleDisplayType;
|
||||
bool m_sourceOrSinkStream;
|
||||
uint32_t m_enabledStreamsBits;
|
||||
|
||||
void resetToDefaults();
|
||||
|
||||
signals:
|
||||
void changedByAPI();
|
||||
void changedByCursor();
|
||||
void highlightedByCursor();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_CHANNELMARKER_H
|
||||
26
android/app/src/main/cpp/dsp/channelsamplesink.cpp
Normal file
26
android/app/src/main/cpp/dsp/channelsamplesink.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "channelsamplesink.h"
|
||||
|
||||
ChannelSampleSink::ChannelSampleSink()
|
||||
{}
|
||||
|
||||
ChannelSampleSink::~ChannelSampleSink()
|
||||
{}
|
||||
38
android/app/src/main/cpp/dsp/channelsamplesink.h
Normal file
38
android/app/src/main/cpp/dsp/channelsamplesink.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 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_
|
||||
26
android/app/src/main/cpp/dsp/channelsamplesource.cpp
Normal file
26
android/app/src/main/cpp/dsp/channelsamplesource.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "channelsamplesource.h"
|
||||
|
||||
ChannelSampleSource::ChannelSampleSource()
|
||||
{}
|
||||
|
||||
ChannelSampleSource::~ChannelSampleSource()
|
||||
{}
|
||||
38
android/app/src/main/cpp/dsp/channelsamplesource.h
Normal file
38
android/app/src/main/cpp/dsp/channelsamplesource.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 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_
|
||||
61
android/app/src/main/cpp/dsp/complex.h
Normal file
61
android/app/src/main/cpp/dsp/complex.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// ----------------------------------------------------------------------------
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _COMPLEX_H
|
||||
#define _COMPLEX_H
|
||||
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
|
||||
typedef std::complex<float> 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
|
||||
112
android/app/src/main/cpp/dsp/costasloop.cpp
Normal file
112
android/app/src/main/cpp/dsp/costasloop.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "costasloop.h"
|
||||
#include <cmath>
|
||||
|
||||
// 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<float> &out, const std::complex<float> cc1, const std::complex<float> 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<float> nco(::cosf(-m_phase), ::sinf(-m_phase));
|
||||
|
||||
std::complex<float> 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());
|
||||
}
|
||||
122
android/app/src/main/cpp/dsp/costasloop.h
Normal file
122
android/app/src/main/cpp/dsp/costasloop.h
Normal file
@@ -0,0 +1,122 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_COSTASLOOP_H_
|
||||
#define SDRBASE_DSP_COSTASLOOP_H_
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<float>& 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<float> 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<float> sample) const // for BPSK
|
||||
{
|
||||
return (sample.real() * sample.imag());
|
||||
}
|
||||
|
||||
float phaseDetector4(std::complex<float> 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<float> 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_ */
|
||||
159
android/app/src/main/cpp/dsp/ctcssdetector.cpp
Normal file
159
android/app/src/main/cpp/dsp/ctcssdetector.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2017, 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#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);
|
||||
}
|
||||
94
android/app/src/main/cpp/dsp/ctcssdetector.h
Normal file
94
android/app/src/main/cpp/dsp/ctcssdetector.h
Normal file
@@ -0,0 +1,94 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2016, 2018, 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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_ */
|
||||
77
android/app/src/main/cpp/dsp/ctcssfrequencies.cpp
Normal file
77
android/app/src/main/cpp/dsp/ctcssfrequencies.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software, you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY, without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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;
|
||||
25
android/app/src/main/cpp/dsp/ctcssfrequencies.h
Normal file
25
android/app/src/main/cpp/dsp/ctcssfrequencies.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "export.h"
|
||||
|
||||
struct SDRBASE_API CTCSSFrequencies {
|
||||
static const int m_nbFreqs;
|
||||
static const float m_Freqs[];
|
||||
};
|
||||
155
android/app/src/main/cpp/dsp/cudavkfftengine.cpp
Normal file
155
android/app/src/main/cpp/dsp/cudavkfftengine.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<CUDAPlan *>(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<CUDAPlan *>(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<CUDAPlan *>(p);
|
||||
|
||||
cudaFree(plan->m_in);
|
||||
plan->m_in = nullptr;
|
||||
cudaFree(plan->m_out);
|
||||
plan->m_out = nullptr;
|
||||
cudaFree(plan->m_buffer);
|
||||
}
|
||||
55
android/app/src/main/cpp/dsp/cudavkfftengine.h
Normal file
55
android/app/src/main/cpp/dsp/cudavkfftengine.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_CUDAVKFFTENGINE_H
|
||||
#define INCLUDE_CUDAVKFFTENGINE_H
|
||||
|
||||
#include "vkfftengine.h"
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <nvrtc.h>
|
||||
#include <cuda_runtime_api.h>
|
||||
#include <cuComplex.h>
|
||||
|
||||
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
|
||||
625
android/app/src/main/cpp/dsp/cwkeyer.cpp
Normal file
625
android/app/src/main/cpp/dsp/cwkeyer.cpp
Normal file
@@ -0,0 +1,625 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdint.h>
|
||||
#include <QChar>
|
||||
#include <QDebug>
|
||||
#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);
|
||||
}
|
||||
163
android/app/src/main/cpp/dsp/cwkeyer.h
Normal file
163
android/app/src/main/cpp/dsp/cwkeyer.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_CWKEYER_H_
|
||||
#define SDRBASE_DSP_CWKEYER_H_
|
||||
|
||||
#include <QObject>
|
||||
#include <QRecursiveMutex>
|
||||
|
||||
#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_ */
|
||||
158
android/app/src/main/cpp/dsp/cwkeyersettings.cpp
Normal file
158
android/app/src/main/cpp/dsp/cwkeyersettings.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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<SWGSDRangel::SWGCWKeyerSettings *>(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<SWGSDRangel::SWGCWKeyerSettings *>(const_cast<SWGSDRangel::SWGObject *>(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();
|
||||
}
|
||||
}
|
||||
63
android/app/src/main/cpp/dsp/cwkeyersettings.h
Normal file
63
android/app/src/main/cpp/dsp/cwkeyersettings.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_CWKEYERSETTINGS_H_
|
||||
#define SDRBASE_DSP_CWKEYERSETTINGS_H_
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
|
||||
#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_ */
|
||||
314
android/app/src/main/cpp/dsp/datafifo.cpp
Normal file
314
android/app/src/main/cpp/dsp/datafifo.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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;
|
||||
}
|
||||
79
android/app/src/main/cpp/dsp/datafifo.h
Normal file
79
android/app/src/main/cpp/dsp/datafifo.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DATAFIFO_H
|
||||
#define INCLUDE_DATAFIFO_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QRecursiveMutex>
|
||||
#include <QElapsedTimer>
|
||||
#include <QByteArray>
|
||||
|
||||
#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
|
||||
465
android/app/src/main/cpp/dsp/dcscodes.cpp
Normal file
465
android/app/src/main/cpp/dsp/dcscodes.cpp
Normal file
@@ -0,0 +1,465 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
// //
|
||||
// Source: http://onfreq.com/syntorx/dcs.html //
|
||||
// //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dcscodes.h"
|
||||
|
||||
const QMap<unsigned int, unsigned int> 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<unsigned int, unsigned int> 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<unsigned int>& codes)
|
||||
{
|
||||
codes.clear();
|
||||
|
||||
for (auto code : m_toCanonicalCode.keys())
|
||||
{
|
||||
if (code == m_toCanonicalCode.value(code)) {
|
||||
codes.append(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
android/app/src/main/cpp/dsp/dcscodes.h
Normal file
35
android/app/src/main/cpp/dsp/dcscodes.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DSP_DCSCODES_H_
|
||||
#define INCLUDE_DSP_DCSCODES_H_
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class SDRBASE_API DCSCodes
|
||||
{
|
||||
public:
|
||||
static void getCanonicalCodes(QList<unsigned int>& codes);
|
||||
static const int m_nbCodes;
|
||||
static const QMap<unsigned int, unsigned int> m_toCanonicalCode;
|
||||
static const QMap<unsigned int, unsigned int> m_signFlip;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_DSP_DCSCODES_H_
|
||||
223
android/app/src/main/cpp/dsp/decimatorc.cpp
Normal file
223
android/app/src/main/cpp/dsp/decimatorc.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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;
|
||||
}
|
||||
58
android/app/src/main/cpp/dsp/decimatorc.h
Normal file
58
android/app/src/main/cpp/dsp/decimatorc.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Half-band centered decimators with Complex (i.e. omplex<float>) 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<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator2; // 1st stages
|
||||
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator4; // 2nd stages
|
||||
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator8; // 3rd stages
|
||||
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator16; // 4th stages
|
||||
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> m_decimator32; // 5th stages
|
||||
IntHalfbandFilterEOF<DECIMATORSXS_HB_FILTER_ORDER, true> 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_
|
||||
4390
android/app/src/main/cpp/dsp/decimators.h
Normal file
4390
android/app/src/main/cpp/dsp/decimators.h
Normal file
File diff suppressed because it is too large
Load Diff
213
android/app/src/main/cpp/dsp/decimatorsff.cpp
Normal file
213
android/app/src/main/cpp/dsp/decimatorsff.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "decimatorsff.h"
|
||||
|
||||
template<>
|
||||
SDRBASE_API void DecimatorsFF<true>::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<false>::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<true>::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<false>::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<true>::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<false>::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<true>::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<false>::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<true>::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<false>::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);
|
||||
}
|
||||
}
|
||||
1139
android/app/src/main/cpp/dsp/decimatorsff.h
Normal file
1139
android/app/src/main/cpp/dsp/decimatorsff.h
Normal file
File diff suppressed because it is too large
Load Diff
214
android/app/src/main/cpp/dsp/decimatorsfi.cpp
Normal file
214
android/app/src/main/cpp/dsp/decimatorsfi.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "decimatorsfi.h"
|
||||
|
||||
template<>
|
||||
SDRBASE_API void DecimatorsFI<true>::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<false>::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<true>::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<false>::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<true>::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<false>::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<true>::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<false>::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<true>::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<false>::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);
|
||||
}
|
||||
}
|
||||
|
||||
1984
android/app/src/main/cpp/dsp/decimatorsfi.h
Normal file
1984
android/app/src/main/cpp/dsp/decimatorsfi.h
Normal file
File diff suppressed because it is too large
Load Diff
1238
android/app/src/main/cpp/dsp/decimatorsif.h
Normal file
1238
android/app/src/main/cpp/dsp/decimatorsif.h
Normal file
File diff suppressed because it is too large
Load Diff
3254
android/app/src/main/cpp/dsp/decimatorsu.h
Normal file
3254
android/app/src/main/cpp/dsp/decimatorsu.h
Normal file
File diff suppressed because it is too large
Load Diff
41
android/app/src/main/cpp/dsp/devicesamplemimo.cpp
Normal file
41
android/app/src/main/cpp/dsp/devicesamplemimo.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
176
android/app/src/main/cpp/dsp/devicesamplemimo.h
Normal file
176
android/app/src/main/cpp/dsp/devicesamplemimo.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_DEVICESAMPLEMIMO_H_
|
||||
#define SDRBASE_DSP_DEVICESAMPLEMIMO_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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_
|
||||
113
android/app/src/main/cpp/dsp/devicesamplesink.cpp
Normal file
113
android/app/src/main/cpp/dsp/devicesamplesink.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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
|
||||
);
|
||||
}
|
||||
|
||||
162
android/app/src/main/cpp/dsp/devicesamplesink.h
Normal file
162
android/app/src/main/cpp/dsp/devicesamplesink.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_DEVICESAMPLESINK_H_
|
||||
#define SDRBASE_DSP_DEVICESAMPLESINK_H_
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#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_ */
|
||||
115
android/app/src/main/cpp/dsp/devicesamplesource.cpp
Normal file
115
android/app/src/main/cpp/dsp/devicesamplesource.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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
|
||||
);
|
||||
}
|
||||
178
android/app/src/main/cpp/dsp/devicesamplesource.h
Normal file
178
android/app/src/main/cpp/dsp/devicesamplesource.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_SAMPLESOURCE_H
|
||||
#define INCLUDE_SAMPLESOURCE_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QByteArray>
|
||||
|
||||
#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
|
||||
247
android/app/src/main/cpp/dsp/devicesamplestatic.cpp
Normal file
247
android/app/src/main/cpp/dsp/devicesamplestatic.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<<log2Decim) << "S/s"
|
||||
<< " center freq position code: " << fcPos
|
||||
<< " image frequency: " << f_img << "Hz";
|
||||
|
||||
return deviceCenterFrequency;
|
||||
}
|
||||
|
||||
int64_t DeviceSampleStatic::calculateSourceCenterFrequency(
|
||||
uint64_t deviceCenterFrequency,
|
||||
int64_t transverterDeltaFrequency,
|
||||
int log2Decim,
|
||||
fcPos_t fcPos,
|
||||
uint32_t devSampleRate,
|
||||
FrequencyShiftScheme frequencyShiftScheme,
|
||||
bool transverterMode)
|
||||
{
|
||||
qint64 centerFrequency = deviceCenterFrequency;
|
||||
centerFrequency += calculateSourceFrequencyShift(log2Decim, fcPos, devSampleRate, frequencyShiftScheme);
|
||||
centerFrequency += transverterMode ? transverterDeltaFrequency : 0;
|
||||
centerFrequency = centerFrequency < 0 ? 0 : centerFrequency;
|
||||
|
||||
qDebug() << "DeviceSampleStatic::calculateSourceCenterFrequency:"
|
||||
<< " frequencyShiftScheme: " << frequencyShiftScheme
|
||||
<< " desired center freq: " << centerFrequency << " Hz"
|
||||
<< " device center freq: " << deviceCenterFrequency << " Hz"
|
||||
<< " device sample rate: " << devSampleRate << "S/s"
|
||||
<< " Actual sample rate: " << devSampleRate/(1<<log2Decim) << "S/s"
|
||||
<< " center freq position code: " << fcPos;
|
||||
|
||||
return centerFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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| |
|
||||
* ^ ^
|
||||
*/
|
||||
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<<log2Interp) << "S/s"
|
||||
<< " center freq position code: " << fcPos
|
||||
<< " image frequency: " << f_img << "Hz";
|
||||
|
||||
return deviceCenterFrequency;
|
||||
}
|
||||
|
||||
int64_t DeviceSampleStatic::calculateSinkCenterFrequency(
|
||||
uint64_t deviceCenterFrequency,
|
||||
int64_t transverterDeltaFrequency,
|
||||
int log2Interp,
|
||||
fcPos_t fcPos,
|
||||
uint32_t devSampleRate,
|
||||
bool transverterMode)
|
||||
{
|
||||
int64_t centerFrequency = deviceCenterFrequency;
|
||||
centerFrequency += calculateSinkFrequencyShift(log2Interp, fcPos, devSampleRate);
|
||||
centerFrequency += transverterMode ? transverterDeltaFrequency : 0;
|
||||
centerFrequency = centerFrequency < 0 ? 0 : centerFrequency;
|
||||
|
||||
qDebug() << "DeviceSampleStatic::calculateSinkCenterFrequency:"
|
||||
<< " desired center freq: " << centerFrequency << " Hz"
|
||||
<< " device center freq: " << deviceCenterFrequency << " Hz"
|
||||
<< " device sample rate: " << devSampleRate << "S/s"
|
||||
<< " Actual sample rate: " << devSampleRate/(1<<log2Interp) << "S/s"
|
||||
<< " center freq position code: " << fcPos;
|
||||
|
||||
return centerFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
int DeviceSampleStatic::calculateSinkFrequencyShift(
|
||||
int log2Interp,
|
||||
fcPos_t fcPos,
|
||||
uint32_t devSampleRate)
|
||||
{
|
||||
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 (log2Interp == 0) {
|
||||
return 0;
|
||||
} else if (log2Interp == 1) {
|
||||
return sign * (halfSampleRate / 2);
|
||||
} else if (log2Interp == 2) {
|
||||
return sign * ((halfSampleRate * 3) / 4);
|
||||
} else if (log2Interp == 3) {
|
||||
return sign * ((halfSampleRate * 5) / 8);
|
||||
} else if (log2Interp == 4) {
|
||||
return sign * ((halfSampleRate * 11) / 16);
|
||||
} else if (log2Interp == 5) {
|
||||
return sign * ((halfSampleRate * 21) / 32);
|
||||
} else if (log2Interp == 6) {
|
||||
return sign * ((halfSampleRate * 21) / 64);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
83
android/app/src/main/cpp/dsp/devicesamplestatic.h
Normal file
83
android/app/src/main/cpp/dsp/devicesamplestatic.h
Normal file
@@ -0,0 +1,83 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
};
|
||||
336
android/app/src/main/cpp/dsp/downchannelizer.cpp
Normal file
336
android/app/src/main/cpp/dsp/downchannelizer.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
|
||||
#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<unsigned int> 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<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>),
|
||||
m_workFunction(0),
|
||||
m_mode(mode),
|
||||
m_sse(true)
|
||||
{
|
||||
switch(mode) {
|
||||
case ModeCenter:
|
||||
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateCenter;
|
||||
break;
|
||||
|
||||
case ModeLowerHalf:
|
||||
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateLowerHalf;
|
||||
break;
|
||||
|
||||
case ModeUpperHalf:
|
||||
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateUpperHalf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
DownChannelizer::FilterStage::FilterStage(Mode mode) :
|
||||
m_filter(new IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>),
|
||||
m_workFunction(0),
|
||||
m_mode(mode),
|
||||
m_sse(true)
|
||||
{
|
||||
switch(mode) {
|
||||
case ModeCenter:
|
||||
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateCenter;
|
||||
break;
|
||||
|
||||
case ModeLowerHalf:
|
||||
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::workDecimateLowerHalf;
|
||||
break;
|
||||
|
||||
case ModeUpperHalf:
|
||||
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::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<Real, 3> 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<unsigned int>& stageIndexes)
|
||||
{
|
||||
// filters are described from lower to upper level but the chain is constructed the other way round
|
||||
std::vector<unsigned int>::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;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
android/app/src/main/cpp/dsp/downchannelizer.h
Normal file
97
android/app/src/main/cpp/dsp/downchannelizer.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_DOWNCHANNELIZER_H
|
||||
#define SDRBASE_DSP_DOWNCHANNELIZER_H
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#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<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::*WorkFunction)(Sample* s);
|
||||
IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER, true>* m_filter;
|
||||
#else
|
||||
typedef bool (IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>::*WorkFunction)(Sample* s);
|
||||
IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER, true>* 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<FilterStage*> 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<unsigned int>& stageIndexes);
|
||||
void freeFilterChain();
|
||||
void debugFilterChain();
|
||||
};
|
||||
|
||||
#endif // SDRBASE_DSP_DOWNCHANNELIZER_H
|
||||
48
android/app/src/main/cpp/dsp/dspcommands.cpp
Normal file
48
android/app/src/main/cpp/dsp/dspcommands.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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)
|
||||
400
android/app/src/main/cpp/dsp/dspcommands.h
Normal file
400
android/app/src/main/cpp/dsp/dspcommands.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DSPCOMMANDS_H
|
||||
#define INCLUDE_DSPCOMMANDS_H
|
||||
|
||||
#include <QString>
|
||||
#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
|
||||
1351
android/app/src/main/cpp/dsp/dspdevicemimoengine.cpp
Normal file
1351
android/app/src/main/cpp/dsp/dspdevicemimoengine.cpp
Normal file
File diff suppressed because it is too large
Load Diff
369
android/app/src/main/cpp/dsp/dspdevicemimoengine.h
Normal file
369
android/app/src/main/cpp/dsp/dspdevicemimoengine.h
Normal file
@@ -0,0 +1,369 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019-2020, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_
|
||||
#define SDRBASE_DSP_DSPDEVICEMIMOENGINE_H_
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#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<int32_t, int64_t, 1024> m_iBeta;
|
||||
MovingAverageUtil<int32_t, int64_t, 1024> m_qBeta;
|
||||
#if IMBALANCE_INT
|
||||
// Fixed point DC + IQ corrections
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgII;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgIQ;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgPhi;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgII2;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgQQ2;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgAmp;
|
||||
#else
|
||||
// Floating point DC + IQ corrections
|
||||
MovingAverageUtil<float, double, 128> m_avgII;
|
||||
MovingAverageUtil<float, double, 128> m_avgIQ;
|
||||
MovingAverageUtil<float, double, 128> m_avgII2;
|
||||
MovingAverageUtil<float, double, 128> m_avgQQ2;
|
||||
MovingAverageUtil<double, double, 128> m_avgPhi;
|
||||
MovingAverageUtil<double, double, 128> 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; //<! Input message queue. Post here.
|
||||
|
||||
using BasebandSampleSinks = std::list<BasebandSampleSink *>;
|
||||
std::vector<BasebandSampleSinks> m_basebandSampleSinks; //!< ancillary sample sinks on main thread (per input stream)
|
||||
std::map<int, bool> m_rxRealElseComplex; //!< map of real else complex indicators for device sources (by input stream)
|
||||
using BasebandSampleSources = std::list<BasebandSampleSource *>;
|
||||
std::vector<BasebandSampleSources> m_basebandSampleSources; //!< channel sample sources (per output stream)
|
||||
std::map<int, bool> m_txRealElseComplex; //!< map of real else complex indicators for device sinks (by input stream)
|
||||
std::vector<IncrementalVector<Sample>> m_sourceSampleBuffers;
|
||||
std::vector<IncrementalVector<Sample>> m_sourceZeroBuffers;
|
||||
unsigned int m_sumIndex; //!< channel index when summing channels
|
||||
|
||||
using MIMOChannels = std::list<MIMOChannel *>;
|
||||
MIMOChannels m_mimoChannels; //!< MIMO channels
|
||||
|
||||
std::vector<SourceCorrection> 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_
|
||||
526
android/app/src/main/cpp/dsp/dspdevicesinkengine.cpp
Normal file
526
android/app/src/main/cpp/dsp/dspdevicesinkengine.cpp
Normal file
@@ -0,0 +1,526 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdio.h>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
android/app/src/main/cpp/dsp/dspdevicesinkengine.h
Normal file
134
android/app/src/main/cpp/dsp/dspdevicesinkengine.h
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_DSPDEVICESINKENGINE_H_
|
||||
#define SDRBASE_DSP_DSPDEVICESINKENGINE_H_
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#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; //<! Input message queue. Post here.
|
||||
|
||||
State m_state;
|
||||
|
||||
QString m_errorMessage;
|
||||
QString m_deviceDescription;
|
||||
|
||||
DeviceSampleSink* m_deviceSampleSink;
|
||||
int m_sampleSinkSequence;
|
||||
|
||||
using BasebandSampleSources = std::list<BasebandSampleSource *>;
|
||||
BasebandSampleSources m_basebandSampleSources; //!< baseband sample sources within main thread (usually file input)
|
||||
|
||||
BasebandSampleSink *m_spectrumSink;
|
||||
IncrementalVector<Sample> m_sourceSampleBuffer;
|
||||
IncrementalVector<Sample> 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_ */
|
||||
659
android/app/src/main/cpp/dsp/dspdevicesourceengine.cpp
Normal file
659
android/app/src/main/cpp/dsp/dspdevicesourceengine.cpp
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015-2019, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dspdevicesourceengine.h"
|
||||
|
||||
#include <dsp/basebandsamplesink.h>
|
||||
#include <dsp/devicesamplesource.h>
|
||||
#include <stdio.h>
|
||||
#include <QDebug>
|
||||
#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); // <I", I">
|
||||
m_avgIQ((xi*xq)>>28); // <I", Q">
|
||||
|
||||
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); // <I, I>
|
||||
m_avgQQ2((yq*yq)>>28); // <Q, Q>
|
||||
|
||||
if ((int64_t) m_avgQQ2 != 0)
|
||||
{
|
||||
int64_t a = (((int64_t) m_avgII2)<<28) / (int64_t) m_avgQQ2;
|
||||
Fixed<int64_t, 28> fA(Fixed<int64_t, 28>::internal(), a);
|
||||
Fixed<int64_t, 28> sqrtA = sqrt((Fixed<int64_t, 28>) 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); // <I", I">
|
||||
m_avgIQ(xi*xq); // <I", Q">
|
||||
|
||||
|
||||
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); // <I, I>
|
||||
m_avgQQ2(yq*yq); // <Q, Q>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
151
android/app/src/main/cpp/dsp/dspdevicesourceengine.h
Normal file
151
android/app/src/main/cpp/dsp/dspdevicesourceengine.h
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015-2019, 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DSPDEVICEENGINE_H
|
||||
#define INCLUDE_DSPDEVICEENGINE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#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; //<! Input message queue. Post here.
|
||||
|
||||
State m_state;
|
||||
|
||||
QString m_errorMessage;
|
||||
QString m_deviceDescription;
|
||||
|
||||
DeviceSampleSource* m_deviceSampleSource;
|
||||
int m_sampleSourceSequence;
|
||||
|
||||
using BasebandSampleSinks = std::list<BasebandSampleSink *>;
|
||||
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<int32_t, int64_t, 1024> m_iBeta;
|
||||
MovingAverageUtil<int32_t, int64_t, 1024> m_qBeta;
|
||||
|
||||
#if IMBALANCE_INT
|
||||
// Fixed point DC + IQ corrections
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgII;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgIQ;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgPhi;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgII2;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgQQ2;
|
||||
MovingAverageUtil<int64_t, int64_t, 128> m_avgAmp;
|
||||
|
||||
#else
|
||||
// Floating point DC + IQ corrections
|
||||
MovingAverageUtil<float, double, 128> m_avgII;
|
||||
MovingAverageUtil<float, double, 128> m_avgIQ;
|
||||
MovingAverageUtil<float, double, 128> m_avgII2;
|
||||
MovingAverageUtil<float, double, 128> m_avgQQ2;
|
||||
MovingAverageUtil<double, double, 128> m_avgPhi;
|
||||
MovingAverageUtil<double, double, 128> 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
|
||||
235
android/app/src/main/cpp/dsp/dspengine.cpp
Normal file
235
android/app/src/main/cpp/dsp/dspengine.cpp
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QGlobalStatic>
|
||||
#include <QThread>
|
||||
|
||||
#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
|
||||
}
|
||||
103
android/app/src/main/cpp/dsp/dspengine.h
Normal file
103
android/app/src/main/cpp/dsp/dspengine.h
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DSPENGINE_H
|
||||
#define INCLUDE_DSPENGINE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <vector>
|
||||
|
||||
#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<DSPDeviceSourceEngine*> m_deviceSourceEngines;
|
||||
unsigned int m_deviceSourceEnginesUIDSequence;
|
||||
QList<DSPDeviceSinkEngine*> m_deviceSinkEngines;
|
||||
unsigned int m_deviceSinkEnginesUIDSequence;
|
||||
QList<DSPDeviceMIMOEngine*> m_deviceMIMOEngines;
|
||||
unsigned int m_deviceMIMOEnginesUIDSequence;
|
||||
QList<DeviceEngineReference> 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
|
||||
102
android/app/src/main/cpp/dsp/dsptypes.h
Normal file
102
android/app/src/main/cpp/dsp/dsptypes.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_DSPTYPES_H
|
||||
#define INCLUDE_DSPTYPES_H
|
||||
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
#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<Real> Complex;
|
||||
typedef std::vector<Complex> 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<Sample> SampleVector;
|
||||
typedef std::vector<FSample> FSampleVector;
|
||||
typedef std::vector<AudioSample> AudioVector;
|
||||
|
||||
#endif // INCLUDE_DSPTYPES_H
|
||||
128
android/app/src/main/cpp/dsp/fftcorr.cpp
Normal file
128
android/app/src/main/cpp/dsp/fftcorr.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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++];
|
||||
}
|
||||
60
android/app/src/main/cpp/dsp/fftcorr.h
Normal file
60
android/app/src/main/cpp/dsp/fftcorr.h
Normal file
@@ -0,0 +1,60 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_FFTCORR2_H_
|
||||
#define SDRBASE_DSP_FFTCORR2_H_
|
||||
|
||||
#include <complex>
|
||||
|
||||
#include "dsp/fftwindow.h"
|
||||
#include "export.h"
|
||||
|
||||
class FFTEngine;
|
||||
|
||||
class SDRBASE_API fftcorr {
|
||||
public:
|
||||
typedef std::complex<float> 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_ */
|
||||
118
android/app/src/main/cpp/dsp/fftengine.cpp
Normal file
118
android/app/src/main/cpp/dsp/fftengine.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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;
|
||||
}
|
||||
53
android/app/src/main/cpp/dsp/fftengine.h
Normal file
53
android/app/src/main/cpp/dsp/fftengine.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FFTENGINE_H
|
||||
#define INCLUDE_FFTENGINE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#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
|
||||
153
android/app/src/main/cpp/dsp/fftfactory.cpp
Normal file
153
android/app/src/main/cpp/dsp/fftfactory.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QMutexLocker>
|
||||
#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<<log2Size;
|
||||
m_fftEngineBySize.insert(std::pair<unsigned int, std::vector<AllocatedEngine>>(fftSize, std::vector<AllocatedEngine>()));
|
||||
m_invFFTEngineBySize.insert(std::pair<unsigned int, std::vector<AllocatedEngine>>(fftSize, std::vector<AllocatedEngine>()));
|
||||
std::vector<AllocatedEngine>& fftEngines = m_fftEngineBySize[fftSize];
|
||||
std::vector<AllocatedEngine>& 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<unsigned int, std::vector<AllocatedEngine>>& 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<unsigned int, std::vector<AllocatedEngine>>(fftSize, std::vector<AllocatedEngine>()));
|
||||
std::vector<AllocatedEngine>& 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<AllocatedEngine>& 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<unsigned int, std::vector<AllocatedEngine>>& enginesBySize = inverse ?
|
||||
m_invFFTEngineBySize : m_fftEngineBySize;
|
||||
|
||||
if (enginesBySize.find(fftSize) != enginesBySize.end())
|
||||
{
|
||||
std::vector<AllocatedEngine>& 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
android/app/src/main/cpp/dsp/fftfactory.h
Normal file
61
android/app/src/main/cpp/dsp/fftfactory.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _SDRBASE_FFTWFACTORY_H
|
||||
#define _SDRBASE_FFTWFACTORY_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <QRecursiveMutex>
|
||||
#include <QString>
|
||||
|
||||
#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<unsigned int, std::vector<AllocatedEngine>> m_fftEngineBySize;
|
||||
std::map<unsigned int, std::vector<AllocatedEngine>> m_invFFTEngineBySize;
|
||||
QRecursiveMutex m_mutex;
|
||||
};
|
||||
|
||||
#endif // _SDRBASE_FFTWFACTORY_H
|
||||
690
android/app/src/main/cpp/dsp/fftfilt.cpp
Normal file
690
android/app/src/main/cpp/dsp/fftfilt.cpp
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015, 2017-2018, 2020, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// ----------------------------------------------------------------------------
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Augmented with more filter types
|
||||
// Copyright (C) 2015-2022 Edouard Griffiths, F4EXB
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include <memory.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <typeinfo>
|
||||
#include <array>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <dsp/misc.h>
|
||||
#include <dsp/fftfilt.h>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 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<float>(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<std::pair<float, float>>& limits, bool pass, FFTWindow::Function wf)
|
||||
{
|
||||
std::vector<int> canvasNeg(flen2, pass ? 0 : 1); // initialize the negative frequencies filter canvas
|
||||
std::vector<int> 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<std::pair<int,int>> indexesNegList;
|
||||
std::vector<std::pair<int,int>> 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<int,int>{i, defaultSecond});
|
||||
}
|
||||
|
||||
if ((canvasNeg[i] == 0) && (cn == 1)) {
|
||||
indexesNegList.back().second = i;
|
||||
}
|
||||
|
||||
if ((canvasPos[i] == 1) && (cp == 0)) {
|
||||
indexesPosList.push_back(std::pair<int,int>{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();
|
||||
}
|
||||
|
||||
153
android/app/src/main/cpp/dsp/fftfilt.h
Normal file
153
android/app/src/main/cpp/dsp/fftfilt.h
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015-2018, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* Filters from Fldigi.
|
||||
*/
|
||||
|
||||
#ifndef _FFTFILT_H
|
||||
#define _FFTFILT_H
|
||||
|
||||
#include <complex>
|
||||
#include <cmath>
|
||||
|
||||
#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<float> 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<std::pair<float, float>>& limits, bool pass = true, FFTWindow::Function wf = FFTWindow::Blackman);
|
||||
void create_filter(const std::vector<std::pair<float, float>>& 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<float> *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<float> 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
|
||||
148
android/app/src/main/cpp/dsp/fftnr.cpp
Normal file
148
android/app/src/main/cpp/dsp/fftnr.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
72
android/app/src/main/cpp/dsp/fftnr.h
Normal file
72
android/app/src/main/cpp/dsp/fftnr.h
Normal file
@@ -0,0 +1,72 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _FFTNR_H
|
||||
#define _FFTNR_H
|
||||
|
||||
#include <complex>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class SDRBASE_API FFTNoiseReduction {
|
||||
public:
|
||||
typedef std::complex<float> 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
|
||||
127
android/app/src/main/cpp/dsp/fftwengine.cpp
Normal file
127
android/app/src/main/cpp/dsp/fftwengine.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#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<Complex*>(m_currentPlan->in);
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::out()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
return reinterpret_cast<Complex*>(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();
|
||||
}
|
||||
66
android/app/src/main/cpp/dsp/fftwengine.h
Normal file
66
android/app/src/main/cpp/dsp/fftwengine.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FFTWENGINE_H
|
||||
#define INCLUDE_FFTWENGINE_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
|
||||
#include <fftw3.h>
|
||||
#include <list>
|
||||
#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<Plan*> Plans;
|
||||
Plans m_plans;
|
||||
Plan* m_currentPlan;
|
||||
bool m_reuse;
|
||||
|
||||
void freeAll();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FFTWENGINE_H
|
||||
129
android/app/src/main/cpp/dsp/fftwindow.cpp
Normal file
129
android/app/src/main/cpp/dsp/fftwindow.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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<Real>& in, std::vector<Real>* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++) {
|
||||
(*out)[i] = in[i] * m_window[i];
|
||||
}
|
||||
}
|
||||
|
||||
void FFTWindow::apply(const std::vector<Complex>& in, std::vector<Complex>* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++) {
|
||||
(*out)[i] = in[i] * m_window[i];
|
||||
}
|
||||
}
|
||||
|
||||
void FFTWindow::apply(std::vector<Complex>& 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];
|
||||
}
|
||||
}
|
||||
|
||||
146
android/app/src/main/cpp/dsp/fftwindow.h
Normal file
146
android/app/src/main/cpp/dsp/fftwindow.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FFTWINDOW_H
|
||||
#define INCLUDE_FFTWINDOW_H
|
||||
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#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<Real>& in, std::vector<Real>* out);
|
||||
void apply(const std::vector<Complex>& in, std::vector<Complex>* out);
|
||||
void apply(std::vector<Complex>& 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<float> 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
|
||||
250
android/app/src/main/cpp/dsp/filerecord.cpp
Normal file
250
android/app/src/main/cpp/dsp/filerecord.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2018 beta-tester <alpha-beta-release@gmx.net> //
|
||||
// Copyright (C) 2020 Felix Schneider <felix@fx-schneider.de> //
|
||||
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2021 Andreas Baulig <free.geronimo@hotmail.de> //
|
||||
// Copyright (C) 2021 Christoph Berg <myon@debian.org> //
|
||||
// Copyright (C) 2022 CRD716 <crd716@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
|
||||
#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<const char*>(&*(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));
|
||||
}
|
||||
97
android/app/src/main/cpp/dsp/filerecord.h
Normal file
97
android/app/src/main/cpp/dsp/filerecord.h
Normal file
@@ -0,0 +1,97 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2016, 2018-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2018 beta-tester <alpha-beta-release@gmx.net> //
|
||||
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2022 CRD716 <crd716@gmail.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FILERECORD_H
|
||||
#define INCLUDE_FILERECORD_H
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include <dsp/basebandsamplesink.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <ctime>
|
||||
|
||||
#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
|
||||
88
android/app/src/main/cpp/dsp/filerecordinterface.cpp
Normal file
88
android/app/src/main/cpp/dsp/filerecordinterface.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
android/app/src/main/cpp/dsp/filerecordinterface.h
Normal file
79
android/app/src/main/cpp/dsp/filerecordinterface.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FILERECORD_INTERFACE_H
|
||||
#define INCLUDE_FILERECORD_INTERFACE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
|
||||
#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
|
||||
52
android/app/src/main/cpp/dsp/filtermbe.cpp
Normal file
52
android/app/src/main/cpp/dsp/filtermbe.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2015 John Greb <hexameron@spam.no> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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);
|
||||
}
|
||||
91
android/app/src/main/cpp/dsp/filtermbe.h
Normal file
91
android/app/src/main/cpp/dsp/filtermbe.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2015 John Greb <hexameron@spam.no> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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<float, 2> m_filterLP;
|
||||
IIRFilter<float, 2> 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_ */
|
||||
88
android/app/src/main/cpp/dsp/filterrc.cpp
Normal file
88
android/app/src/main/cpp/dsp/filterrc.cpp
Normal file
@@ -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 <hexameron@spam.no> //
|
||||
// Copyright (C) 2015, 2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
#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;
|
||||
}
|
||||
80
android/app/src/main/cpp/dsp/filterrc.h
Normal file
80
android/app/src/main/cpp/dsp/filterrc.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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_ */
|
||||
73
android/app/src/main/cpp/dsp/firfilter.cpp
Normal file
73
android/app/src/main/cpp/dsp/firfilter.cpp
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "firfilter.h"
|
||||
#include <stdio.h>
|
||||
|
||||
namespace FirFilterGenerators
|
||||
{
|
||||
|
||||
void generateLowPassFilter(int nTaps, double sampleRate, double cutoff, std::vector<Real> &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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
144
android/app/src/main/cpp/dsp/firfilter.h
Normal file
144
android/app/src/main/cpp/dsp/firfilter.h
Normal file
@@ -0,0 +1,144 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#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<Real> &taps);
|
||||
};
|
||||
|
||||
template <class Type>
|
||||
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<Real> m_taps;
|
||||
std::vector<Type> m_samples;
|
||||
size_t m_ptr;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct Lowpass : public FirFilter<T>
|
||||
{
|
||||
public:
|
||||
void create(int nTaps, double sampleRate, double cutoff)
|
||||
{
|
||||
this->init(nTaps);
|
||||
FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, cutoff, this->m_taps);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct Bandpass : public FirFilter<T>
|
||||
{
|
||||
void create(int nTaps, double sampleRate, double lowCutoff, double highCutoff)
|
||||
{
|
||||
this->init(nTaps);
|
||||
FirFilterGenerators::generateLowPassFilter(nTaps, sampleRate, highCutoff, this->m_taps);
|
||||
std::vector<Real> 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 <class T>
|
||||
struct Highpass : public FirFilter<T>
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
80
android/app/src/main/cpp/dsp/fmpreemphasis.cpp
Normal file
80
android/app/src/main/cpp/dsp/fmpreemphasis.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// Copyright (C) 2020 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmath>
|
||||
#include <QDebug>
|
||||
#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;
|
||||
}
|
||||
58
android/app/src/main/cpp/dsp/fmpreemphasis.h
Normal file
58
android/app/src/main/cpp/dsp/fmpreemphasis.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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_ */
|
||||
93
android/app/src/main/cpp/dsp/freqlockcomplex.cpp
Normal file
93
android/app/src/main/cpp/dsp/freqlockcomplex.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "freqlockcomplex.h"
|
||||
#include <cmath>
|
||||
|
||||
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<float> 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;
|
||||
}
|
||||
|
||||
62
android/app/src/main/cpp/dsp/freqlockcomplex.h
Normal file
62
android/app/src/main/cpp/dsp/freqlockcomplex.h
Normal file
@@ -0,0 +1,62 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2018-2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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<float>& 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<float> 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_ */
|
||||
122
android/app/src/main/cpp/dsp/gaussian.h
Normal file
122
android/app/src/main/cpp/dsp/gaussian.h
Normal file
@@ -0,0 +1,122 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2020-2021 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_GAUSSIAN_H
|
||||
#define INCLUDE_GAUSSIAN_H
|
||||
|
||||
#include <math.h>
|
||||
#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 Type> 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<Real> m_taps;
|
||||
std::vector<Type> m_samples;
|
||||
unsigned int m_ptr;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_GAUSSIAN_H
|
||||
3403
android/app/src/main/cpp/dsp/gfft.h
Normal file
3403
android/app/src/main/cpp/dsp/gfft.h
Normal file
File diff suppressed because it is too large
Load Diff
44
android/app/src/main/cpp/dsp/glscopeinterface.h
Normal file
44
android/app/src/main/cpp/dsp/glscopeinterface.h
Normal file
@@ -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 <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRBASE_DSP_GLSCOPEINTERFACE_H_
|
||||
#define SDRBASE_DSP_GLSCOPEINTERFACE_H_
|
||||
|
||||
#include "glscopesettings.h"
|
||||
|
||||
class GLScopeInterface
|
||||
{
|
||||
public:
|
||||
GLScopeInterface() {}
|
||||
virtual ~GLScopeInterface() {}
|
||||
virtual void setTraces(std::vector<GLScopeSettings::TraceData>* tracesData, std::vector<float *>* traces) = 0;
|
||||
virtual void newTraces(std::vector<float *>* traces, int traceIndex, std::vector<Projector::ProjectionType>* 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_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user