Compare commits
24 Commits
v0.1.6-flu
...
v0.8.0-flu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aa19ada14 | ||
|
|
356738ac10 | ||
|
|
06aa8491b4 | ||
|
|
6073ea615e | ||
|
|
5533df92b5 | ||
|
|
24c6abd4f3 | ||
|
|
5b3960f7d6 | ||
|
|
33e790957e | ||
|
|
88e3636c3f | ||
|
|
cc2a495984 | ||
|
|
6718ef7129 | ||
|
|
bfd05bd249 | ||
|
|
8d3366fbf9 | ||
|
|
9b0e9dcacf | ||
|
|
c3e97332fd | ||
|
|
b1d8d5e029 | ||
|
|
77501af2f5 | ||
|
|
64401a6ce9 | ||
|
|
72f9dfe17b | ||
|
|
bf850eed38 | ||
|
|
56689fc993 | ||
|
|
ba373f749a | ||
|
|
23ab5ec746 | ||
|
|
10825171fd |
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) 设备接收并显示列车预警消息,功能包括:
|
LBJ Console 是一款应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
||||||
|
|
||||||
- 接收列车预警消息,支持可选的手机推送通知。
|
- 接收列车预警消息,支持可选的手机推送通知。
|
||||||
|
- 监控指定列车的轨迹,在地图上显示。
|
||||||
- 在地图上显示预警消息的 GPS 信息。
|
- 在地图上显示预警消息的 GPS 信息。
|
||||||
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
||||||
|
- [WIP] 从 RTL-TCP 获取数据。
|
||||||
|
|
||||||
[android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。
|
[android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。
|
||||||
|
|
||||||
|
|
||||||
## 数据文件
|
## 数据文件
|
||||||
|
|
||||||
LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示:
|
LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示:
|
||||||
|
|
||||||
- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。
|
- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。
|
||||||
- `loco_type_info.csv`:包含机车类型编码信息,格式为 `机车类型编码前缀,机车类型`。
|
- `loco_type_info.csv`:包含机车类型编码信息,格式为 `机车类型编码前缀,机车类型`。
|
||||||
- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。
|
- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。
|
||||||
|
|
||||||
数据来源于网络,可能存在错误或不完整,欢迎通过提交 Pull Request 共同完善数据准确性。
|
数据来源于网络,可能存在错误或不完整,欢迎通过提交 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)授权。
|
该项目采用 GNU 通用公共许可证 v3.0(GPLv3)授权。
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ if (flutterVersionName == null) {
|
|||||||
android {
|
android {
|
||||||
namespace = "org.noxylva.lbjconsole.flutter"
|
namespace = "org.noxylva.lbjconsole.flutter"
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
ndkVersion = "26.1.10909125"
|
ndkVersion = "28.1.13356709"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
@@ -72,6 +72,12 @@ android {
|
|||||||
universalApk true
|
universalApk true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path "src/main/cpp/CMakeLists.txt"
|
||||||
|
version "3.22.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" android:usesPermissionFlags="neverForLocation"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" android:usesPermissionFlags="neverForLocation"/>
|
||||||
@@ -14,10 +15,11 @@
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||||
<application
|
<application
|
||||||
android:label="lbjconsole"
|
android:label="LBJ Console"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
@@ -47,6 +49,15 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
|
<!-- 前台服务配置 -->
|
||||||
|
<service
|
||||||
|
android:name="id.flutter.flutter_background_service.BackgroundService"
|
||||||
|
android:foregroundServiceType="connectedDevice|dataSync"
|
||||||
|
android:exported="false"
|
||||||
|
android:stopWithTask="false"
|
||||||
|
android:enabled="true"
|
||||||
|
tools:replace="android:exported"/>
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
|||||||
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user