mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-09 10:35:21 +02:00
Compare commits
275 Commits
0.2.1_alph
...
0.2.5_beta
Author | SHA1 | Date | |
---|---|---|---|
a2d93915e8 | |||
29e9db184f | |||
2f93c7ae58 | |||
4abfe407da | |||
9b27e81091 | |||
39787743fd | |||
22e47807b8 | |||
898525a6d8 | |||
1ebcfe7d80 | |||
1dbc39b970 | |||
80f5f6c288 | |||
b18acd469f | |||
cefcd18269 | |||
4de3ac176d | |||
afd5699ff1 | |||
b79461e3ce | |||
de6ab8ecdf | |||
d0180d42a8 | |||
2e504b40f6 | |||
9b00304c29 | |||
979928ded8 | |||
7c4e442432 | |||
0dd445f101 | |||
f217804838 | |||
9a630fff06 | |||
db508214d7 | |||
8e764f48ae | |||
2583063f5f | |||
dd4ec22b39 | |||
42dbcec93f | |||
9bbf634f5d | |||
d6b09759de | |||
5bb8a943ad | |||
b370eda0d5 | |||
69bcbf6f27 | |||
04823abb83 | |||
7269a0ea12 | |||
153b58fbbd | |||
149af55e61 | |||
9cac95fd82 | |||
09498f3b18 | |||
bb919d0f32 | |||
6d0abd73a5 | |||
717f2a822b | |||
8946b4b4b6 | |||
db279d2b36 | |||
bb7965b3c4 | |||
a33fe5a4cc | |||
4a03f0870c | |||
bfe15aff19 | |||
42bc2d01f7 | |||
0cb9fc0df8 | |||
450896b122 | |||
cc0b89dbe2 | |||
c887b96a77 | |||
22541ae0f4 | |||
d83da38d79 | |||
b21f8abbd6 | |||
e9aade4d0d | |||
2bf2fff3d6 | |||
463a22fdfb | |||
3175022b31 | |||
504d910226 | |||
c2769e1a72 | |||
7577253dbf | |||
e4c5b2dbd1 | |||
fafd76ff94 | |||
e354d11820 | |||
a3374c7eca | |||
552b886cea | |||
ff9a19381b | |||
77aacc2e5d | |||
6a1fa2c13b | |||
c3bb64bf6e | |||
da68ab4ed0 | |||
1aedf92bcd | |||
abcf484506 | |||
a93681a980 | |||
a08758ea54 | |||
0a0f5b8e8c | |||
84f67a3ac1 | |||
22d18a9e58 | |||
d1a8425d43 | |||
65d94f03e4 | |||
3a49041f27 | |||
eec2f7c4a0 | |||
720df5ce89 | |||
d7cea16d4a | |||
d5c0fdd525 | |||
4a86d6073c | |||
98b6e580b4 | |||
c96c69c112 | |||
bd545feb2c | |||
e90b6656c3 | |||
5099c16a12 | |||
d9dcfa4a88 | |||
1fcd783dd9 | |||
2c84123158 | |||
c3d39029b8 | |||
20b703f8bf | |||
89c579880c | |||
db389372ad | |||
3a6eaf6526 | |||
3e27af472b | |||
7bea6058fe | |||
b02b6c30b5 | |||
46e9266752 | |||
e3db19b16a | |||
774663d70d | |||
2c729bf646 | |||
9b1c9e9e29 | |||
80badebb37 | |||
fc9e155481 | |||
16d8a31c12 | |||
7ba6081cb3 | |||
c3a8865dd3 | |||
929ca50b06 | |||
e5123dd8bf | |||
fe1de4bed9 | |||
c612620ca5 | |||
ca9d2c01af | |||
51d90c1898 | |||
a6a4193fbb | |||
f4f8c77ffa | |||
9b8c1a3072 | |||
92b77904f6 | |||
9805e4a395 | |||
6a01c9d426 | |||
48df92c8a5 | |||
5bb2f9bf05 | |||
e5dbac4345 | |||
618d4ac4cc | |||
19e516f206 | |||
afadb71d64 | |||
b3d1eabbad | |||
eac0a7a13f | |||
e06ed84330 | |||
4eae7c3ba5 | |||
61a612cf30 | |||
f1084157a3 | |||
de3b056133 | |||
02ae50905d | |||
0a5fd5c271 | |||
ef968ac1fb | |||
3156236745 | |||
5d320fdd53 | |||
cee6af1e14 | |||
35c7f0e3cf | |||
fc9bc496cb | |||
75f8a45119 | |||
50a73a380d | |||
e62042d26a | |||
c109de3949 | |||
39c87782db | |||
b6566dde14 | |||
ef36283370 | |||
922a226028 | |||
ba81f25933 | |||
da9528576a | |||
3fdd2477e5 | |||
62368e35a7 | |||
82d3431f1d | |||
edbc0c149d | |||
b8987e6d2d | |||
6296b8865b | |||
2df185e340 | |||
6262c64daa | |||
0fe5af9816 | |||
e94888d533 | |||
6130428989 | |||
5400a4e18a | |||
0d45217dfd | |||
fa1e647235 | |||
d637cb9e75 | |||
406f18bf11 | |||
72611b5fa7 | |||
313b786d88 | |||
801a56787f | |||
62868b2533 | |||
4bf88739b5 | |||
087380c966 | |||
fbd7321b48 | |||
f6cfe83d45 | |||
71f6be8d08 | |||
6e5450ed24 | |||
027054b57e | |||
3b6a3ff94d | |||
46d5b8a750 | |||
ac068036b8 | |||
ace1fe1e5e | |||
14a8e81662 | |||
eff7bbdd5a | |||
60342de9c0 | |||
47b04ffef4 | |||
1507e6ec31 | |||
524f20bc2f | |||
2c4d7cbf09 | |||
5fedda08d7 | |||
2056eae139 | |||
48a8b04eaa | |||
51ee02f9da | |||
ab8ce4c53f | |||
2aaf254565 | |||
109696c65a | |||
35ef99c6e9 | |||
91d382ca0c | |||
ec234e99a1 | |||
9de585190f | |||
d6b9e1d86a | |||
1ef31f0f8b | |||
c1052b1b28 | |||
e497122c06 | |||
407fcaadc6 | |||
7e6f24d203 | |||
c0825dbeeb | |||
39a65b51fb | |||
acf3fe0297 | |||
70c2ef36f5 | |||
7190acfe9e | |||
bf6210721d | |||
27731f376a | |||
d82260d4d4 | |||
78086a79f4 | |||
aa2caa67ad | |||
709627a738 | |||
649be86cb6 | |||
b56aab8f74 | |||
53ec38766a | |||
dbe811b47a | |||
f08515420e | |||
11913ab683 | |||
1cecc78c0c | |||
6717c43fc2 | |||
e44d20bdbc | |||
e50ed1b960 | |||
cff5987329 | |||
b2191c5d2c | |||
03dc5d2042 | |||
19e07eb767 | |||
c4f49203a1 | |||
9830337103 | |||
eadaf3ce6b | |||
31a95031e4 | |||
cdea80f8c5 | |||
b65bddc1b3 | |||
7759de96da | |||
9d2b60b88e | |||
a9c82ecc81 | |||
3aa8ce80db | |||
294337aa63 | |||
908abf0743 | |||
509e4aa7f7 | |||
13fbd6a800 | |||
b430f88245 | |||
e24e3cbc2a | |||
08292c279e | |||
10f1b992dd | |||
022898c61d | |||
cd7e5cf1bc | |||
f5d6e609d7 | |||
ed49ad6f7f | |||
f8c9aa1be4 | |||
e364ebac6d | |||
1a42421d92 | |||
e8ae20232a | |||
3216daec2b | |||
0dc14c663a | |||
07d3fa2034 | |||
f1fd6fce7a | |||
355a348ba0 | |||
d85fed00a0 | |||
d47679c5cf | |||
379d6f8101 | |||
43f6802199 | |||
6bfe15b14e |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: ryzerth
|
45
.github/workflows/cmake.yml
vendored
Normal file
45
.github/workflows/cmake.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Linux Build
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Update repositories
|
||||
run: sudo apt update
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt install libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev portaudio19-dev libhackrf-dev
|
||||
|
||||
- name: Create Build Environment
|
||||
# Some projects don't allow in-source building, so create a separate build directory
|
||||
# We'll use this as our working directory for all subsequent commands
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure CMake
|
||||
# Use a bash shell so we can use the same syntax for environment variable
|
||||
# access regardless of the host operating system
|
||||
shell: bash
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
# Note the current convention is to use the -S and -B options here to specify source
|
||||
# and build directories, but this is only available with CMake 3.13 and higher.
|
||||
# The CMake binaries on the Github Actions machines are (as of this writing) 3.12
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
shell: bash
|
||||
# Execute the build. You can specify a specific target with "--target <NAME>"
|
||||
run: cmake --build . --config $BUILD_TYPE
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,3 +1,11 @@
|
||||
build/
|
||||
.vscode/
|
||||
*.old
|
||||
*.dll
|
||||
*.exe
|
||||
*.zip
|
||||
*.wav
|
||||
.DS_Store
|
||||
root_dev/
|
||||
sdrpp_v0.2.5_beta_x64
|
||||
sdrpp_v0.2.5_beta_x64_new_wf
|
101
CMakeLists.txt
101
CMakeLists.txt
@ -1,48 +1,81 @@
|
||||
cmake_minimum_required(VERSION 3.9)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(sdrpp)
|
||||
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
|
||||
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Depedencies: soapysdr)" ON)
|
||||
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Depedencies: libairspyhf)" ON)
|
||||
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Depedencies: libairspy)" ON)
|
||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" ON)
|
||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON)
|
||||
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: portaudio)" ON)
|
||||
|
||||
# Compiler config
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17")
|
||||
# Core of SDR++
|
||||
add_subdirectory("core")
|
||||
|
||||
# PothosSDR
|
||||
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
|
||||
# Base modules
|
||||
add_subdirectory("radio")
|
||||
add_subdirectory("recorder")
|
||||
|
||||
# Volk
|
||||
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
|
||||
link_libraries(volk)
|
||||
# Source modules
|
||||
if (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
add_subdirectory("rtl_tcp_source")
|
||||
endif (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
|
||||
# SoapySDR
|
||||
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
|
||||
link_libraries(SoapySDR)
|
||||
if (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
add_subdirectory("spyserver_source")
|
||||
endif (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
|
||||
# Main code
|
||||
include_directories(sdrpp "src/")
|
||||
include_directories(sdrpp "src/imgui")
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
file(GLOB IMGUI "src/imgui/*.cpp")
|
||||
add_executable(sdrpp ${SRC} ${IMGUI})
|
||||
if (OPT_BUILD_SOAPY_SOURCE)
|
||||
add_subdirectory("soapy_source")
|
||||
endif (OPT_BUILD_SOAPY_SOURCE)
|
||||
|
||||
# Glew
|
||||
find_package(GLEW REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE GLEW::GLEW)
|
||||
if (OPT_BUILD_AIRSPYHF_SOURCE)
|
||||
add_subdirectory("airspyhf_source")
|
||||
endif (OPT_BUILD_AIRSPYHF_SOURCE)
|
||||
|
||||
# GLFW3
|
||||
find_package(glfw3 CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE glfw)
|
||||
if (OPT_BUILD_AIRSPY_SOURCE)
|
||||
add_subdirectory("airspy_source")
|
||||
endif (OPT_BUILD_AIRSPY_SOURCE)
|
||||
|
||||
# FFTW3
|
||||
find_package(FFTW3 CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3)
|
||||
find_package(FFTW3f CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3f)
|
||||
find_package(FFTW3l CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3l)
|
||||
if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
|
||||
# PortAudio
|
||||
find_package(portaudio CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE portaudio portaudio_static)
|
||||
if (OPT_BUILD_HACKRF_SOURCE)
|
||||
add_subdirectory("hackrf_source")
|
||||
endif (OPT_BUILD_HACKRF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_AUDIO_SINK)
|
||||
add_subdirectory("audio_sink")
|
||||
endif (OPT_BUILD_AUDIO_SINK)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||
endif (MSVC)
|
||||
|
||||
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||
target_link_libraries(sdrpp PRIVATE sdrpp_core)
|
||||
|
||||
# Copy dynamic libs over
|
||||
if (MSVC)
|
||||
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
endif (MSVC)
|
||||
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"
|
31
airspy_source/CMakeLists.txt
Normal file
31
airspy_source/CMakeLists.txt
Normal file
@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(airspy_source)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
||||
endif (MSVC)
|
||||
|
||||
include_directories("src/")
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
add_library(airspy_source SHARED ${SRC})
|
||||
target_link_libraries(airspy_source PRIVATE sdrpp_core)
|
||||
set_target_properties(airspy_source PROPERTIES PREFIX "")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/")
|
||||
|
||||
target_link_libraries(airspy_source PUBLIC airspy)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBAIRSPYHF REQUIRED libairspy)
|
||||
|
||||
target_include_directories(airspy_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS})
|
||||
target_link_directories(airspy_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS})
|
||||
target_link_libraries(airspy_source PUBLIC ${LIBAIRSPYHF_LIBRARIES})
|
||||
endif (MSVC)
|
597
airspy_source/src/main.cpp
Normal file
597
airspy_source/src/main.cpp
Normal file
@ -0,0 +1,597 @@
|
||||
#include <imgui.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
#include <libairspy/airspy.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO {
|
||||
/* Name: */ "airspy_source",
|
||||
/* Description: */ "Airspy source module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
class AirspySourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
AirspySourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 10000000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
refresh();
|
||||
if (sampleRateList.size() > 0) {
|
||||
sampleRate = sampleRateList[0];
|
||||
}
|
||||
|
||||
// Select device from config
|
||||
config.aquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
selectByString(devSerial);
|
||||
core::setInputSampleRate(sampleRate);
|
||||
|
||||
sigpath::sourceManager.registerSource("Airspy", &handler);
|
||||
}
|
||||
|
||||
~AirspySourceModule() {
|
||||
|
||||
}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
devList.clear();
|
||||
devListTxt = "";
|
||||
|
||||
uint64_t serials[256];
|
||||
int n = airspy_list_devices(serials, 256);
|
||||
|
||||
char buf[1024];
|
||||
for (int i = 0; i < n; i++) {
|
||||
sprintf(buf, "%016" PRIX64, serials[i]);
|
||||
devList.push_back(serials[i]);
|
||||
devListTxt += buf;
|
||||
devListTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
if (devList.size() != 0) {
|
||||
selectBySerial(devList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void selectByString(std::string serial) {
|
||||
char buf[1024];
|
||||
for (int i = 0; i < devList.size(); i++) {
|
||||
sprintf(buf, "%016" PRIX64, devList[i]);
|
||||
std::string str = buf;
|
||||
if (serial == str) {
|
||||
selectBySerial(devList[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
void selectBySerial(uint64_t serial) {
|
||||
selectedSerial = serial;
|
||||
airspy_device* dev;
|
||||
int err = airspy_open_sn(&dev, selectedSerial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, selectedSerial);
|
||||
spdlog::error("Could not open Airspy HF+ {0}", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t sampleRates[256];
|
||||
airspy_get_samplerates(dev, sampleRates, 0);
|
||||
int n = sampleRates[0];
|
||||
airspy_get_samplerates(dev, sampleRates, n);
|
||||
sampleRateList.clear();
|
||||
sampleRateListTxt = "";
|
||||
for (int i = 0; i < n; i++) {
|
||||
sampleRateList.push_back(sampleRates[i]);
|
||||
sampleRateListTxt += getBandwdithScaled(sampleRates[i]);
|
||||
sampleRateListTxt += '\0';
|
||||
}
|
||||
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
selectedSerStr = std::string(buf);
|
||||
|
||||
// Load config here
|
||||
config.aquire();
|
||||
bool created = false;
|
||||
if (!config.conf["devices"].contains(selectedSerStr)) {
|
||||
created = true;
|
||||
config.conf["devices"][selectedSerStr]["sampleRate"] = 10000000;
|
||||
config.conf["devices"][selectedSerStr]["gainMode"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["sensitiveGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["linearGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lnaGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["mixerGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["vgaGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lnaAgc"] = false;
|
||||
config.conf["devices"][selectedSerStr]["mixerAgc"] = false;
|
||||
config.conf["devices"][selectedSerStr]["biasT"] = false;
|
||||
}
|
||||
|
||||
// Load sample rate
|
||||
srId = 0;
|
||||
sampleRate = sampleRateList[0];
|
||||
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
|
||||
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
|
||||
for (int i = 0; i < sampleRateList.size(); i++) {
|
||||
if (sampleRateList[i] == selectedSr) {
|
||||
srId = i;
|
||||
sampleRate = selectedSr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load gains
|
||||
if (config.conf["devices"][selectedSerStr].contains("gainMode")) {
|
||||
gainMode = config.conf["devices"][selectedSerStr]["gainMode"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("sensitiveGain")) {
|
||||
sensitiveGain = config.conf["devices"][selectedSerStr]["sensitiveGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("linearGain")) {
|
||||
linearGain = config.conf["devices"][selectedSerStr]["linearGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lnaGain")) {
|
||||
lnaGain = config.conf["devices"][selectedSerStr]["lnaGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("mixerGain")) {
|
||||
mixerGain = config.conf["devices"][selectedSerStr]["mixerGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("vgaGain")) {
|
||||
vgaGain = config.conf["devices"][selectedSerStr]["vgaGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lnaAgc")) {
|
||||
lnaAgc = config.conf["devices"][selectedSerStr]["lnaAgc"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("mixerAgc")) {
|
||||
mixerAgc = config.conf["devices"][selectedSerStr]["mixerAgc"];
|
||||
}
|
||||
|
||||
// Load Bias-T
|
||||
if (config.conf["devices"][selectedSerStr].contains("biasT")) {
|
||||
biasT = config.conf["devices"][selectedSerStr]["biasT"];
|
||||
}
|
||||
|
||||
config.release(created);
|
||||
|
||||
airspy_close(dev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
spdlog::info("AirspySourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
spdlog::info("AirspySourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
return;
|
||||
}
|
||||
if (_this->selectedSerial == 0) {
|
||||
spdlog::error("Tried to start AirspyHF+ source with null serial");
|
||||
return;
|
||||
}
|
||||
|
||||
int err = airspy_open_sn(&_this->openDev, _this->selectedSerial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
|
||||
spdlog::error("Could not open Airspy {0}", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
airspy_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]);
|
||||
airspy_set_freq(_this->openDev, _this->freq);
|
||||
|
||||
if (_this->gainMode == 0) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
else if (_this->gainMode == 1) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
else if (_this->gainMode == 2) {
|
||||
if (_this->lnaAgc) {
|
||||
airspy_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->mixerAgc) {
|
||||
airspy_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
|
||||
airspy_set_rf_bias(_this->openDev, _this->biasT);
|
||||
|
||||
airspy_start_rx(_this->openDev, callback, _this);
|
||||
|
||||
_this->running = true;
|
||||
spdlog::info("AirspySourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
if (!_this->running) {
|
||||
return;
|
||||
}
|
||||
_this->running = false;
|
||||
_this->stream.stopWriter();
|
||||
airspy_close(_this->openDev);
|
||||
_this->stream.clearWriteStop();
|
||||
spdlog::info("AirspySourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
airspy_set_freq(_this->openDev, freq);
|
||||
}
|
||||
_this->freq = freq;
|
||||
spdlog::info("AirspySourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (_this->running) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(CONCAT("##_airspy_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
|
||||
_this->selectBySerial(_this->devList[_this->devId]);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["device"] = _this->selectedSerStr;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Combo(CONCAT("##_airspy_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) {
|
||||
_this->sampleRate = _this->sampleRateList[_this->srId];
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
|
||||
if (ImGui::Button(CONCAT("Refresh##_airspy_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
|
||||
_this->refresh();
|
||||
config.aquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
_this->selectByString(devSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { style::endDisabled(); }
|
||||
|
||||
ImGui::BeginGroup();
|
||||
ImGui::Columns(3, CONCAT("AirspyGainModeColumns##_", _this->name), false);
|
||||
if (ImGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", _this->name), _this->gainMode == 0)) {
|
||||
_this->gainMode = 0;
|
||||
if (_this->running) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::RadioButton(CONCAT("Linear##_airspy_gm_", _this->name), _this->gainMode == 1)) {
|
||||
_this->gainMode = 1;
|
||||
if (_this->running) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::RadioButton(CONCAT("Free##_airspy_gm_", _this->name), _this->gainMode == 2)) {
|
||||
_this->gainMode = 2;
|
||||
if (_this->running) {
|
||||
if (_this->lnaAgc) {
|
||||
airspy_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->mixerAgc) {
|
||||
airspy_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
ImGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", _this->name), false);
|
||||
ImGui::EndGroup();
|
||||
|
||||
// Gain menus
|
||||
|
||||
if (_this->gainMode == 0) {
|
||||
ImGui::Text("Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) {
|
||||
if (_this->running) {
|
||||
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_this->gainMode == 1) {
|
||||
ImGui::Text("Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_lin_gain_", _this->name), &_this->linearGain, 0, 21)) {
|
||||
if (_this->running) {
|
||||
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_this->gainMode == 2) {
|
||||
// Calculate position of sliders
|
||||
float pos = ImGui::CalcTextSize("Mixer Gain").x + 10;
|
||||
|
||||
if (_this->lnaAgc) { style::beginDisabled(); }
|
||||
ImGui::Text("LNA Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(pos);
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (_this->lnaAgc) { style::endDisabled(); }
|
||||
|
||||
if (_this->mixerAgc) { style::beginDisabled(); }
|
||||
ImGui::Text("Mixer Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(pos);
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (_this->mixerAgc) { style::endDisabled(); }
|
||||
|
||||
ImGui::Text("VGA Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(pos);
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
// AGC Control
|
||||
if (ImGui::Checkbox(CONCAT("LNA AGC##_airspy_", _this->name), &_this->lnaAgc)) {
|
||||
if (_this->running) {
|
||||
if (_this->lnaAgc) {
|
||||
airspy_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (ImGui::Checkbox(CONCAT("Mixer AGC##_airspy_", _this->name), &_this->mixerAgc)) {
|
||||
if (_this->running) {
|
||||
if (_this->mixerAgc) {
|
||||
airspy_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bias T
|
||||
|
||||
if (ImGui::Checkbox(CONCAT("Bias T##_airspy_", _this->name), &_this->biasT)) {
|
||||
if (_this->running) {
|
||||
airspy_set_rf_bias(_this->openDev, _this->biasT);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static int callback(airspy_transfer_t* transfer) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)transfer->ctx;
|
||||
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
|
||||
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
airspy_device* openDev;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
uint64_t selectedSerial = 0;
|
||||
std::string selectedSerStr = "";
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
|
||||
bool biasT = false;
|
||||
|
||||
int lnaGain = 0;
|
||||
int vgaGain = 0;
|
||||
int mixerGain = 0;
|
||||
int linearGain = 0;
|
||||
int sensitiveGain = 0;
|
||||
|
||||
int gainMode = 0;
|
||||
|
||||
bool lnaAgc = false;
|
||||
bool mixerAgc = false;
|
||||
|
||||
std::vector<uint64_t> devList;
|
||||
std::string devListTxt;
|
||||
std::vector<uint32_t> sampleRateList;
|
||||
std::string sampleRateListTxt;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(options::opts.root + "/airspy_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new AirspySourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
delete (AirspySourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
31
airspyhf_source/CMakeLists.txt
Normal file
31
airspyhf_source/CMakeLists.txt
Normal file
@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(airspyhf_source)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
||||
endif (MSVC)
|
||||
|
||||
include_directories("src/")
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
add_library(airspyhf_source SHARED ${SRC})
|
||||
target_link_libraries(airspyhf_source PRIVATE sdrpp_core)
|
||||
set_target_properties(airspyhf_source PROPERTIES PREFIX "")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/")
|
||||
|
||||
target_link_libraries(airspyhf_source PUBLIC airspyhf)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBAIRSPYHF REQUIRED libairspyhf)
|
||||
|
||||
target_include_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS})
|
||||
target_link_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS})
|
||||
target_link_libraries(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARIES})
|
||||
endif (MSVC)
|
393
airspyhf_source/src/main.cpp
Normal file
393
airspyhf_source/src/main.cpp
Normal file
@ -0,0 +1,393 @@
|
||||
#include <imgui.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
#include <libairspyhf/airspyhf.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO {
|
||||
/* Name: */ "airspyhf_source",
|
||||
/* Description: */ "Airspy HF+ source module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
const char* AGG_MODES_STR = "Off\0Low\0High\0";
|
||||
|
||||
class AirspyHFSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
AirspyHFSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 768000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
refresh();
|
||||
|
||||
config.aquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
selectByString(devSerial);
|
||||
core::setInputSampleRate(sampleRate);
|
||||
|
||||
sigpath::sourceManager.registerSource("Airspy HF+", &handler);
|
||||
}
|
||||
|
||||
~AirspyHFSourceModule() {
|
||||
|
||||
}
|
||||
|
||||
enum AGCMode {
|
||||
AGC_MODE_OFF,
|
||||
AGC_MODE_LOW,
|
||||
AGC_MODE_HIGG
|
||||
};
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
devList.clear();
|
||||
devListTxt = "";
|
||||
|
||||
uint64_t serials[256];
|
||||
int n = airspyhf_list_devices(serials, 256);
|
||||
|
||||
char buf[1024];
|
||||
for (int i = 0; i < n; i++) {
|
||||
sprintf(buf, "%016" PRIX64, serials[i]);
|
||||
devList.push_back(serials[i]);
|
||||
devListTxt += buf;
|
||||
devListTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
if (devList.size() != 0) {
|
||||
selectBySerial(devList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void selectByString(std::string serial) {
|
||||
char buf[1024];
|
||||
for (int i = 0; i < devList.size(); i++) {
|
||||
sprintf(buf, "%016" PRIX64, devList[i]);
|
||||
std::string str = buf;
|
||||
if (serial == str) {
|
||||
selectBySerial(devList[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
void selectBySerial(uint64_t serial) {
|
||||
selectedSerial = serial;
|
||||
airspyhf_device_t* dev;
|
||||
int err = airspyhf_open_sn(&dev, selectedSerial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, selectedSerial);
|
||||
spdlog::error("Could not open Airspy HF+ {0}", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t sampleRates[256];
|
||||
airspyhf_get_samplerates(dev, sampleRates, 0);
|
||||
int n = sampleRates[0];
|
||||
airspyhf_get_samplerates(dev, sampleRates, n);
|
||||
sampleRateList.clear();
|
||||
sampleRateListTxt = "";
|
||||
for (int i = 0; i < n; i++) {
|
||||
sampleRateList.push_back(sampleRates[i]);
|
||||
sampleRateListTxt += getBandwdithScaled(sampleRates[i]);
|
||||
sampleRateListTxt += '\0';
|
||||
}
|
||||
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
selectedSerStr = std::string(buf);
|
||||
|
||||
// Load config here
|
||||
config.aquire();
|
||||
bool created = false;
|
||||
if (!config.conf["devices"].contains(selectedSerStr)) {
|
||||
created = true;
|
||||
config.conf["devices"][selectedSerStr]["sampleRate"] = 768000;
|
||||
config.conf["devices"][selectedSerStr]["agcMode"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lna"] = false;
|
||||
config.conf["devices"][selectedSerStr]["attenuation"] = 0;
|
||||
}
|
||||
|
||||
// Load sample rate
|
||||
srId = 0;
|
||||
sampleRate = sampleRateList[0];
|
||||
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
|
||||
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
|
||||
for (int i = 0; i < sampleRateList.size(); i++) {
|
||||
if (sampleRateList[i] == selectedSr) {
|
||||
srId = i;
|
||||
sampleRate = selectedSr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load Gains
|
||||
if (config.conf["devices"][selectedSerStr].contains("agcMode")) {
|
||||
agcMode = config.conf["devices"][selectedSerStr]["agcMode"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lna")) {
|
||||
hfLNA = config.conf["devices"][selectedSerStr]["lna"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("attenuation")) {
|
||||
atten = config.conf["devices"][selectedSerStr]["attenuation"];
|
||||
}
|
||||
|
||||
config.release(created);
|
||||
|
||||
airspyhf_close(dev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
return;
|
||||
}
|
||||
if (_this->selectedSerial == 0) {
|
||||
spdlog::error("Tried to start AirspyHF+ source with null serial");
|
||||
return;
|
||||
}
|
||||
|
||||
int err = airspyhf_open_sn(&_this->openDev, _this->selectedSerial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
|
||||
spdlog::error("Could not open Airspy HF+ {0}", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
airspyhf_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]);
|
||||
airspyhf_set_freq(_this->openDev, _this->freq);
|
||||
airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0));
|
||||
if (_this->agcMode > 0) {
|
||||
airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1);
|
||||
}
|
||||
airspyhf_set_hf_att(_this->openDev, _this->atten / 6);
|
||||
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
|
||||
|
||||
airspyhf_start(_this->openDev, callback, _this);
|
||||
|
||||
_this->running = true;
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
if (!_this->running) {
|
||||
return;
|
||||
}
|
||||
_this->running = false;
|
||||
_this->stream.stopWriter();
|
||||
airspyhf_close(_this->openDev);
|
||||
_this->stream.clearWriteStop();
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
airspyhf_set_freq(_this->openDev, freq);
|
||||
}
|
||||
_this->freq = freq;
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (_this->running) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
|
||||
_this->selectBySerial(_this->devList[_this->devId]);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["device"] = _this->selectedSerStr;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) {
|
||||
_this->sampleRate = _this->sampleRateList[_this->srId];
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
|
||||
if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
|
||||
_this->refresh();
|
||||
config.aquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
_this->selectByString(devSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { style::endDisabled(); }
|
||||
|
||||
ImGui::Text("AGC Mode");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_airspyhf_agc_", _this->name), &_this->agcMode, AGG_MODES_STR)) {
|
||||
if (_this->running) {
|
||||
airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0));
|
||||
if (_this->agcMode > 0) {
|
||||
airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["agcMode"] = _this->agcMode;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("HF LNA");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox(CONCAT("##_airspyhf_lna_", _this->name), &_this->hfLNA)) {
|
||||
if (_this->running) {
|
||||
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lna"] = _this->hfLNA;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Attenuation");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspyhf_attn_", _this->name), &_this->atten, 0, 48, "%d dB")) {
|
||||
_this->atten = (_this->atten / 6) * 6;
|
||||
if (_this->running) {
|
||||
airspyhf_set_hf_att(_this->openDev, _this->atten / 6);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.aquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["attenuation"] = _this->atten;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int callback(airspyhf_transfer_t* transfer) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)transfer->ctx;
|
||||
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
|
||||
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
airspyhf_device_t* openDev;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
uint64_t selectedSerial = 0;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int agcMode = AGC_MODE_OFF;
|
||||
bool hfLNA = false;
|
||||
int atten = 0;
|
||||
std::string selectedSerStr = "";
|
||||
|
||||
std::vector<uint64_t> devList;
|
||||
std::string devListTxt;
|
||||
std::vector<uint32_t> sampleRateList;
|
||||
std::string sampleRateListTxt;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(options::opts.root + "/airspyhf_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new AirspyHFSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
delete (AirspyHFSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
32
audio_sink/CMakeLists.txt
Normal file
32
audio_sink/CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(audio_sink)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
|
||||
endif (MSVC)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include_directories("src/")
|
||||
|
||||
add_library(audio_sink SHARED ${SRC})
|
||||
target_link_libraries(audio_sink PRIVATE sdrpp_core)
|
||||
set_target_properties(audio_sink PROPERTIES PREFIX "")
|
||||
|
||||
if (MSVC)
|
||||
find_package(portaudio CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC portaudio)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0)
|
||||
|
||||
target_include_directories(sdrpp_core PUBLIC ${PORTAUDIO_INCLUDE_DIRS})
|
||||
|
||||
target_link_directories(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARY_DIRS})
|
||||
|
||||
target_link_libraries(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARIES})
|
||||
|
||||
endif (MSVC)
|
290
audio_sink/src/main.cpp
Normal file
290
audio_sink/src/main.cpp
Normal file
@ -0,0 +1,290 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <signal_path/sink.h>
|
||||
#include <portaudio.h>
|
||||
#include <dsp/audio.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO {
|
||||
/* Name: */ "audio_sink",
|
||||
/* Description: */ "Audio sink module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
class AudioSink : SinkManager::Sink {
|
||||
public:
|
||||
struct AudioDevice_t {
|
||||
std::string name;
|
||||
int index;
|
||||
int channels;
|
||||
int srId;
|
||||
std::vector<double> sampleRates;
|
||||
std::string txtSampleRates;
|
||||
};
|
||||
|
||||
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
||||
_stream = stream;
|
||||
_streamName = streamName;
|
||||
s2m.init(_stream->sinkOut);
|
||||
monoRB.init(&s2m.out);
|
||||
stereoRB.init(_stream->sinkOut);
|
||||
|
||||
// Initialize PortAudio
|
||||
devCount = Pa_GetDeviceCount();
|
||||
devId = Pa_GetDefaultOutputDevice();
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
PaStreamParameters outputParams;
|
||||
outputParams.sampleFormat = paFloat32;
|
||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
// Gather hardware info
|
||||
for(int i = 0; i < devCount; i++) {
|
||||
deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if (deviceInfo->maxOutputChannels < 1) {
|
||||
continue;
|
||||
}
|
||||
AudioDevice_t dev;
|
||||
dev.name = deviceInfo->name;
|
||||
dev.index = i;
|
||||
dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
|
||||
dev.sampleRates.clear();
|
||||
dev.txtSampleRates = "";
|
||||
for (int j = 0; j < 6; j++) {
|
||||
outputParams.channelCount = dev.channels;
|
||||
outputParams.device = dev.index;
|
||||
outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
||||
PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
|
||||
if (err != paFormatIsSupported) {
|
||||
continue;
|
||||
}
|
||||
dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
|
||||
dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
|
||||
dev.txtSampleRates += '\0';
|
||||
}
|
||||
if (dev.sampleRates.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (i == devId) {
|
||||
devListId = devices.size();
|
||||
defaultDev = devListId;
|
||||
}
|
||||
dev.srId = 0;
|
||||
|
||||
AudioDevice_t* _dev = new AudioDevice_t;
|
||||
*_dev = dev;
|
||||
devices.push_back(_dev);
|
||||
|
||||
deviceNames.push_back(deviceInfo->name);
|
||||
txtDevList += deviceInfo->name;
|
||||
txtDevList += '\0';
|
||||
}
|
||||
|
||||
// Load config from file
|
||||
}
|
||||
|
||||
~AudioSink() {
|
||||
for (auto const& dev : devices) {
|
||||
delete dev;
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
doStart();
|
||||
running = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
doStop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void menuHandler() {
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devListId, txtDevList.c_str())) {
|
||||
// TODO: Load SR from config
|
||||
if (running) {
|
||||
doStop();
|
||||
doStart();
|
||||
}
|
||||
// TODO: Save to config
|
||||
}
|
||||
|
||||
AudioDevice_t* dev = devices[devListId];
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &dev->srId, dev->txtSampleRates.c_str())) {
|
||||
_stream->setSampleRate(dev->sampleRates[dev->srId]);
|
||||
if (running) {
|
||||
doStop();
|
||||
doStart();
|
||||
}
|
||||
// TODO: Save to config
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void doStart() {
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
AudioDevice_t* dev = devices[devListId];
|
||||
PaStreamParameters outputParams;
|
||||
deviceInfo = Pa_GetDeviceInfo(dev->index);
|
||||
outputParams.channelCount = 2;
|
||||
outputParams.sampleFormat = paFloat32;
|
||||
outputParams.hostApiSpecificStreamInfo = NULL;
|
||||
outputParams.device = dev->index;
|
||||
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
|
||||
PaError err;
|
||||
|
||||
float sampleRate = dev->sampleRates[dev->srId];
|
||||
int bufferSize = sampleRate / 60.0f;
|
||||
|
||||
if (dev->channels == 2) {
|
||||
stereoRB.data.setMaxLatency(bufferSize * 2);
|
||||
stereoRB.start();
|
||||
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _stereo_cb, this);
|
||||
}
|
||||
else {
|
||||
monoRB.data.setMaxLatency(bufferSize * 2);
|
||||
monoRB.start();
|
||||
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _mono_cb, this);
|
||||
}
|
||||
|
||||
if (err != 0) {
|
||||
spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = Pa_StartStream(stream);
|
||||
if (err != 0) {
|
||||
spdlog::error("Error while starting audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
|
||||
return;
|
||||
}
|
||||
spdlog::info("Audio device open.");
|
||||
running = true;
|
||||
}
|
||||
|
||||
void doStop() {
|
||||
s2m.stop();
|
||||
monoRB.stop();
|
||||
stereoRB.stop();
|
||||
monoRB.data.stopReader();
|
||||
stereoRB.data.stopReader();
|
||||
Pa_StopStream(stream);
|
||||
Pa_CloseStream(stream);
|
||||
monoRB.data.clearReadStop();
|
||||
stereoRB.data.clearReadStop();
|
||||
}
|
||||
|
||||
static int _mono_cb(const void *input, void *output, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
|
||||
AudioSink* _this = (AudioSink*)userData;
|
||||
_this->monoRB.data.read((float*)output, frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _stereo_cb(const void *input, void *output, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
|
||||
AudioSink* _this = (AudioSink*)userData;
|
||||
_this->stereoRB.data.read((dsp::stereo_t*)output, frameCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SinkManager::Stream* _stream;
|
||||
dsp::StereoToMono s2m;
|
||||
dsp::RingBufferSink<float> monoRB;
|
||||
dsp::RingBufferSink<dsp::stereo_t> stereoRB;
|
||||
std::string _streamName;
|
||||
PaStream *stream;
|
||||
|
||||
int srId = 0;
|
||||
int devCount;
|
||||
int devId = 0;
|
||||
int devListId = 0;
|
||||
int defaultDev = 0;
|
||||
bool running = false;
|
||||
|
||||
const double POSSIBLE_SAMP_RATE[6] = {
|
||||
48000.0f,
|
||||
44100.0f,
|
||||
24000.0f,
|
||||
22050.0f,
|
||||
12000.0f,
|
||||
11025.0f
|
||||
};
|
||||
|
||||
std::vector<AudioDevice_t*> devices;
|
||||
std::vector<std::string> deviceNames;
|
||||
std::string txtDevList;
|
||||
|
||||
};
|
||||
|
||||
class AudioSinkModule : public ModuleManager::Instance {
|
||||
public:
|
||||
AudioSinkModule(std::string name) {
|
||||
this->name = name;
|
||||
provider.create = create_sink;
|
||||
provider.ctx = this;
|
||||
|
||||
Pa_Initialize();
|
||||
|
||||
sigpath::sinkManager.registerSinkProvider("Audio", provider);
|
||||
}
|
||||
|
||||
~AudioSinkModule() {
|
||||
Pa_Terminate();
|
||||
}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
|
||||
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
SinkManager::SinkProvider provider;
|
||||
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Nothing here
|
||||
// TODO: Do instancing here (in source modules as well) to prevent multiple loads
|
||||
}
|
||||
|
||||
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
||||
AudioSinkModule* instance = new AudioSinkModule(name);
|
||||
return instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_() {
|
||||
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
|
||||
}
|
90
core/CMakeLists.txt
Normal file
90
core/CMakeLists.txt
Normal file
@ -0,0 +1,90 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(sdrpp_core)
|
||||
|
||||
# Set compiler options
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "-g -O3 -std=c++17 -fpermissive")
|
||||
endif (MSVC)
|
||||
add_definitions(-DSDRPP_IS_CORE)
|
||||
|
||||
# Main code
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
# Add code to dyn lib
|
||||
add_library(sdrpp_core SHARED ${SRC})
|
||||
|
||||
# Include core headers
|
||||
target_include_directories(sdrpp_core PUBLIC "src/")
|
||||
target_include_directories(sdrpp_core PUBLIC "src/imgui")
|
||||
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/lib/")
|
||||
|
||||
# Misc headers
|
||||
target_include_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/include/")
|
||||
|
||||
# Volk
|
||||
target_link_libraries(sdrpp_core PUBLIC volk)
|
||||
|
||||
# Glew
|
||||
find_package(GLEW REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC GLEW::GLEW)
|
||||
|
||||
# GLFW3
|
||||
find_package(glfw3 CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC glfw)
|
||||
|
||||
# FFTW3
|
||||
find_package(FFTW3f CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f)
|
||||
|
||||
else()
|
||||
find_package(PkgConfig)
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
pkg_check_modules(GLEW REQUIRED glew)
|
||||
pkg_check_modules(FFTW3 REQUIRED fftw3f)
|
||||
pkg_check_modules(VOLK REQUIRED volk)
|
||||
pkg_check_modules(GLFW3 REQUIRED glfw3)
|
||||
|
||||
target_include_directories(sdrpp_core PUBLIC
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
${FFTW3_INCLUDE_DIRS}
|
||||
${GLFW3_INCLUDE_DIRS}
|
||||
${VOLK_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_directories(sdrpp_core PUBLIC
|
||||
${GLEW_LIBRARY_DIRS}
|
||||
${FFTW3_LIBRARY_DIRS}
|
||||
${GLFW3_LIBRARY_DIRS}
|
||||
${VOLK_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(sdrpp_core PUBLIC
|
||||
${OPENGL_LIBRARIES}
|
||||
${GLEW_LIBRARIES}
|
||||
${FFTW3_LIBRARIES}
|
||||
${GLFW3_LIBRARIES}
|
||||
${VOLK_LIBRARIES}
|
||||
)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
target_link_libraries(sdrpp_core PUBLIC stdc++fs)
|
||||
endif ()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
|
||||
endif ()
|
||||
|
||||
endif ()
|
||||
|
||||
set(CORE_FILES ${RUNTIME_OUTPUT_DIRECTORY} PARENT_SCOPE)
|
||||
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"
|
118
core/src/config.cpp
Normal file
118
core/src/config.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include <config.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
ConfigManager::ConfigManager() {
|
||||
|
||||
}
|
||||
|
||||
ConfigManager::~ConfigManager() {
|
||||
disableAutoSave();
|
||||
}
|
||||
|
||||
void ConfigManager::setPath(std::string file) {
|
||||
path = file;
|
||||
}
|
||||
|
||||
void ConfigManager::load(json def, bool lock) {
|
||||
if (lock) { mtx.lock(); }
|
||||
if (path == "") {
|
||||
spdlog::error("Config manager tried to load file with no path specified");
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::warn("Config file '{0}' does not exist, creating it", path);
|
||||
conf = def;
|
||||
save(false);
|
||||
}
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
spdlog::error("Config file '{0}' isn't a file", path);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(path.c_str());
|
||||
file >> conf;
|
||||
file.close();
|
||||
if (lock) { mtx.unlock(); }
|
||||
}
|
||||
|
||||
void ConfigManager::save(bool lock) {
|
||||
if (lock) { mtx.lock(); }
|
||||
std::ofstream file(path.c_str());
|
||||
file << conf.dump(4);
|
||||
file.close();
|
||||
if (lock) { mtx.unlock(); }
|
||||
}
|
||||
|
||||
void ConfigManager::enableAutoSave() {
|
||||
if (!autoSaveEnabled) {
|
||||
autoSaveEnabled = true;
|
||||
autoSaveThread = std::thread(autoSaveWorker, this);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::disableAutoSave() {
|
||||
if (autoSaveEnabled) {
|
||||
autoSaveEnabled = false;
|
||||
autoSaveThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::aquire() {
|
||||
mtx.lock();
|
||||
}
|
||||
|
||||
void ConfigManager::release(bool changed) {
|
||||
this->changed |= changed;
|
||||
mtx.unlock();
|
||||
}
|
||||
|
||||
void ConfigManager::autoSaveWorker(ConfigManager* _this) {
|
||||
while (_this->autoSaveEnabled) {
|
||||
if (!_this->mtx.try_lock()) {
|
||||
spdlog::warn("ConfigManager locked, waiting...");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
continue;
|
||||
}
|
||||
if (_this->changed) {
|
||||
_this->changed = false;
|
||||
_this->save(false);
|
||||
}
|
||||
_this->mtx.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
}
|
||||
|
||||
// void ConfigManager::setResourceDir(std::string path) {
|
||||
// if (!std::filesystem::exists(path)) {
|
||||
// spdlog::error("Resource directory '{0}' does not exist", path);
|
||||
// return;
|
||||
// }
|
||||
// if (!std::filesystem::is_regular_file(path)) {
|
||||
// spdlog::error("Resource directory '{0}' is not a directory", path);
|
||||
// return;
|
||||
// }
|
||||
// resDir = path;
|
||||
// }
|
||||
|
||||
// std::string ConfigManager::getResourceDir() {
|
||||
// return resDir;
|
||||
// }
|
||||
|
||||
// void ConfigManager::setConfigDir(std::string path) {
|
||||
// if (!std::filesystem::exists(path)) {
|
||||
// spdlog::error("Resource directory '{0}' does not exist", path);
|
||||
// return;
|
||||
// }
|
||||
// if (!std::filesystem::is_regular_file(path)) {
|
||||
// spdlog::error("Resource directory '{0}' is not a directory", path);
|
||||
// return;
|
||||
// }
|
||||
// resDir = path;
|
||||
// }
|
||||
|
||||
// std::string ConfigManager::getConfigDir() {
|
||||
// return configDir;
|
||||
// }
|
||||
|
32
core/src/config.h
Normal file
32
core/src/config.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <json.hpp>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
class ConfigManager {
|
||||
public:
|
||||
ConfigManager();
|
||||
~ConfigManager();
|
||||
void setPath(std::string file);
|
||||
void load(json def, bool lock = true);
|
||||
void save(bool lock = true);
|
||||
void enableAutoSave();
|
||||
void disableAutoSave();
|
||||
void aquire();
|
||||
void release(bool changed = false);
|
||||
|
||||
json conf;
|
||||
|
||||
private:
|
||||
static void autoSaveWorker(ConfigManager* _this);
|
||||
|
||||
std::string path = "";
|
||||
bool changed = false;
|
||||
bool autoSaveEnabled = false;
|
||||
std::thread autoSaveThread;
|
||||
std::mutex mtx;
|
||||
|
||||
};
|
358
core/src/core.cpp
Normal file
358
core/src/core.cpp
Normal file
@ -0,0 +1,358 @@
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <stdio.h>
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/icons.h>
|
||||
#include <version.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <stb_image.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
#include <options.h>
|
||||
#include <duktape/duktape.h>
|
||||
#include <duktape/duk_console.h>
|
||||
#include <filesystem>
|
||||
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include <stb_image_resize.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace core {
|
||||
ConfigManager configManager;
|
||||
ScriptManager scriptManager;
|
||||
ModuleManager moduleManager;
|
||||
|
||||
void setInputSampleRate(double samplerate) {
|
||||
// NOTE: Zoom controls won't work
|
||||
gui::waterfall.setBandwidth(samplerate);
|
||||
gui::waterfall.setViewOffset(0);
|
||||
gui::waterfall.setViewBandwidth(samplerate);
|
||||
sigpath::signalPath.setSampleRate(samplerate);
|
||||
setViewBandwidthSlider(samplerate);
|
||||
}
|
||||
};
|
||||
|
||||
bool maximized = false;
|
||||
bool fullScreen = false;
|
||||
|
||||
static void glfw_error_callback(int error, const char* description) {
|
||||
spdlog::error("Glfw Error {0}: {1}", error, description);
|
||||
}
|
||||
|
||||
static void maximized_callback(GLFWwindow* window, int n) {
|
||||
if (n == GLFW_TRUE) {
|
||||
maximized = true;
|
||||
}
|
||||
else {
|
||||
maximized = false;
|
||||
}
|
||||
}
|
||||
|
||||
duk_ret_t test_func(duk_context *ctx) {
|
||||
printf("Hello from C++\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// main
|
||||
int sdrpp_main(int argc, char *argv[]) {
|
||||
#ifdef _WIN32
|
||||
//FreeConsole();
|
||||
// ConfigManager::setResourceDir("./res");
|
||||
// ConfigManager::setConfigDir(".");
|
||||
#endif
|
||||
|
||||
spdlog::info("SDR++ v" VERSION_STR);
|
||||
|
||||
// Load default options and parse command line
|
||||
options::loadDefaults();
|
||||
if (!options::parse(argc, argv)) { return -1; }
|
||||
|
||||
// Check root directory
|
||||
if (!std::filesystem::exists(options::opts.root)) {
|
||||
spdlog::warn("Root directory {0} does not exist, creating it", options::opts.root);
|
||||
if (!std::filesystem::create_directory(options::opts.root)) {
|
||||
spdlog::error("Could not create root directory {0}", options::opts.root);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(options::opts.root)) {
|
||||
spdlog::error("{0} is not a directory", options::opts.root);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ======== DEFAULT CONFIG ========
|
||||
json defConfig;
|
||||
defConfig["bandColors"]["amateur"] = "#FF0000FF";
|
||||
defConfig["bandColors"]["aviation"] = "#00FF00FF";
|
||||
defConfig["bandColors"]["broadcast"] = "#0000FFFF";
|
||||
defConfig["bandColors"]["marine"] = "#00FFFFFF";
|
||||
defConfig["bandColors"]["military"] = "#FFFF00FF";
|
||||
defConfig["bandPlan"] = "General";
|
||||
defConfig["bandPlanEnabled"] = true;
|
||||
defConfig["centerTuning"] = false;
|
||||
defConfig["colorMap"] = "Classic";
|
||||
defConfig["fftHeight"] = 300;
|
||||
defConfig["frequency"] = 100000000.0;
|
||||
defConfig["max"] = 0.0;
|
||||
defConfig["maximized"] = false;
|
||||
defConfig["menuOrder"] = {
|
||||
"Source",
|
||||
"Radio",
|
||||
"Recorder",
|
||||
"Sinks",
|
||||
"Audio",
|
||||
"Scripting",
|
||||
"Band Plan",
|
||||
"Display"
|
||||
};
|
||||
defConfig["menuWidth"] = 300;
|
||||
defConfig["min"] = -70.0;
|
||||
|
||||
defConfig["moduleInstances"]["Radio"] = "radio";
|
||||
defConfig["moduleInstances"]["Recorder"] = "recorder";
|
||||
defConfig["moduleInstances"]["SoapySDR Source"] = "soapy_source";
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"] = "plutosdr_source";
|
||||
defConfig["moduleInstances"]["RTL-TCP Source"] = "rtl_tcp_source";
|
||||
defConfig["moduleInstances"]["AirspyHF+ Source"] = "airspyhf_source";
|
||||
defConfig["moduleInstances"]["Airspy Source"] = "airspy_source";
|
||||
defConfig["moduleInstances"]["HackRF Source"] = "hackrf_source";
|
||||
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
|
||||
|
||||
defConfig["modules"] = json::array();
|
||||
defConfig["offset"] = 0.0;
|
||||
defConfig["showWaterfall"] = true;
|
||||
defConfig["source"] = "";
|
||||
defConfig["streams"] = json::object();
|
||||
defConfig["windowSize"]["h"] = 720;
|
||||
defConfig["windowSize"]["w"] = 1280;
|
||||
|
||||
defConfig["bandColors"]["broadcast"] = "#0000FFFF";
|
||||
defConfig["bandColors"]["amateur"] = "#FF0000FF";
|
||||
defConfig["bandColors"]["aviation"] = "#00FF00FF";
|
||||
defConfig["bandColors"]["marine"] = "#00FFFFFF";
|
||||
defConfig["bandColors"]["military"] = "#FFFF00FF";
|
||||
|
||||
#ifdef _WIN32
|
||||
defConfig["modulesDirectory"] = "./modules";
|
||||
defConfig["resourcesDirectory"] = "./res";
|
||||
#else
|
||||
defConfig["modulesDirectory"] = "/usr/lib/sdrpp/plugins";
|
||||
defConfig["resourcesDirectory"] = "/usr/share/sdrpp";
|
||||
#endif
|
||||
|
||||
// Load config
|
||||
spdlog::info("Loading config");
|
||||
core::configManager.setPath(options::opts.root + "/config.json");
|
||||
core::configManager.load(defConfig);
|
||||
core::configManager.enableAutoSave();
|
||||
|
||||
// Fix config
|
||||
core::configManager.aquire();
|
||||
for (auto const& item : defConfig.items()) {
|
||||
if (!core::configManager.conf.contains(item.key())) {
|
||||
spdlog::warn("Missing key in config {0}, repairing", item.key());
|
||||
core::configManager.conf[item.key()] = defConfig[item.key()];
|
||||
}
|
||||
}
|
||||
core::configManager.release(true);
|
||||
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
// GL 3.2 + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
#else
|
||||
// GL 3.0 + GLSL 120
|
||||
const char* glsl_version = "#version 120";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
#endif
|
||||
|
||||
core::configManager.aquire();
|
||||
int winWidth = core::configManager.conf["windowSize"]["w"];
|
||||
int winHeight = core::configManager.conf["windowSize"]["h"];
|
||||
maximized = core::configManager.conf["maximized"];
|
||||
std::string resDir = core::configManager.conf["resourcesDirectory"];
|
||||
json bandColors = core::configManager.conf["bandColors"];
|
||||
core::configManager.release();
|
||||
|
||||
// Create window with graphics context
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
GLFWwindow* window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||
if (window == NULL)
|
||||
return 1;
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
|
||||
if (maximized) {
|
||||
glfwMaximizeWindow(window);
|
||||
}
|
||||
|
||||
glfwSetWindowMaximizeCallback(window, maximized_callback);
|
||||
#endif
|
||||
|
||||
// Load app icon
|
||||
GLFWimage icons[10];
|
||||
icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
|
||||
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16;
|
||||
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24;
|
||||
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32;
|
||||
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48;
|
||||
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64;
|
||||
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96;
|
||||
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128;
|
||||
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196;
|
||||
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256;
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
|
||||
glfwSetWindowIcon(window, 10, icons);
|
||||
stbi_image_free(icons[0].pixels);
|
||||
for (int i = 1; i < 10; i++) {
|
||||
free(icons[i].pixels);
|
||||
}
|
||||
|
||||
if (glewInit() != GLEW_OK) {
|
||||
spdlog::error("Failed to initialize OpenGL loader!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
io.IniFilename = NULL;
|
||||
|
||||
// Setup Platform/Renderer bindings
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
if (!style::setDarkStyle(resDir)) { return -1; }
|
||||
|
||||
LoadingScreen::setWindow(window);
|
||||
|
||||
LoadingScreen::show("Loading icons");
|
||||
spdlog::info("Loading icons");
|
||||
if (!icons::load(resDir)) { return -1; }
|
||||
|
||||
LoadingScreen::show("Loading band plans");
|
||||
spdlog::info("Loading band plans");
|
||||
bandplan::loadFromDir(resDir + "/bandplans");
|
||||
|
||||
LoadingScreen::show("Loading band plan colors");
|
||||
spdlog::info("Loading band plans color table");
|
||||
bandplan::loadColorTable(bandColors);
|
||||
|
||||
windowInit();
|
||||
|
||||
spdlog::info("Ready.");
|
||||
|
||||
bool _maximized = maximized;
|
||||
int fsWidth, fsHeight, fsPosX, fsPosY;
|
||||
|
||||
// Main loop
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
//ImGui::ShowDemoWindow();
|
||||
|
||||
if (_maximized != maximized) {
|
||||
_maximized = maximized;
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["maximized"]= _maximized;
|
||||
if (!maximized) {
|
||||
glfwSetWindowSize(window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
|
||||
}
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
int _winWidth, _winHeight;
|
||||
glfwGetWindowSize(window, &_winWidth, &_winHeight);
|
||||
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
|
||||
fullScreen = !fullScreen;
|
||||
if (fullScreen) {
|
||||
spdlog::info("Fullscreen: ON");
|
||||
fsWidth = _winWidth;
|
||||
fsHeight = _winHeight;
|
||||
glfwGetWindowPos(window, &fsPosX, &fsPosY);
|
||||
const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
|
||||
}
|
||||
else {
|
||||
spdlog::info("Fullscreen: OFF");
|
||||
glfwSetWindowMonitor(window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
|
||||
winWidth = _winWidth;
|
||||
winHeight = _winHeight;
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["windowSize"]["w"] = winWidth;
|
||||
core::configManager.conf["windowSize"]["h"] = winHeight;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (winWidth > 0 && winHeight > 0) {
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
|
||||
drawWindow();
|
||||
}
|
||||
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f);
|
||||
//glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapInterval(1); // Enable vsync
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
|
||||
return 0;
|
||||
}
|
15
core/src/core.h
Normal file
15
core/src/core.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <config.h>
|
||||
#include <module.h>
|
||||
#include <scripting.h>
|
||||
#include <module.h>
|
||||
|
||||
namespace core {
|
||||
SDRPP_EXPORT ConfigManager configManager;
|
||||
SDRPP_EXPORT ScriptManager scriptManager;
|
||||
SDRPP_EXPORT ModuleManager moduleManager;
|
||||
|
||||
void setInputSampleRate(double samplerate);
|
||||
};
|
||||
|
||||
int sdrpp_main(int argc, char *argv[]);
|
33
core/src/credits.cpp
Normal file
33
core/src/credits.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include <credits.h>
|
||||
|
||||
namespace sdrpp_credits {
|
||||
const char* contributors[] = {
|
||||
"Ryzerth (Author)",
|
||||
"aosync",
|
||||
"Alexsey Shestacov",
|
||||
"Benjamin Kyd",
|
||||
"Cropinghigh",
|
||||
"Howard0su",
|
||||
"Martin Hauke",
|
||||
"Raov",
|
||||
"Szymon Zakrent",
|
||||
"Tobias Mädel"
|
||||
};
|
||||
|
||||
const char* libraries[] = {
|
||||
"Dear ImGui (ocornut)",
|
||||
"json (nlohmann)",
|
||||
"portaudio (P.A. comm.)",
|
||||
"SoapySDR (PothosWare)",
|
||||
"spdlog (gabime)",
|
||||
};
|
||||
|
||||
const char* patrons[] = {
|
||||
"SignalsEverywhere",
|
||||
"Lee Donaghy"
|
||||
};
|
||||
|
||||
const int contributorCount = sizeof(contributors) / sizeof(char*);
|
||||
const int libraryCount = sizeof(libraries) / sizeof(char*);
|
||||
const int patronCount = sizeof(patrons) / sizeof(char*);
|
||||
}
|
11
core/src/credits.h
Normal file
11
core/src/credits.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <module.h>
|
||||
|
||||
namespace sdrpp_credits {
|
||||
SDRPP_EXPORT const char* contributors[];
|
||||
SDRPP_EXPORT const char* libraries[];
|
||||
SDRPP_EXPORT const char* patrons[];
|
||||
SDRPP_EXPORT const int contributorCount;
|
||||
SDRPP_EXPORT const int libraryCount;
|
||||
SDRPP_EXPORT const int patronCount;
|
||||
}
|
93
core/src/dsp/audio.h
Normal file
93
core/src/dsp/audio.h
Normal file
@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
|
||||
namespace dsp {
|
||||
class MonoToStereo : public generic_block<MonoToStereo> {
|
||||
public:
|
||||
MonoToStereo() {}
|
||||
|
||||
MonoToStereo(stream<float>* in) { init(in); }
|
||||
|
||||
~MonoToStereo() { generic_block<MonoToStereo>::stop(); }
|
||||
|
||||
void init(stream<float>* in) {
|
||||
_in = in;
|
||||
generic_block<MonoToStereo>::registerInput(_in);
|
||||
generic_block<MonoToStereo>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<MonoToStereo>::ctrlMtx);
|
||||
generic_block<MonoToStereo>::tempStop();
|
||||
generic_block<MonoToStereo>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<MonoToStereo>::registerInput(_in);
|
||||
generic_block<MonoToStereo>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i].l = _in->readBuf[i];
|
||||
out.writeBuf[i].r = _in->readBuf[i];
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
class StereoToMono : public generic_block<StereoToMono> {
|
||||
public:
|
||||
StereoToMono() {}
|
||||
|
||||
StereoToMono(stream<stereo_t>* in) { init(in); }
|
||||
|
||||
~StereoToMono() { generic_block<StereoToMono>::stop(); }
|
||||
|
||||
void init(stream<stereo_t>* in) {
|
||||
_in = in;
|
||||
generic_block<StereoToMono>::registerInput(_in);
|
||||
generic_block<StereoToMono>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<stereo_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<StereoToMono>::ctrlMtx);
|
||||
generic_block<StereoToMono>::tempStop();
|
||||
generic_block<StereoToMono>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<StereoToMono>::registerInput(_in);
|
||||
generic_block<StereoToMono>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] = (_in->readBuf[i].l + _in->readBuf[i].r) / 2.0f;
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
stream<stereo_t>* _in;
|
||||
|
||||
};
|
||||
}
|
128
core/src/dsp/block.h
Normal file
128
core/src/dsp/block.h
Normal file
@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#define FL_M_PI 3.1415926535f
|
||||
|
||||
namespace dsp {
|
||||
|
||||
template <class BLOCK>
|
||||
class generic_block {
|
||||
public:
|
||||
virtual void init() {}
|
||||
|
||||
virtual ~generic_block() {
|
||||
stop();
|
||||
}
|
||||
|
||||
virtual void start() {
|
||||
std::lock_guard<std::mutex> lck(ctrlMtx);
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
doStart();
|
||||
}
|
||||
|
||||
virtual void stop() {
|
||||
std::lock_guard<std::mutex> lck(ctrlMtx);
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
doStop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
virtual int calcOutSize(int inSize) { return inSize; }
|
||||
|
||||
virtual int run() = 0;
|
||||
|
||||
friend BLOCK;
|
||||
|
||||
private:
|
||||
void workerLoop() {
|
||||
while (run() >= 0);
|
||||
}
|
||||
|
||||
void aquire() {
|
||||
ctrlMtx.lock();
|
||||
}
|
||||
|
||||
void release() {
|
||||
ctrlMtx.unlock();
|
||||
}
|
||||
|
||||
void registerInput(untyped_steam* inStream) {
|
||||
inputs.push_back(inStream);
|
||||
}
|
||||
|
||||
void unregisterInput(untyped_steam* inStream) {
|
||||
inputs.erase(std::remove(inputs.begin(), inputs.end(), inStream), inputs.end());
|
||||
}
|
||||
|
||||
void registerOutput(untyped_steam* outStream) {
|
||||
outputs.push_back(outStream);
|
||||
}
|
||||
|
||||
void unregisterOutput(untyped_steam* outStream) {
|
||||
outputs.erase(std::remove(outputs.begin(), outputs.end(), outStream), outputs.end());
|
||||
}
|
||||
|
||||
virtual void doStart() {
|
||||
workerThread = std::thread(&generic_block<BLOCK>::workerLoop, this);
|
||||
}
|
||||
|
||||
virtual void doStop() {
|
||||
for (auto& in : inputs) {
|
||||
in->stopReader();
|
||||
}
|
||||
for (auto& out : outputs) {
|
||||
out->stopWriter();
|
||||
}
|
||||
|
||||
// TODO: Make sure this isn't needed, I don't know why it stops
|
||||
if (workerThread.joinable()) {
|
||||
workerThread.join();
|
||||
}
|
||||
|
||||
for (auto& in : inputs) {
|
||||
in->clearReadStop();
|
||||
}
|
||||
for (auto& out : outputs) {
|
||||
out->clearWriteStop();
|
||||
}
|
||||
}
|
||||
|
||||
void tempStart() {
|
||||
if (tempStopped) {
|
||||
doStart();
|
||||
tempStopped = false;
|
||||
}
|
||||
}
|
||||
|
||||
void tempStop() {
|
||||
if (running && !tempStopped) {
|
||||
doStop();
|
||||
tempStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<untyped_steam*> inputs;
|
||||
std::vector<untyped_steam*> outputs;
|
||||
|
||||
bool running = false;
|
||||
bool tempStopped = false;
|
||||
|
||||
std::thread workerThread;
|
||||
|
||||
protected:
|
||||
std::mutex ctrlMtx;
|
||||
|
||||
};
|
||||
}
|
@ -1,33 +1,23 @@
|
||||
#pragma once
|
||||
#include <condition_variable>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <dsp/block.h>
|
||||
#include <string.h>
|
||||
|
||||
#define STREAM_BUF_SZ 1000000
|
||||
#define RING_BUF_SZ 1000000
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class stream {
|
||||
class RingBuffer {
|
||||
public:
|
||||
stream() {
|
||||
RingBuffer() {
|
||||
|
||||
}
|
||||
|
||||
stream(int maxLatency) {
|
||||
size = STREAM_BUF_SZ;
|
||||
_buffer = new T[size];
|
||||
_stopReader = false;
|
||||
_stopWriter = false;
|
||||
this->maxLatency = maxLatency;
|
||||
writec = 0;
|
||||
readc = 0;
|
||||
readable = 0;
|
||||
writable = size;
|
||||
memset(_buffer, 0, size * sizeof(T));
|
||||
}
|
||||
RingBuffer(int maxLatency) { init(maxLatency); }
|
||||
|
||||
~RingBuffer() { delete _buffer; }
|
||||
|
||||
void init(int maxLatency) {
|
||||
size = STREAM_BUF_SZ;
|
||||
size = RING_BUF_SZ;
|
||||
_buffer = new T[size];
|
||||
_stopReader = false;
|
||||
_stopWriter = false;
|
185
core/src/dsp/convertion.h
Normal file
185
core/src/dsp/convertion.h
Normal file
@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
|
||||
namespace dsp {
|
||||
class ComplexToStereo : public generic_block<ComplexToStereo> {
|
||||
public:
|
||||
ComplexToStereo() {}
|
||||
|
||||
ComplexToStereo(stream<complex_t>* in) { init(in); }
|
||||
|
||||
~ComplexToStereo() { generic_block<ComplexToStereo>::stop(); }
|
||||
|
||||
static_assert(sizeof(complex_t) == sizeof(stereo_t));
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<ComplexToStereo>::registerInput(_in);
|
||||
generic_block<ComplexToStereo>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexToStereo>::ctrlMtx);
|
||||
generic_block<ComplexToStereo>::tempStop();
|
||||
generic_block<ComplexToStereo>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexToStereo>::registerInput(_in);
|
||||
generic_block<ComplexToStereo>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
float avg;
|
||||
int count;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ComplexToReal : public generic_block<ComplexToReal> {
|
||||
public:
|
||||
ComplexToReal() {}
|
||||
|
||||
ComplexToReal(stream<complex_t>* in) { init(in); }
|
||||
|
||||
~ComplexToReal() { generic_block<ComplexToReal>::stop(); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<ComplexToReal>::registerInput(_in);
|
||||
generic_block<ComplexToReal>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexToReal>::ctrlMtx);
|
||||
generic_block<ComplexToReal>::tempStop();
|
||||
generic_block<ComplexToReal>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexToReal>::registerInput(_in);
|
||||
generic_block<ComplexToReal>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_deinterleave_real_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
float avg;
|
||||
int count;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ComplexToImag : public generic_block<ComplexToImag> {
|
||||
public:
|
||||
ComplexToImag() {}
|
||||
|
||||
ComplexToImag(stream<complex_t>* in) { init(in); }
|
||||
|
||||
~ComplexToImag() { generic_block<ComplexToImag>::stop(); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<ComplexToImag>::registerInput(_in);
|
||||
generic_block<ComplexToImag>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexToImag>::ctrlMtx);
|
||||
generic_block<ComplexToImag>::tempStop();
|
||||
generic_block<ComplexToImag>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexToImag>::registerInput(_in);
|
||||
generic_block<ComplexToImag>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_deinterleave_imag_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
if(!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
float avg;
|
||||
int count;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class RealToComplex : public generic_block<RealToComplex> {
|
||||
public:
|
||||
RealToComplex() {}
|
||||
|
||||
RealToComplex(stream<float>* in) { init(in); }
|
||||
|
||||
~RealToComplex() {
|
||||
delete[] nullBuffer;
|
||||
generic_block<RealToComplex>::stop();
|
||||
}
|
||||
|
||||
void init(stream<float>* in) {
|
||||
_in = in;
|
||||
nullBuffer = new float[STREAM_BUFFER_SIZE];
|
||||
memset(nullBuffer, 0, STREAM_BUFFER_SIZE * sizeof(float));
|
||||
generic_block<RealToComplex>::registerInput(_in);
|
||||
generic_block<RealToComplex>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<RealToComplex>::ctrlMtx);
|
||||
generic_block<RealToComplex>::tempStop();
|
||||
generic_block<RealToComplex>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<RealToComplex>::registerInput(_in);
|
||||
generic_block<RealToComplex>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, nullBuffer, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
float avg;
|
||||
int count;
|
||||
float* nullBuffer;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
}
|
279
core/src/dsp/demodulator.h
Normal file
279
core/src/dsp/demodulator.h
Normal file
@ -0,0 +1,279 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <volk/volk.h>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#define FAST_ATAN2_COEF1 FL_M_PI / 4.0f
|
||||
#define FAST_ATAN2_COEF2 3.0f * FAST_ATAN2_COEF1
|
||||
|
||||
inline float fast_arctan2(float y, float x) {
|
||||
float abs_y = fabsf(y);
|
||||
float r, angle;
|
||||
if (x == 0.0f && y == 0.0f) { return 0.0f; }
|
||||
if (x>=0.0f) {
|
||||
r = (x - abs_y) / (x + abs_y);
|
||||
angle = FAST_ATAN2_COEF1 - FAST_ATAN2_COEF1 * r;
|
||||
}
|
||||
else {
|
||||
r = (x + abs_y) / (abs_y - x);
|
||||
angle = FAST_ATAN2_COEF2 - FAST_ATAN2_COEF1 * r;
|
||||
}
|
||||
if (y < 0.0f) {
|
||||
return -angle;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
namespace dsp {
|
||||
class FMDemod : public generic_block<FMDemod> {
|
||||
public:
|
||||
FMDemod() {}
|
||||
|
||||
FMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
|
||||
|
||||
~FMDemod() { generic_block<FMDemod>::stop(); }
|
||||
|
||||
void init(stream<complex_t>* in, float sampleRate, float deviation) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_deviation = deviation;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FMDemod>::registerInput(_in);
|
||||
generic_block<FMDemod>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
|
||||
generic_block<FMDemod>::tempStop();
|
||||
generic_block<FMDemod>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FMDemod>::registerInput(_in);
|
||||
generic_block<FMDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
|
||||
generic_block<FMDemod>::tempStop();
|
||||
_sampleRate = sampleRate;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FMDemod>::tempStart();
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
void setDeviation(float deviation) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
|
||||
generic_block<FMDemod>::tempStop();
|
||||
_deviation = deviation;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FMDemod>::tempStart();
|
||||
}
|
||||
|
||||
float getDeviation() {
|
||||
return _deviation;
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// This is somehow faster than volk...
|
||||
|
||||
float diff, currentPhase;
|
||||
for (int i = 0; i < count; i++) {
|
||||
currentPhase = fast_arctan2(_in->readBuf[i].i, _in->readBuf[i].q);
|
||||
diff = currentPhase - phase;
|
||||
if (diff > 3.1415926535f) { diff -= 2 * 3.1415926535f; }
|
||||
else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; }
|
||||
out.writeBuf[i] = diff / phasorSpeed;
|
||||
phase = currentPhase;
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
float phase, phasorSpeed, _sampleRate, _deviation;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class AMDemod : public generic_block<AMDemod> {
|
||||
public:
|
||||
AMDemod() {}
|
||||
|
||||
AMDemod(stream<complex_t>* in) { init(in); }
|
||||
|
||||
~AMDemod() { generic_block<AMDemod>::stop(); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<AMDemod>::registerInput(_in);
|
||||
generic_block<AMDemod>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<AMDemod>::ctrlMtx);
|
||||
generic_block<AMDemod>::tempStop();
|
||||
generic_block<AMDemod>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<AMDemod>::registerInput(_in);
|
||||
generic_block<AMDemod>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_magnitude_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
|
||||
volk_32f_accumulator_s32f(&avg, out.writeBuf, count);
|
||||
avg /= (float)count;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] -= avg;
|
||||
}
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
float avg;
|
||||
int count;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class SSBDemod : public generic_block<SSBDemod> {
|
||||
public:
|
||||
SSBDemod() {}
|
||||
|
||||
SSBDemod(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) { init(in, sampleRate, bandWidth, mode); }
|
||||
|
||||
~SSBDemod() {
|
||||
generic_block<SSBDemod>::stop();
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
enum {
|
||||
MODE_USB,
|
||||
MODE_LSB,
|
||||
MODE_DSB
|
||||
};
|
||||
|
||||
void init(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_bandWidth = bandWidth;
|
||||
_mode = mode;
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
buffer = new lv_32fc_t[STREAM_BUFFER_SIZE];
|
||||
generic_block<SSBDemod>::registerInput(_in);
|
||||
generic_block<SSBDemod>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<SSBDemod>::ctrlMtx);
|
||||
generic_block<SSBDemod>::tempStop();
|
||||
generic_block<SSBDemod>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<SSBDemod>::registerInput(_in);
|
||||
generic_block<SSBDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
// No need to restart
|
||||
_sampleRate = sampleRate;
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setBandWidth(float bandWidth) {
|
||||
// No need to restart
|
||||
_bandWidth = bandWidth;
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setMode(int mode) {
|
||||
_mode = mode;
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
volk_32fc_deinterleave_real_32f(out.writeBuf, buffer, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
int _mode;
|
||||
float _sampleRate, _bandWidth;
|
||||
stream<complex_t>* _in;
|
||||
lv_32fc_t* buffer;
|
||||
lv_32fc_t phase;
|
||||
lv_32fc_t phaseDelta;
|
||||
|
||||
};
|
||||
}
|
168
core/src/dsp/filter.h
Normal file
168
core/src/dsp/filter.h
Normal file
@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/window.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp {
|
||||
|
||||
template <class T>
|
||||
class FIR : public generic_block<FIR<T>> {
|
||||
public:
|
||||
FIR() {}
|
||||
|
||||
FIR(stream<T>* in, dsp::filter_window::generic_window* window) { init(in, window); }
|
||||
|
||||
~FIR() {
|
||||
generic_block<FIR<T>>::stop();
|
||||
volk_free(buffer);
|
||||
volk_free(taps);
|
||||
}
|
||||
|
||||
void init(stream<T>* in, dsp::filter_window::generic_window* window) {
|
||||
_in = in;
|
||||
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
window->createTaps(taps, tapCount);
|
||||
|
||||
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
|
||||
bufStart = &buffer[tapCount];
|
||||
generic_block<FIR<T>>::registerInput(_in);
|
||||
generic_block<FIR<T>>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<FIR<T>>::ctrlMtx);
|
||||
generic_block<FIR<T>>::tempStop();
|
||||
generic_block<FIR<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FIR<T>>::registerInput(_in);
|
||||
generic_block<FIR<T>>::tempStart();
|
||||
}
|
||||
|
||||
void updateWindow(dsp::filter_window::generic_window* window) {
|
||||
_window = window;
|
||||
volk_free(taps);
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
window->createTaps(taps, tapCount);
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
memcpy(bufStart, _in->readBuf, count * sizeof(T));
|
||||
_in->flush();
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
volk_32f_x2_dot_prod_32f((float*)&out.writeBuf[i], (float*)&buffer[i+1], taps, tapCount);
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[i], (lv_32fc_t*)&buffer[i+1], taps, tapCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
memmove(buffer, &buffer[count], tapCount * sizeof(T));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
stream<T>* _in;
|
||||
|
||||
dsp::filter_window::generic_window* _window;
|
||||
|
||||
T* bufStart;
|
||||
T* buffer;
|
||||
int tapCount;
|
||||
float* taps;
|
||||
|
||||
};
|
||||
|
||||
class BFMDeemp : public generic_block<BFMDeemp> {
|
||||
public:
|
||||
BFMDeemp() {}
|
||||
|
||||
BFMDeemp(stream<float>* in, float sampleRate, float tau) { init(in, sampleRate, tau); }
|
||||
|
||||
~BFMDeemp() { generic_block<BFMDeemp>::stop(); }
|
||||
|
||||
void init(stream<float>* in, float sampleRate, float tau) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_tau = tau;
|
||||
float dt = 1.0f / _sampleRate;
|
||||
alpha = dt / (_tau + dt);
|
||||
generic_block<BFMDeemp>::registerInput(_in);
|
||||
generic_block<BFMDeemp>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<BFMDeemp>::ctrlMtx);
|
||||
generic_block<BFMDeemp>::tempStop();
|
||||
generic_block<BFMDeemp>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<BFMDeemp>::registerInput(_in);
|
||||
generic_block<BFMDeemp>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
_sampleRate = sampleRate;
|
||||
float dt = 1.0f / _sampleRate;
|
||||
alpha = dt / (_tau + dt);
|
||||
}
|
||||
|
||||
void setTau(float tau) {
|
||||
_tau = tau;
|
||||
float dt = 1.0f / _sampleRate;
|
||||
alpha = dt / (_tau + dt);
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (bypass) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(float));
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
if (isnan(lastOut)) {
|
||||
lastOut = 0.0f;
|
||||
}
|
||||
out.writeBuf[0] = (alpha * _in->readBuf[0]) + ((1-alpha) * lastOut);
|
||||
for (int i = 1; i < count; i++) {
|
||||
out.writeBuf[i] = (alpha * _in->readBuf[i]) + ((1 - alpha) * out.writeBuf[i - 1]);
|
||||
}
|
||||
lastOut = out.writeBuf[count - 1];
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
bool bypass = false;
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
float lastOut = 0.0f;
|
||||
float alpha;
|
||||
float _tau;
|
||||
float _sampleRate;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
}
|
105
core/src/dsp/math.h
Normal file
105
core/src/dsp/math.h
Normal file
@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <volk/volk.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class Add : public generic_block<Add<T>> {
|
||||
public:
|
||||
Add() {}
|
||||
|
||||
Add(stream<T>* a, stream<T>* b) { init(a, b); }
|
||||
|
||||
~Add() { generic_block<Add>::stop(); }
|
||||
|
||||
void init(stream<T>* a, stream<T>* b) {
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Add>::registerInput(a);
|
||||
generic_block<Add>::registerInput(b);
|
||||
generic_block<Add>::registerOutput(&out);
|
||||
}
|
||||
|
||||
int run() {
|
||||
a_count = _a->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
b_count = _b->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
volk_32fc_x2_add_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
|
||||
}
|
||||
else {
|
||||
volk_32f_x2_add_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
|
||||
}
|
||||
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
if (!out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
int a_count, b_count;
|
||||
stream<T>* _a;
|
||||
stream<T>* _b;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class Multiply : public generic_block<Multiply<T>> {
|
||||
public:
|
||||
Multiply() {}
|
||||
|
||||
Multiply(stream<T>* a, stream<T>* b) { init(a, b); }
|
||||
|
||||
~Multiply() { generic_block<Multiply>::stop(); }
|
||||
|
||||
void init(stream<T>* a, stream<T>* b) {
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Multiply>::registerInput(a);
|
||||
generic_block<Multiply>::registerInput(b);
|
||||
generic_block<Multiply>::registerOutput(&out);
|
||||
}
|
||||
|
||||
int run() {
|
||||
a_count = _a->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
b_count = _b->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
volk_32fc_x2_multiply_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
|
||||
}
|
||||
else {
|
||||
volk_32f_x2_multiply_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
|
||||
}
|
||||
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
if (!out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
int a_count, b_count;
|
||||
stream<T>* _a;
|
||||
stream<T>* _b;
|
||||
|
||||
};
|
||||
}
|
99
core/src/dsp/measure.h
Normal file
99
core/src/dsp/measure.h
Normal file
@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <fftw3.h>
|
||||
#include <volk/volk.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <dsp/types.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp {
|
||||
class VolumeMeasure : public generic_block<VolumeMeasure> {
|
||||
public:
|
||||
VolumeMeasure() {}
|
||||
|
||||
VolumeMeasure(stream<stereo_t>* in) { init(in); }
|
||||
|
||||
~VolumeMeasure() {
|
||||
generic_block<VolumeMeasure>::stop();
|
||||
delete[] leftBuf;
|
||||
delete[] rightBuf;
|
||||
}
|
||||
|
||||
void init(stream<stereo_t>* in) {
|
||||
_in = in;
|
||||
leftBuf = new float[STREAM_BUFFER_SIZE];
|
||||
rightBuf = new float[STREAM_BUFFER_SIZE];
|
||||
generic_block<VolumeMeasure>::registerInput(_in);
|
||||
generic_block<VolumeMeasure>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<stereo_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<VolumeMeasure>::ctrlMtx);
|
||||
generic_block<VolumeMeasure>::tempStop();
|
||||
generic_block<VolumeMeasure>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<VolumeMeasure>::registerInput(_in);
|
||||
generic_block<VolumeMeasure>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(stereo_t));
|
||||
volk_32fc_deinterleave_32f_x2(leftBuf, rightBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
// Get peak from last value
|
||||
float time = (float)count / sampleRate;
|
||||
peak.l -= peakFall * time;
|
||||
peak.r -= peakFall * time;
|
||||
stereo_t _peak;
|
||||
_peak.l = powf(10, peak.l / 10.0f);
|
||||
_peak.r = powf(10, peak.r / 10.0f);
|
||||
|
||||
stereo_t _average;
|
||||
|
||||
// Calculate average
|
||||
volk_32f_s32f_power_32f(leftBuf, leftBuf, 2, count);
|
||||
volk_32f_s32f_power_32f(rightBuf, rightBuf, 2, count);
|
||||
volk_32f_sqrt_32f(leftBuf, leftBuf, count);
|
||||
volk_32f_sqrt_32f(rightBuf, rightBuf, count);
|
||||
volk_32f_accumulator_s32f(&_average.l, leftBuf, count);
|
||||
volk_32f_accumulator_s32f(&_average.r, rightBuf, count);
|
||||
_average.l /= (float)count;
|
||||
_average.r /= (float)count;
|
||||
|
||||
// Calculate peak
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (leftBuf[i] > _peak.l) { _peak.l = leftBuf[i]; }
|
||||
if (rightBuf[i] > _peak.r) { _peak.r = rightBuf[i]; }
|
||||
}
|
||||
|
||||
// Assign
|
||||
peak.l = 10.0f * log10f(_peak.l);
|
||||
peak.r = 10.0f * log10f(_peak.r);
|
||||
average.l = (average.l * (1.0f - avgFilt)) + (10.0f * log10f(_average.l) * avgFilt);
|
||||
average.r = (average.r * (1.0f - avgFilt)) + (10.0f * log10f(_average.r) * avgFilt);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
stereo_t peak = {0, 0};
|
||||
stereo_t average = {0, 0};
|
||||
|
||||
private:
|
||||
int count;
|
||||
float peakFall = 10.0f; // dB/S
|
||||
float avgFilt = 0.2f; // IIR filter coef
|
||||
float sampleRate = 48000;
|
||||
stream<stereo_t>* _in;
|
||||
|
||||
float* leftBuf;
|
||||
float* rightBuf;
|
||||
};
|
||||
}
|
300
core/src/dsp/processing.h
Normal file
300
core/src/dsp/processing.h
Normal file
@ -0,0 +1,300 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <volk/volk.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class FrequencyXlator : public generic_block<FrequencyXlator<T>> {
|
||||
public:
|
||||
FrequencyXlator() {}
|
||||
|
||||
FrequencyXlator(stream<complex_t>* in, float sampleRate, float freq) { init(in, sampleRate, freq); }
|
||||
|
||||
~FrequencyXlator() {
|
||||
generic_block<FrequencyXlator<T>>::stop();
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, float sampleRate, float freq) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_freq = freq;
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
generic_block<FrequencyXlator<T>>::registerInput(_in);
|
||||
generic_block<FrequencyXlator<T>>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInputSize(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<FrequencyXlator<T>>::ctrlMtx);
|
||||
generic_block<FrequencyXlator<T>>::tempStop();
|
||||
generic_block<FrequencyXlator<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FrequencyXlator<T>>::registerInput(_in);
|
||||
generic_block<FrequencyXlator<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
// No need to restart
|
||||
_sampleRate = sampleRate;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
void setFrequency(float freq) {
|
||||
// No need to restart
|
||||
_freq = freq;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getFrequency() {
|
||||
return _freq;
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// TODO: Do float xlation
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
spdlog::error("XLATOR NOT IMPLEMENTED FOR FLOAT");
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
float _sampleRate;
|
||||
float _freq;
|
||||
lv_32fc_t phaseDelta;
|
||||
lv_32fc_t phase;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class AGC : public generic_block<AGC> {
|
||||
public:
|
||||
AGC() {}
|
||||
|
||||
AGC(stream<float>* in, float fallRate, float sampleRate) { init(in, fallRate, sampleRate); }
|
||||
|
||||
~AGC() { generic_block<AGC>::stop(); }
|
||||
|
||||
void init(stream<float>* in, float fallRate, float sampleRate) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_fallRate = fallRate;
|
||||
_CorrectedFallRate = _fallRate / _sampleRate;
|
||||
generic_block<AGC>::registerInput(_in);
|
||||
generic_block<AGC>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
|
||||
generic_block<AGC>::tempStop();
|
||||
generic_block<AGC>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<AGC>::registerInput(_in);
|
||||
generic_block<AGC>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
|
||||
_sampleRate = sampleRate;
|
||||
_CorrectedFallRate = _fallRate / _sampleRate;
|
||||
}
|
||||
|
||||
void setFallRate(float fallRate) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
|
||||
_fallRate = fallRate;
|
||||
_CorrectedFallRate = _fallRate / _sampleRate;
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
level = pow(10, ((10.0f * log10f(level)) - (_CorrectedFallRate * count)) / 10.0f);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (_in->readBuf[i] > level) { level = _in->readBuf[i]; }
|
||||
}
|
||||
|
||||
volk_32f_s32f_multiply_32f(out.writeBuf, _in->readBuf, 1.0f / level, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
float level = 0.0f;
|
||||
float _fallRate;
|
||||
float _CorrectedFallRate;
|
||||
float _sampleRate;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class Volume : public generic_block<Volume<T>> {
|
||||
public:
|
||||
Volume() {}
|
||||
|
||||
Volume(stream<T>* in, float volume) { init(in, volume); }
|
||||
|
||||
~Volume() { generic_block<Volume<T>>::stop(); }
|
||||
|
||||
void init(stream<T>* in, float volume) {
|
||||
_in = in;
|
||||
_volume = volume;
|
||||
generic_block<Volume<T>>::registerInput(_in);
|
||||
generic_block<Volume<T>>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInputSize(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Volume<T>>::ctrlMtx);
|
||||
generic_block<Volume<T>>::tempStop();
|
||||
generic_block<Volume<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Volume<T>>::registerInput(_in);
|
||||
generic_block<Volume<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setVolume(float volume) {
|
||||
_volume = volume;
|
||||
level = powf(_volume, 2);
|
||||
}
|
||||
|
||||
float getVolume() {
|
||||
return _volume;
|
||||
}
|
||||
|
||||
void setMuted(bool muted) {
|
||||
_muted = muted;
|
||||
}
|
||||
|
||||
bool getMuted() {
|
||||
return _muted;
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (_muted) {
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
memset(out.writeBuf, 0, sizeof(stereo_t) * count);
|
||||
}
|
||||
else {
|
||||
memset(out.writeBuf, 0, sizeof(float) * count);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count * 2);
|
||||
}
|
||||
else {
|
||||
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count);
|
||||
}
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
float level = 1.0f;
|
||||
float _volume = 1.0f;
|
||||
bool _muted = false;
|
||||
stream<T>* _in;
|
||||
|
||||
};
|
||||
|
||||
class Squelch : public generic_block<Squelch> {
|
||||
public:
|
||||
Squelch() {}
|
||||
|
||||
Squelch(stream<complex_t>* in, float level) { init(in, level); }
|
||||
|
||||
~Squelch() {
|
||||
generic_block<Squelch>::stop();
|
||||
delete[] normBuffer;
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, float level) {
|
||||
_in = in;
|
||||
_level = level;
|
||||
normBuffer = new float[STREAM_BUFFER_SIZE];
|
||||
generic_block<Squelch>::registerInput(_in);
|
||||
generic_block<Squelch>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Squelch>::ctrlMtx);
|
||||
generic_block<Squelch>::tempStop();
|
||||
generic_block<Squelch>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Squelch>::registerInput(_in);
|
||||
generic_block<Squelch>::tempStart();
|
||||
}
|
||||
|
||||
void setLevel(float level) {
|
||||
_level = level;
|
||||
}
|
||||
|
||||
float getLevel() {
|
||||
return _level;
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
float sum = 0.0f;
|
||||
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->readBuf, count);
|
||||
volk_32f_accumulator_s32f(&sum, normBuffer, count);
|
||||
sum /= (float)count;
|
||||
|
||||
if (10.0f * log10f(sum) >= _level) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
}
|
||||
else {
|
||||
memset(out.writeBuf, 0, count * sizeof(complex_t));
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
|
||||
private:
|
||||
int count;
|
||||
float* normBuffer;
|
||||
float _level = -50.0f;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
}
|
267
core/src/dsp/resampling.h
Normal file
267
core/src/dsp/resampling.h
Normal file
@ -0,0 +1,267 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/window.h>
|
||||
#include <numeric>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class PolyphaseResampler : public generic_block<PolyphaseResampler<T>> {
|
||||
public:
|
||||
PolyphaseResampler() {}
|
||||
|
||||
PolyphaseResampler(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) { init(in, window, inSampleRate, outSampleRate); }
|
||||
|
||||
~PolyphaseResampler() {
|
||||
generic_block<PolyphaseResampler<T>>::stop();
|
||||
volk_free(buffer);
|
||||
volk_free(taps);
|
||||
freeTapPhases();
|
||||
}
|
||||
|
||||
void init(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) {
|
||||
_in = in;
|
||||
_window = window;
|
||||
_inSampleRate = inSampleRate;
|
||||
_outSampleRate = outSampleRate;
|
||||
|
||||
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
|
||||
_interp = _outSampleRate / _gcd;
|
||||
_decim = _inSampleRate / _gcd;
|
||||
|
||||
tapCount = _window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
_window->createTaps(taps, tapCount, _interp);
|
||||
|
||||
buildTapPhases();
|
||||
|
||||
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
|
||||
memset(buffer, 0, STREAM_BUFFER_SIZE * sizeof(T) * 2);
|
||||
generic_block<PolyphaseResampler<T>>::registerInput(_in);
|
||||
generic_block<PolyphaseResampler<T>>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
generic_block<PolyphaseResampler<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<PolyphaseResampler<T>>::registerInput(_in);
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInSampleRate(float inSampleRate) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
_inSampleRate = inSampleRate;
|
||||
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
|
||||
_interp = _outSampleRate / _gcd;
|
||||
_decim = _inSampleRate / _gcd;
|
||||
buildTapPhases();
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setOutSampleRate(float outSampleRate) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
_outSampleRate = outSampleRate;
|
||||
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
|
||||
_interp = _outSampleRate / _gcd;
|
||||
_decim = _inSampleRate / _gcd;
|
||||
buildTapPhases();
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
int getInterpolation() {
|
||||
return _interp;
|
||||
}
|
||||
|
||||
int getDecimation() {
|
||||
return _decim;
|
||||
}
|
||||
|
||||
void updateWindow(dsp::filter_window::generic_window* window) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
_window = window;
|
||||
volk_free(taps);
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
window->createTaps(taps, tapCount, _interp);
|
||||
buildTapPhases();
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
int calcOutSize(int in) {
|
||||
return (in * _interp) / _decim;
|
||||
}
|
||||
|
||||
virtual int run() override {
|
||||
count = _in->read();
|
||||
if (count < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int outCount = calcOutSize(count);
|
||||
|
||||
memcpy(&buffer[tapsPerPhase], _in->readBuf, count * sizeof(T));
|
||||
_in->flush();
|
||||
|
||||
// Write to output
|
||||
int outIndex = 0;
|
||||
int _interp_m_1 = _interp - 1;
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
for (int i = 0; outIndex < outCount; i += _decim) {
|
||||
int phase = i % _interp;
|
||||
volk_32f_x2_dot_prod_32f(&out.writeBuf[outIndex], &buffer[i / _interp], tapPhases[phase], tapsPerPhase);
|
||||
outIndex++;
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
for (int i = 0; outIndex < outCount; i += _decim) {
|
||||
int phase = i % _interp;
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex], (lv_32fc_t*)&buffer[(i / _interp)], tapPhases[phase], tapsPerPhase);
|
||||
outIndex++;
|
||||
}
|
||||
}
|
||||
if (!out.swap(outCount)) { return -1; }
|
||||
|
||||
memmove(buffer, &buffer[count], tapsPerPhase * sizeof(T));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
void buildTapPhases(){
|
||||
if(!taps){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tapPhases.empty()){
|
||||
freeTapPhases();
|
||||
}
|
||||
|
||||
int phases = _interp;
|
||||
tapsPerPhase = (tapCount+phases-1)/phases; //Integer division ceiling
|
||||
|
||||
bufStart = &buffer[tapsPerPhase];
|
||||
|
||||
for(int i = 0; i < phases; i++){
|
||||
tapPhases.push_back((float*)volk_malloc(tapsPerPhase * sizeof(float), volk_get_alignment()));
|
||||
}
|
||||
|
||||
int currentTap = 0;
|
||||
for(int tap = 0; tap < tapsPerPhase; tap++) {
|
||||
for (int phase = 0; phase < phases; phase++) {
|
||||
if(currentTap < tapCount) {
|
||||
tapPhases[(_interp - 1) - phase][tap] = taps[currentTap++];
|
||||
}
|
||||
else{
|
||||
tapPhases[(_interp - 1) - phase][tap] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void freeTapPhases(){
|
||||
for(auto & tapPhase : tapPhases){
|
||||
volk_free(tapPhase);
|
||||
}
|
||||
tapPhases.clear();
|
||||
}
|
||||
|
||||
int count;
|
||||
stream<T>* _in;
|
||||
|
||||
dsp::filter_window::generic_window* _window;
|
||||
|
||||
T* bufStart;
|
||||
T* buffer;
|
||||
int tapCount;
|
||||
int _interp, _decim;
|
||||
float _inSampleRate, _outSampleRate;
|
||||
float* taps;
|
||||
|
||||
int tapsPerPhase;
|
||||
std::vector<float*> tapPhases;
|
||||
|
||||
};
|
||||
|
||||
class PowerDecimator : public generic_block<PowerDecimator> {
|
||||
public:
|
||||
PowerDecimator() {}
|
||||
|
||||
PowerDecimator(stream<complex_t>* in, unsigned int power) { init(in, power); }
|
||||
|
||||
~PowerDecimator() {
|
||||
generic_block<PowerDecimator>::stop();
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, unsigned int power) {
|
||||
_in = in;
|
||||
_power = power;
|
||||
generic_block<PowerDecimator>::registerInput(_in);
|
||||
generic_block<PowerDecimator>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<PowerDecimator>::ctrlMtx);
|
||||
generic_block<PowerDecimator>::tempStop();
|
||||
generic_block<PowerDecimator>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<PowerDecimator>::registerInput(_in);
|
||||
generic_block<PowerDecimator>::tempStart();
|
||||
}
|
||||
|
||||
void setPower(unsigned int power) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<PowerDecimator>::ctrlMtx);
|
||||
generic_block<PowerDecimator>::tempStop();
|
||||
generic_block<PowerDecimator>::unregisterInput(_in);
|
||||
_power = power;
|
||||
generic_block<PowerDecimator>::registerInput(_in);
|
||||
generic_block<PowerDecimator>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (_power == 0) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
}
|
||||
else if (_power == 1) {
|
||||
for (int j = 0; j < count; j += 2) {
|
||||
out.writeBuf[j / 2].i = (_in->readBuf[j].i + _in->readBuf[j + 1].i) * 0.5f;
|
||||
out.writeBuf[j / 2].q = (_in->readBuf[j].q + _in->readBuf[j + 1].q) * 0.5f;
|
||||
}
|
||||
count /= 2;
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
|
||||
if (_power > 1) {
|
||||
for (int i = 1; i < _power; i++) {
|
||||
for (int j = 0; j < count; j += 2) {
|
||||
out.writeBuf[j / 2].i = (_in->readBuf[j].i + _in->readBuf[j + 1].i) * 0.5f;
|
||||
out.writeBuf[j / 2].q = (_in->readBuf[j].q + _in->readBuf[j + 1].q) * 0.5f;
|
||||
}
|
||||
count /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
|
||||
private:
|
||||
int count;
|
||||
unsigned int _power = 0;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
}
|
189
core/src/dsp/routing.h
Normal file
189
core/src/dsp/routing.h
Normal file
@ -0,0 +1,189 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/buffer.h>
|
||||
#include <string.h>
|
||||
#include <numeric>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class Splitter : public generic_block<Splitter<T>> {
|
||||
public:
|
||||
Splitter() {}
|
||||
|
||||
Splitter(stream<T>* in) { init(in); }
|
||||
|
||||
~Splitter() { generic_block<Splitter>::stop(); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
generic_block<Splitter>::registerInput(_in);
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
|
||||
generic_block<Splitter>::tempStop();
|
||||
generic_block<Splitter>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Splitter>::registerInput(_in);
|
||||
generic_block<Splitter>::tempStart();
|
||||
}
|
||||
|
||||
void bindStream(stream<T>* stream) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
|
||||
generic_block<Splitter>::tempStop();
|
||||
out.push_back(stream);
|
||||
generic_block<Splitter>::registerOutput(stream);
|
||||
generic_block<Splitter>::tempStart();
|
||||
}
|
||||
|
||||
void unbindStream(stream<T>* stream) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
|
||||
generic_block<Splitter>::tempStop();
|
||||
generic_block<Splitter>::unregisterOutput(stream);
|
||||
out.erase(std::remove(out.begin(), out.end(), stream), out.end());
|
||||
generic_block<Splitter>::tempStart();
|
||||
}
|
||||
|
||||
private:
|
||||
int run() {
|
||||
// TODO: If too slow, buffering might be necessary
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
for (const auto& stream : out) {
|
||||
memcpy(stream->writeBuf, _in->readBuf, count * sizeof(T));
|
||||
if (!stream->swap(count)) { return -1; }
|
||||
}
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
std::vector<stream<T>*> out;
|
||||
|
||||
};
|
||||
|
||||
|
||||
// NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works...
|
||||
template <class T>
|
||||
class Reshaper : public generic_block<Reshaper<T>> {
|
||||
public:
|
||||
Reshaper() {}
|
||||
|
||||
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
|
||||
|
||||
~Reshaper() { generic_block<Reshaper<T>>::stop(); }
|
||||
|
||||
void init(stream<T>* in, int keep, int skip) {
|
||||
_in = in;
|
||||
_keep = keep;
|
||||
_skip = skip;
|
||||
ringBuf.init(keep * 2);
|
||||
generic_block<Reshaper<T>>::registerInput(_in);
|
||||
generic_block<Reshaper<T>>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
|
||||
generic_block<Reshaper<T>>::tempStop();
|
||||
generic_block<Reshaper<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Reshaper<T>>::registerInput(_in);
|
||||
generic_block<Reshaper<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setKeep(int keep) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
|
||||
generic_block<Reshaper<T>>::tempStop();
|
||||
generic_block<Reshaper<T>>::unregisterInput(_in);
|
||||
_keep = keep;
|
||||
ringBuf.setMaxLatency(keep * 2);
|
||||
generic_block<Reshaper<T>>::registerInput(_in);
|
||||
generic_block<Reshaper<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setSkip(int skip) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
|
||||
generic_block<Reshaper<T>>::tempStop();
|
||||
generic_block<Reshaper<T>>::unregisterInput(_in);
|
||||
_skip = skip;
|
||||
generic_block<Reshaper<T>>::registerInput(_in);
|
||||
generic_block<Reshaper<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
ringBuf.write(_in->readBuf, count);
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
void doStart() {
|
||||
workThread = std::thread(&Reshaper<T>::loop, this);
|
||||
bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while (run() >= 0);
|
||||
}
|
||||
|
||||
void doStop() {
|
||||
_in->stopReader();
|
||||
ringBuf.stopReader();
|
||||
out.stopWriter();
|
||||
ringBuf.stopWriter();
|
||||
|
||||
if (workThread.joinable()) {
|
||||
workThread.join();
|
||||
}
|
||||
if (bufferWorkerThread.joinable()) {
|
||||
bufferWorkerThread.join();
|
||||
}
|
||||
|
||||
_in->clearReadStop();
|
||||
ringBuf.clearReadStop();
|
||||
out.clearWriteStop();
|
||||
ringBuf.clearWriteStop();
|
||||
}
|
||||
|
||||
void bufferWorker() {
|
||||
complex_t* buf = new complex_t[_keep];
|
||||
bool delay = _skip < 0;
|
||||
|
||||
int readCount = std::min<int>(_keep + _skip, _keep);
|
||||
int skip = std::max<int>(_skip, 0);
|
||||
int delaySize = (-_skip) * sizeof(complex_t);
|
||||
int delayCount = (-_skip);
|
||||
|
||||
complex_t* start = &buf[std::max<int>(-_skip, 0)];
|
||||
complex_t* delayStart = &buf[_keep + _skip];
|
||||
|
||||
while (true) {
|
||||
if (delay) {
|
||||
memmove(buf, delayStart, delaySize);
|
||||
for (int i = 0; i < delayCount; i++) {
|
||||
buf[i].i /= 10.0f;
|
||||
buf[i].q /= 10.0f;
|
||||
}
|
||||
}
|
||||
if (ringBuf.readAndSkip(start, readCount, skip) < 0) { break; };
|
||||
memcpy(out.writeBuf, buf, _keep * sizeof(complex_t));
|
||||
if (!out.swap(_keep)) { break; }
|
||||
}
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
int _outBlockSize;
|
||||
RingBuffer<T> ringBuf;
|
||||
std::thread bufferWorkerThread;
|
||||
std::thread workThread;
|
||||
int _keep, _skip;
|
||||
|
||||
|
||||
};
|
||||
}
|
140
core/src/dsp/sink.h
Normal file
140
core/src/dsp/sink.h
Normal file
@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/buffer.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class HandlerSink : public generic_block<HandlerSink<T>> {
|
||||
public:
|
||||
HandlerSink() {}
|
||||
|
||||
HandlerSink(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { init(in, handler, ctx); }
|
||||
|
||||
~HandlerSink() { generic_block<HandlerSink<T>>::stop(); }
|
||||
|
||||
void init(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) {
|
||||
_in = in;
|
||||
_handler = handler;
|
||||
_ctx = ctx;
|
||||
generic_block<HandlerSink<T>>::registerInput(_in);
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
|
||||
generic_block<HandlerSink<T>>::tempStop();
|
||||
generic_block<HandlerSink<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<HandlerSink<T>>::registerInput(_in);
|
||||
generic_block<HandlerSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setHandler(void (*handler)(T* data, int count, void* ctx), void* ctx) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
|
||||
generic_block<HandlerSink<T>>::tempStop();
|
||||
_handler = handler;
|
||||
_ctx = ctx;
|
||||
generic_block<HandlerSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
_handler(_in->readBuf, count, _ctx);
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
int count;
|
||||
stream<T>* _in;
|
||||
void (*_handler)(T* data, int count, void* ctx);
|
||||
void* _ctx;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class RingBufferSink : public generic_block<RingBufferSink<T>> {
|
||||
public:
|
||||
RingBufferSink() {}
|
||||
|
||||
RingBufferSink(stream<T>* in) { init(in); }
|
||||
|
||||
~RingBufferSink() { generic_block<RingBufferSink<T>>::stop(); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
data.init(480); // TODO: Use an argument
|
||||
generic_block<RingBufferSink<T>>::registerInput(_in);
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<RingBufferSink<T>>::ctrlMtx);
|
||||
generic_block<RingBufferSink<T>>::tempStop();
|
||||
generic_block<RingBufferSink<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<RingBufferSink<T>>::registerInput(_in);
|
||||
generic_block<RingBufferSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
if (data.write(_in->readBuf, count) < 0) { return -1; }
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
RingBuffer<T> data;
|
||||
|
||||
private:
|
||||
void doStop() {
|
||||
_in->stopReader();
|
||||
data.stopWriter();
|
||||
if (generic_block<RingBufferSink<T>>::workerThread.joinable()) {
|
||||
generic_block<RingBufferSink<T>>::workerThread.join();
|
||||
}
|
||||
_in->clearReadStop();
|
||||
data.clearWriteStop();
|
||||
}
|
||||
|
||||
int count;
|
||||
stream<T>* _in;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class NullSink : public generic_block<NullSink<T>> {
|
||||
public:
|
||||
NullSink() {}
|
||||
|
||||
NullSink(stream<T>* in) { init(in); }
|
||||
|
||||
~NullSink() { generic_block<NullSink<T>>::stop(); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
generic_block<NullSink<T>>::registerInput(_in);
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<NullSink<T>>::ctrlMtx);
|
||||
generic_block<NullSink<T>>::tempStop();
|
||||
generic_block<NullSink<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<NullSink<T>>::registerInput(_in);
|
||||
generic_block<NullSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
int count;
|
||||
stream<T>* _in;
|
||||
|
||||
};
|
||||
}
|
74
core/src/dsp/source.h
Normal file
74
core/src/dsp/source.h
Normal file
@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
|
||||
namespace dsp {
|
||||
class SineSource : public generic_block<SineSource> {
|
||||
public:
|
||||
SineSource() {}
|
||||
|
||||
SineSource(int blockSize, float sampleRate, float freq) { init(blockSize, sampleRate, freq); }
|
||||
|
||||
~SineSource() { generic_block<SineSource>::stop(); }
|
||||
|
||||
void init(int blockSize, float sampleRate, float freq) {
|
||||
_blockSize = blockSize;
|
||||
_sampleRate = sampleRate;
|
||||
_freq = freq;
|
||||
zeroPhase = (lv_32fc_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(lv_32fc_t), volk_get_alignment());
|
||||
for (int i = 0; i < STREAM_BUFFER_SIZE; i++) {
|
||||
zeroPhase[i] = lv_cmake(1.0f, 0.0f);
|
||||
}
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
generic_block<SineSource>::registerOutput(&out);
|
||||
}
|
||||
|
||||
void setBlockSize(int blockSize) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<SineSource>::ctrlMtx);
|
||||
generic_block<SineSource>::tempStop();
|
||||
_blockSize = blockSize;
|
||||
generic_block<SineSource>::tempStart();
|
||||
}
|
||||
|
||||
int getBlockSize() {
|
||||
return _blockSize;
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
// No need to restart
|
||||
_sampleRate = sampleRate;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
void setFrequency(float freq) {
|
||||
// No need to restart
|
||||
_freq = freq;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getFrequency() {
|
||||
return _freq;
|
||||
}
|
||||
|
||||
int run() {
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, zeroPhase, phaseDelta, &phase, _blockSize);
|
||||
if(!out.swap(_blockSize)) { return -1; }
|
||||
return _blockSize;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
int _blockSize;
|
||||
float _sampleRate;
|
||||
float _freq;
|
||||
lv_32fc_t phaseDelta;
|
||||
lv_32fc_t phase;
|
||||
lv_32fc_t* zeroPhase;
|
||||
|
||||
};
|
||||
}
|
126
core/src/dsp/stream.h
Normal file
126
core/src/dsp/stream.h
Normal file
@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <volk/volk.h>
|
||||
|
||||
// 1MB buffer
|
||||
#define STREAM_BUFFER_SIZE 1000000
|
||||
|
||||
namespace dsp {
|
||||
class untyped_steam {
|
||||
public:
|
||||
virtual bool swap(int size) { return false; }
|
||||
virtual int read() { return -1; }
|
||||
virtual void flush() {}
|
||||
virtual void stopWriter() {}
|
||||
virtual void clearWriteStop() {}
|
||||
virtual void stopReader() {}
|
||||
virtual void clearReadStop() {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class stream : public untyped_steam {
|
||||
public:
|
||||
stream() {
|
||||
writeBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());
|
||||
readBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());
|
||||
}
|
||||
|
||||
~stream() {
|
||||
volk_free(writeBuf);
|
||||
volk_free(readBuf);
|
||||
}
|
||||
|
||||
bool swap(int size) {
|
||||
{
|
||||
// Wait to either swap or stop
|
||||
std::unique_lock<std::mutex> lck(swapMtx);
|
||||
swapCV.wait(lck, [this]{ return (canSwap || writerStop); });
|
||||
|
||||
// If writer was stopped, abandon operation
|
||||
if (writerStop) { return false; }
|
||||
|
||||
// Swap buffers
|
||||
dataSize = size;
|
||||
T* temp = writeBuf;
|
||||
writeBuf = readBuf;
|
||||
readBuf = temp;
|
||||
canSwap = false;
|
||||
}
|
||||
|
||||
// Notify reader that some data is ready
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(rdyMtx);
|
||||
dataReady = true;
|
||||
}
|
||||
rdyCV.notify_all();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int read() {
|
||||
// Wait for data to be ready or to be stopped
|
||||
std::unique_lock<std::mutex> lck(rdyMtx);
|
||||
rdyCV.wait(lck, [this]{ return (dataReady || readerStop); });
|
||||
|
||||
return (readerStop ? -1 : dataSize);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
// Clear data ready
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(rdyMtx);
|
||||
dataReady = false;
|
||||
}
|
||||
|
||||
// Notify writer that buffers can be swapped
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(swapMtx);
|
||||
canSwap = true;
|
||||
}
|
||||
|
||||
swapCV.notify_all();
|
||||
}
|
||||
|
||||
void stopWriter() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(swapMtx);
|
||||
writerStop = true;
|
||||
}
|
||||
swapCV.notify_all();
|
||||
}
|
||||
|
||||
void clearWriteStop() {
|
||||
writerStop = false;
|
||||
}
|
||||
|
||||
void stopReader() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(rdyMtx);
|
||||
readerStop = true;
|
||||
}
|
||||
rdyCV.notify_all();
|
||||
}
|
||||
|
||||
void clearReadStop() {
|
||||
readerStop = false;
|
||||
}
|
||||
|
||||
T* writeBuf;
|
||||
T* readBuf;
|
||||
|
||||
private:
|
||||
std::mutex swapMtx;
|
||||
std::condition_variable swapCV;
|
||||
bool canSwap = true;
|
||||
|
||||
std::mutex rdyMtx;
|
||||
std::condition_variable rdyCV;
|
||||
bool dataReady = false;
|
||||
|
||||
bool readerStop = false;
|
||||
bool writerStop = false;
|
||||
|
||||
int dataSize = 0;
|
||||
};
|
||||
}
|
@ -5,4 +5,9 @@ namespace dsp {
|
||||
float q;
|
||||
float i;
|
||||
};
|
||||
|
||||
struct stereo_t {
|
||||
float l;
|
||||
float r;
|
||||
};
|
||||
}
|
112
core/src/dsp/vfo.h
Normal file
112
core/src/dsp/vfo.h
Normal file
@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/window.h>
|
||||
#include <dsp/resampling.h>
|
||||
#include <dsp/processing.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace dsp {
|
||||
class VFO {
|
||||
public:
|
||||
VFO() {}
|
||||
|
||||
~VFO() { stop(); }
|
||||
|
||||
VFO(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
|
||||
init(in, offset, inSampleRate, outSampleRate, bandWidth);
|
||||
};
|
||||
|
||||
void init(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
|
||||
_in = in;
|
||||
_offset = offset;
|
||||
_inSampleRate = inSampleRate;
|
||||
_outSampleRate = outSampleRate;
|
||||
_bandWidth = bandWidth;
|
||||
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
|
||||
xlator.init(_in, _inSampleRate, -_offset);
|
||||
win.init(realCutoff, realCutoff, inSampleRate);
|
||||
resamp.init(&xlator.out, &win, _inSampleRate, _outSampleRate);
|
||||
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
resamp.updateWindow(&win);
|
||||
|
||||
out = &resamp.out;
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running) { return; }
|
||||
xlator.start();
|
||||
resamp.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running) { return; }
|
||||
xlator.stop();
|
||||
resamp.stop();
|
||||
}
|
||||
|
||||
void setInSampleRate(float inSampleRate) {
|
||||
_inSampleRate = inSampleRate;
|
||||
if (running) { xlator.stop(); resamp.stop(); }
|
||||
xlator.setSampleRate(_inSampleRate);
|
||||
resamp.setInSampleRate(_inSampleRate);
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
if (running) { xlator.start(); resamp.start(); }
|
||||
}
|
||||
|
||||
void setOutSampleRate(float outSampleRate) {
|
||||
_outSampleRate = outSampleRate;
|
||||
if (running) { resamp.stop(); }
|
||||
resamp.setOutSampleRate(_outSampleRate);
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
if (running) { resamp.start(); }
|
||||
}
|
||||
|
||||
void setOutSampleRate(float outSampleRate, float bandWidth) {
|
||||
_outSampleRate = outSampleRate;
|
||||
_bandWidth = bandWidth;
|
||||
if (running) { resamp.stop(); }
|
||||
resamp.setOutSampleRate(_outSampleRate);
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
if (running) { resamp.start(); }
|
||||
}
|
||||
|
||||
void setOffset(float offset) {
|
||||
_offset = offset;
|
||||
xlator.setFrequency(-_offset);
|
||||
}
|
||||
|
||||
void setBandwidth(float bandWidth) {
|
||||
_bandWidth = bandWidth;
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
}
|
||||
|
||||
stream<complex_t>* out;
|
||||
|
||||
private:
|
||||
bool running = false;
|
||||
float _offset, _inSampleRate, _outSampleRate, _bandWidth;
|
||||
filter_window::BlackmanWindow win;
|
||||
stream<complex_t>* _in;
|
||||
FrequencyXlator<complex_t> xlator;
|
||||
PolyphaseResampler<complex_t> resamp;
|
||||
|
||||
};
|
||||
}
|
76
core/src/dsp/window.h
Normal file
76
core/src/dsp/window.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace filter_window {
|
||||
class generic_window {
|
||||
public:
|
||||
virtual int getTapCount() { return -1; }
|
||||
virtual void createTaps(float* taps, int tapCount, float factor = 1.0f) {}
|
||||
};
|
||||
|
||||
class BlackmanWindow : public filter_window::generic_window {
|
||||
public:
|
||||
BlackmanWindow() {}
|
||||
BlackmanWindow(float cutoff, float transWidth, float sampleRate) { init(cutoff, transWidth, sampleRate); }
|
||||
|
||||
void init(float cutoff, float transWidth, float sampleRate) {
|
||||
_cutoff = cutoff;
|
||||
_transWidth = transWidth;
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void setCutoff(float cutoff) {
|
||||
_cutoff = cutoff;
|
||||
}
|
||||
|
||||
void setTransWidth(float transWidth) {
|
||||
_transWidth = transWidth;
|
||||
}
|
||||
|
||||
int getTapCount() {
|
||||
float fc = _cutoff / _sampleRate;
|
||||
if (fc > 1.0f) {
|
||||
fc = 1.0f;
|
||||
}
|
||||
|
||||
int _M = 4.0f / (_transWidth / _sampleRate);
|
||||
if (_M < 4) {
|
||||
_M = 4;
|
||||
}
|
||||
|
||||
if (_M % 2 == 0) { _M++; }
|
||||
|
||||
return _M;
|
||||
}
|
||||
|
||||
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
|
||||
float fc = _cutoff / _sampleRate;
|
||||
if (fc > 1.0f) {
|
||||
fc = 1.0f;
|
||||
}
|
||||
float tc = tapCount;
|
||||
float sum = 0.0f;
|
||||
float val;
|
||||
for (int i = 0; i < tapCount; i++) {
|
||||
val = (sin(2.0f * FL_M_PI * fc * ((float)i - (tc / 2))) / ((float)i - (tc / 2))) *
|
||||
(0.42f - (0.5f * cos(2.0f * FL_M_PI / tc)) + (0.8f * cos(4.0f * FL_M_PI / tc)));
|
||||
taps[i] = val; // tapCount - i - 1
|
||||
sum += val;
|
||||
}
|
||||
for (int i = 0; i < tapCount; i++) {
|
||||
taps[i] *= factor;
|
||||
taps[i] /= sum;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float _cutoff, _transWidth, _sampleRate;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
3783
core/src/duktape/duk_config.h
Normal file
3783
core/src/duktape/duk_config.h
Normal file
File diff suppressed because it is too large
Load Diff
185
core/src/duktape/duk_console.c
Normal file
185
core/src/duktape/duk_console.c
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Minimal 'console' binding.
|
||||
*
|
||||
* https://github.com/DeveloperToolsWG/console-object/blob/master/api.md
|
||||
* https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference
|
||||
* https://developer.mozilla.org/en/docs/Web/API/console
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "duktape.h"
|
||||
#include "duk_console.h"
|
||||
|
||||
/* XXX: Add some form of log level filtering. */
|
||||
|
||||
/* XXX: Should all output be written via e.g. console.write(formattedMsg)?
|
||||
* This would make it easier for user code to redirect all console output
|
||||
* to a custom backend.
|
||||
*/
|
||||
|
||||
/* XXX: Init console object using duk_def_prop() when that call is available. */
|
||||
|
||||
static duk_ret_t duk__console_log_helper(duk_context *ctx, const char *error_name) {
|
||||
duk_uint_t flags = (duk_uint_t) duk_get_current_magic(ctx);
|
||||
FILE *output = (flags & DUK_CONSOLE_STDOUT_ONLY) ? stdout : stderr;
|
||||
duk_idx_t n = duk_get_top(ctx);
|
||||
duk_idx_t i;
|
||||
|
||||
duk_get_global_string(ctx, "console");
|
||||
duk_get_prop_string(ctx, -1, "format");
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (duk_check_type_mask(ctx, i, DUK_TYPE_MASK_OBJECT)) {
|
||||
/* Slow path formatting. */
|
||||
duk_dup(ctx, -1); /* console.format */
|
||||
duk_dup(ctx, i);
|
||||
duk_call(ctx, 1);
|
||||
duk_replace(ctx, i); /* arg[i] = console.format(arg[i]); */
|
||||
}
|
||||
}
|
||||
|
||||
duk_pop_2(ctx);
|
||||
|
||||
duk_push_string(ctx, " ");
|
||||
duk_insert(ctx, 0);
|
||||
duk_join(ctx, n);
|
||||
|
||||
if (error_name) {
|
||||
duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_require_string(ctx, -1));
|
||||
duk_push_string(ctx, "name");
|
||||
duk_push_string(ctx, error_name);
|
||||
duk_def_prop(ctx, -3, DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE); /* to get e.g. 'Trace: 1 2 3' */
|
||||
duk_get_prop_string(ctx, -1, "stack");
|
||||
}
|
||||
|
||||
fprintf(output, "%s\n", duk_to_string(ctx, -1));
|
||||
if (flags & DUK_CONSOLE_FLUSH) {
|
||||
fflush(output);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static duk_ret_t duk__console_assert(duk_context *ctx) {
|
||||
if (duk_to_boolean(ctx, 0)) {
|
||||
return 0;
|
||||
}
|
||||
duk_remove(ctx, 0);
|
||||
|
||||
return duk__console_log_helper(ctx, "AssertionError");
|
||||
}
|
||||
|
||||
static duk_ret_t duk__console_log(duk_context *ctx) {
|
||||
return duk__console_log_helper(ctx, NULL);
|
||||
}
|
||||
|
||||
static duk_ret_t duk__console_trace(duk_context *ctx) {
|
||||
return duk__console_log_helper(ctx, "Trace");
|
||||
}
|
||||
|
||||
static duk_ret_t duk__console_info(duk_context *ctx) {
|
||||
return duk__console_log_helper(ctx, NULL);
|
||||
}
|
||||
|
||||
static duk_ret_t duk__console_warn(duk_context *ctx) {
|
||||
return duk__console_log_helper(ctx, NULL);
|
||||
}
|
||||
|
||||
static duk_ret_t duk__console_error(duk_context *ctx) {
|
||||
return duk__console_log_helper(ctx, "Error");
|
||||
}
|
||||
|
||||
static duk_ret_t duk__console_dir(duk_context *ctx) {
|
||||
/* For now, just share the formatting of .log() */
|
||||
return duk__console_log_helper(ctx, 0);
|
||||
}
|
||||
|
||||
static void duk__console_reg_vararg_func(duk_context *ctx, duk_c_function func, const char *name, duk_uint_t flags) {
|
||||
duk_push_c_function(ctx, func, DUK_VARARGS);
|
||||
duk_push_string(ctx, "name");
|
||||
duk_push_string(ctx, name);
|
||||
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); /* Improve stacktraces by displaying function name */
|
||||
duk_set_magic(ctx, -1, (duk_int_t) flags);
|
||||
duk_put_prop_string(ctx, -2, name);
|
||||
}
|
||||
|
||||
void duk_console_init(duk_context *ctx, duk_uint_t flags) {
|
||||
duk_uint_t flags_orig;
|
||||
|
||||
/* If both DUK_CONSOLE_STDOUT_ONLY and DUK_CONSOLE_STDERR_ONLY where specified,
|
||||
* just turn off DUK_CONSOLE_STDOUT_ONLY and keep DUK_CONSOLE_STDERR_ONLY.
|
||||
*/
|
||||
if ((flags & DUK_CONSOLE_STDOUT_ONLY) && (flags & DUK_CONSOLE_STDERR_ONLY)) {
|
||||
flags &= ~DUK_CONSOLE_STDOUT_ONLY;
|
||||
}
|
||||
/* Remember the (possibly corrected) flags we received. */
|
||||
flags_orig = flags;
|
||||
|
||||
duk_push_object(ctx);
|
||||
|
||||
/* Custom function to format objects; user can replace.
|
||||
* For now, try JX-formatting and if that fails, fall back
|
||||
* to ToString(v).
|
||||
*/
|
||||
duk_eval_string(ctx,
|
||||
"(function (E) {"
|
||||
"return function format(v){"
|
||||
"try{"
|
||||
"return E('jx',v);"
|
||||
"}catch(e){"
|
||||
"return String(v);" /* String() allows symbols, ToString() internal algorithm doesn't. */
|
||||
"}"
|
||||
"};"
|
||||
"})(Duktape.enc)");
|
||||
duk_put_prop_string(ctx, -2, "format");
|
||||
|
||||
flags = flags_orig;
|
||||
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
|
||||
/* No output indicators were specified; these levels go to stdout. */
|
||||
flags |= DUK_CONSOLE_STDOUT_ONLY;
|
||||
}
|
||||
duk__console_reg_vararg_func(ctx, duk__console_assert, "assert", flags);
|
||||
duk__console_reg_vararg_func(ctx, duk__console_log, "log", flags);
|
||||
duk__console_reg_vararg_func(ctx, duk__console_log, "debug", flags); /* alias to console.log */
|
||||
duk__console_reg_vararg_func(ctx, duk__console_trace, "trace", flags);
|
||||
duk__console_reg_vararg_func(ctx, duk__console_info, "info", flags);
|
||||
|
||||
flags = flags_orig;
|
||||
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
|
||||
/* No output indicators were specified; these levels go to stderr. */
|
||||
flags |= DUK_CONSOLE_STDERR_ONLY;
|
||||
}
|
||||
duk__console_reg_vararg_func(ctx, duk__console_warn, "warn", flags);
|
||||
duk__console_reg_vararg_func(ctx, duk__console_error, "error", flags);
|
||||
duk__console_reg_vararg_func(ctx, duk__console_error, "exception", flags); /* alias to console.error */
|
||||
duk__console_reg_vararg_func(ctx, duk__console_dir, "dir", flags);
|
||||
|
||||
duk_put_global_string(ctx, "console");
|
||||
|
||||
/* Proxy wrapping: ensures any undefined console method calls are
|
||||
* ignored silently. This was required specifically by the
|
||||
* DeveloperToolsWG proposal (and was implemented also by Firefox:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=629607). This is
|
||||
* apparently no longer the preferred way of implementing console.
|
||||
* When Proxy is enabled, whitelist at least .toJSON() to avoid
|
||||
* confusing JX serialization of the console object.
|
||||
*/
|
||||
|
||||
if (flags & DUK_CONSOLE_PROXY_WRAPPER) {
|
||||
/* Tolerate failure to initialize Proxy wrapper in case
|
||||
* Proxy support is disabled.
|
||||
*/
|
||||
(void) duk_peval_string_noresult(ctx,
|
||||
"(function(){"
|
||||
"var D=function(){};"
|
||||
"var W={toJSON:true};" /* whitelisted */
|
||||
"console=new Proxy(console,{"
|
||||
"get:function(t,k){"
|
||||
"var v=t[k];"
|
||||
"return typeof v==='function'||W[k]?v:D;"
|
||||
"}"
|
||||
"});"
|
||||
"})();"
|
||||
);
|
||||
}
|
||||
}
|
29
core/src/duktape/duk_console.h
Normal file
29
core/src/duktape/duk_console.h
Normal file
@ -0,0 +1,29 @@
|
||||
#if !defined(DUK_CONSOLE_H_INCLUDED)
|
||||
#define DUK_CONSOLE_H_INCLUDED
|
||||
|
||||
#include "duktape.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Use a proxy wrapper to make undefined methods (console.foo()) no-ops. */
|
||||
#define DUK_CONSOLE_PROXY_WRAPPER (1U << 0)
|
||||
|
||||
/* Flush output after every call. */
|
||||
#define DUK_CONSOLE_FLUSH (1U << 1)
|
||||
|
||||
/* Send output to stdout only (default is mixed stdout/stderr). */
|
||||
#define DUK_CONSOLE_STDOUT_ONLY (1U << 2)
|
||||
|
||||
/* Send output to stderr only (default is mixed stdout/stderr). */
|
||||
#define DUK_CONSOLE_STDERR_ONLY (1U << 3)
|
||||
|
||||
/* Initialize the console system */
|
||||
extern void duk_console_init(duk_context *ctx, duk_uint_t flags);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* end 'extern "C"' wrapper */
|
||||
|
||||
#endif /* DUK_CONSOLE_H_INCLUDED */
|
99755
core/src/duktape/duktape.c
Normal file
99755
core/src/duktape/duktape.c
Normal file
File diff suppressed because it is too large
Load Diff
1450
core/src/duktape/duktape.h
Normal file
1450
core/src/duktape/duktape.h
Normal file
File diff suppressed because it is too large
Load Diff
43
core/src/event.h
Normal file
43
core/src/event.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
template <class T>
|
||||
class Event {
|
||||
public:
|
||||
Event() {}
|
||||
~Event() {}
|
||||
|
||||
struct EventHandler {
|
||||
EventHandler() {}
|
||||
EventHandler(void (*handler)(T, void*), void* ctx) {
|
||||
this->handler = handler;
|
||||
this->ctx = ctx;
|
||||
}
|
||||
|
||||
void (*handler)(T, void*);
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
void emit(T value) {
|
||||
for (auto const& handler : handlers) {
|
||||
handler.handler(value, handler.ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void bindHandler(const EventHandler& handler) {
|
||||
handlers.push_back(handler);
|
||||
}
|
||||
|
||||
void unbindHandler(const EventHandler& handler) {
|
||||
if (handlers.find(handler) == handlers.end()) {
|
||||
spdlog::error("Tried to remove a non-existant event handler");
|
||||
return;
|
||||
}
|
||||
handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<EventHandler> handlers;
|
||||
|
||||
};
|
49
core/src/gui/colormaps.cpp
Normal file
49
core/src/gui/colormaps.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <gui/colormaps.h>
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <fstream>
|
||||
#include <json.hpp>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace colormaps {
|
||||
std::map<std::string, Map> maps;
|
||||
|
||||
void loadMap(std::string path) {
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
spdlog::error("Could not load {0}, file doesn't exist", path);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(path.c_str());
|
||||
json data;
|
||||
file >> data;
|
||||
file.close();
|
||||
|
||||
Map map;
|
||||
std::vector<std::string> mapTxt;
|
||||
|
||||
try {
|
||||
map.name = data["name"];
|
||||
map.author = data["author"];
|
||||
mapTxt = data["map"].get<std::vector<std::string>>();
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
spdlog::error("Could not load {0}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
map.entryCount = mapTxt.size();
|
||||
map.map = new float[mapTxt.size() * 3];
|
||||
int i = 0;
|
||||
for(auto const& col : mapTxt) {
|
||||
uint8_t r, g, b, a;
|
||||
map.map[i * 3] = std::stoi(col.substr(1, 2), NULL, 16);
|
||||
map.map[(i * 3) + 1] = std::stoi(col.substr(3, 2), NULL, 16);
|
||||
map.map[(i * 3) + 2] = std::stoi(col.substr(5, 2), NULL, 16);
|
||||
i++;
|
||||
}
|
||||
|
||||
maps[map.name] = map;
|
||||
}
|
||||
}
|
18
core/src/gui/colormaps.h
Normal file
18
core/src/gui/colormaps.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <module.h>
|
||||
#include <map>
|
||||
|
||||
namespace colormaps {
|
||||
struct Map {
|
||||
std::string name;
|
||||
std::string author;
|
||||
float* map;
|
||||
int entryCount;
|
||||
};
|
||||
|
||||
void loadMap(std::string path);
|
||||
|
||||
SDRPP_EXPORT std::map<std::string, Map> maps;
|
||||
}
|
61
core/src/gui/dialogs/credits.cpp
Normal file
61
core/src/gui/dialogs/credits.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include <gui/dialogs/credits.h>
|
||||
#include <imgui.h>
|
||||
#include <gui/icons.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <credits.h>
|
||||
#include <version.h>
|
||||
|
||||
namespace credits {
|
||||
ImFont* bigFont;
|
||||
|
||||
void init() {
|
||||
|
||||
}
|
||||
|
||||
void show() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
|
||||
ImGui::OpenPopup("Credits");
|
||||
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
|
||||
|
||||
ImGui::PushFont(style::hugeFont);
|
||||
ImGui::Text("SDR++ ");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::Image(icons::LOGO, ImVec2(128, 128));
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("This software is brought to you by\n\n");
|
||||
|
||||
ImGui::Columns(3, "CreditColumns", true);
|
||||
|
||||
ImGui::Text("Contributors");
|
||||
for (int i = 0; i < sdrpp_credits::contributorCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::contributors[i]);
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Libraries");
|
||||
for (int i = 0; i < sdrpp_credits::libraryCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::libraries[i]);
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Patrons");
|
||||
for (int i = 0; i < sdrpp_credits::patronCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::patrons[i]);
|
||||
}
|
||||
|
||||
ImGui::Columns(1, "CreditColumnsEnd", true);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("SDR++ v" VERSION_STR);
|
||||
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleVar(1);
|
||||
}
|
||||
}
|
6
core/src/gui/dialogs/credits.h
Normal file
6
core/src/gui/dialogs/credits.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace credits {
|
||||
void init();
|
||||
void show();
|
||||
}
|
85
core/src/gui/dialogs/loading_screen.cpp
Normal file
85
core/src/gui/dialogs/loading_screen.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include <GL/glew.h>
|
||||
#include <gui/dialogs/loading_screen.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <imgui.h>
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <gui/icons.h>
|
||||
#include <gui/style.h>
|
||||
#include <credits.h>
|
||||
|
||||
namespace LoadingScreen {
|
||||
GLFWwindow* _win;
|
||||
|
||||
void setWindow(GLFWwindow* win) {
|
||||
_win = win;
|
||||
}
|
||||
|
||||
void show(std::string msg) {
|
||||
glfwPollEvents();
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
|
||||
ImGui::NewFrame();
|
||||
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
|
||||
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
|
||||
ImGui::OpenPopup("Credits");
|
||||
ImGui::PushStyleColor(ImGuiCol_ModalWindowDarkening, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground);
|
||||
|
||||
ImGui::PushFont(style::hugeFont);
|
||||
ImGui::Text("SDR++ ");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::Image(icons::LOGO, ImVec2(128, 128));
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("This software is brought to you by\n\n");
|
||||
|
||||
ImGui::Columns(3, "CreditColumns", true);
|
||||
|
||||
ImGui::Text("Contributors");
|
||||
for (int i = 0; i < sdrpp_credits::contributorCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::contributors[i]);
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Libraries");
|
||||
for (int i = 0; i < sdrpp_credits::libraryCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::libraries[i]);
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Patrons");
|
||||
for (int i = 0; i < sdrpp_credits::patronCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::patrons[i]);
|
||||
}
|
||||
|
||||
ImGui::Columns(1, "CreditColumnsEnd", true);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("%s", msg.c_str());
|
||||
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleVar(1);
|
||||
ImGui::PopStyleColor(1);
|
||||
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(_win, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(_win);
|
||||
}
|
||||
}
|
10
core/src/gui/dialogs/loading_screen.h
Normal file
10
core/src/gui/dialogs/loading_screen.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
namespace LoadingScreen {
|
||||
void setWindow(GLFWwindow* win);
|
||||
void show(std::string msg);
|
||||
};
|
7
core/src/gui/gui.cpp
Normal file
7
core/src/gui/gui.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include <gui/gui.h>
|
||||
|
||||
namespace gui {
|
||||
ImGui::WaterFall waterfall;
|
||||
FrequencySelect freqSelect;
|
||||
Menu menu;
|
||||
};
|
14
core/src/gui/gui.h
Normal file
14
core/src/gui/gui.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <gui/widgets/waterfall.h>
|
||||
#include <gui/widgets/frequency_select.h>
|
||||
#include <gui/widgets/menu.h>
|
||||
#include <gui/dialogs/loading_screen.h>
|
||||
#include <module.h>
|
||||
|
||||
namespace gui {
|
||||
SDRPP_EXPORT ImGui::WaterFall waterfall;
|
||||
SDRPP_EXPORT FrequencySelect freqSelect;
|
||||
SDRPP_EXPORT Menu menu;
|
||||
|
||||
void selectSource(std::string name);
|
||||
};
|
53
core/src/gui/icons.cpp
Normal file
53
core/src/gui/icons.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <gui/icons.h>
|
||||
#include <stdint.h>
|
||||
#include <GL/glew.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <imgui/stb_image.h>
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace icons {
|
||||
ImTextureID LOGO;
|
||||
ImTextureID PLAY;
|
||||
ImTextureID STOP;
|
||||
ImTextureID MENU;
|
||||
ImTextureID MUTED;
|
||||
ImTextureID UNMUTED;
|
||||
ImTextureID NORMAL_TUNING;
|
||||
ImTextureID CENTER_TUNING;
|
||||
|
||||
GLuint loadTexture(std::string path) {
|
||||
int w,h,n;
|
||||
stbi_uc* data = stbi_load(path.c_str(), &w, &h, &n, 0);
|
||||
GLuint texId;
|
||||
glGenTextures(1, &texId);
|
||||
glBindTexture(GL_TEXTURE_2D, texId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)data);
|
||||
stbi_image_free(data);
|
||||
return texId;
|
||||
}
|
||||
|
||||
bool load(std::string resDir) {
|
||||
if (!std::filesystem::is_directory(resDir)) {
|
||||
spdlog::error("Inavlid resource directory: {0}", resDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGO = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/sdrpp.png");
|
||||
PLAY = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/play.png");
|
||||
STOP = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/stop.png");
|
||||
MENU = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/menu.png");
|
||||
MUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/muted.png");
|
||||
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
|
||||
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
|
||||
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
18
core/src/gui/icons.h
Normal file
18
core/src/gui/icons.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <imgui/imgui.h>
|
||||
#include <GL/glew.h>
|
||||
#include <string>
|
||||
|
||||
namespace icons {
|
||||
extern ImTextureID LOGO;
|
||||
extern ImTextureID PLAY;
|
||||
extern ImTextureID STOP;
|
||||
extern ImTextureID MENU;
|
||||
extern ImTextureID MUTED;
|
||||
extern ImTextureID UNMUTED;
|
||||
extern ImTextureID NORMAL_TUNING;
|
||||
extern ImTextureID CENTER_TUNING;
|
||||
|
||||
GLuint loadTexture(std::string path);
|
||||
bool load(std::string resDir);
|
||||
}
|
642
core/src/gui/main_window.cpp
Normal file
642
core/src/gui/main_window.cpp
Normal file
@ -0,0 +1,642 @@
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/gui.h>
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <stdio.h>
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <imgui_plot.h>
|
||||
#include <thread>
|
||||
#include <complex>
|
||||
#include <gui/widgets/waterfall.h>
|
||||
#include <gui/widgets/frequency_select.h>
|
||||
#include <fftw3.h>
|
||||
#include <signal_path/dsp.h>
|
||||
#include <gui/icons.h>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <watcher.h>
|
||||
#include <signal_path/vfo_manager.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/menus/source.h>
|
||||
#include <gui/menus/display.h>
|
||||
#include <gui/menus/bandplan.h>
|
||||
#include <gui/menus/sink.h>
|
||||
#include <gui/menus/scripting.h>
|
||||
#include <gui/dialogs/credits.h>
|
||||
#include <filesystem>
|
||||
#include <signal_path/source.h>
|
||||
#include <gui/dialogs/loading_screen.h>
|
||||
#include <options.h>
|
||||
#include <gui/colormaps.h>
|
||||
|
||||
// const int FFTSizes[] = {
|
||||
// 65536,
|
||||
// 32768,
|
||||
// 16384,
|
||||
// 8192,
|
||||
// 4096,
|
||||
// 2048
|
||||
// };
|
||||
|
||||
// const char* FFTSizesStr[] = {
|
||||
// "65536",
|
||||
// "32768",
|
||||
// "16384",
|
||||
// "8192",
|
||||
// "4096",
|
||||
// "2048"
|
||||
// };
|
||||
|
||||
// int fftSizeId = 0;
|
||||
int fftSize = 8192 * 8;
|
||||
|
||||
std::thread worker;
|
||||
std::mutex fft_mtx;
|
||||
fftwf_complex *fft_in, *fft_out;
|
||||
fftwf_plan p;
|
||||
float* tempFFT;
|
||||
float* FFTdata;
|
||||
char buf[1024];
|
||||
bool experimentalZoom = false;
|
||||
|
||||
|
||||
|
||||
void fftHandler(dsp::complex_t* samples, int count, void* ctx) {
|
||||
memcpy(fft_in, samples, count * sizeof(dsp::complex_t));
|
||||
fftwf_execute(p);
|
||||
int half = count / 2;
|
||||
|
||||
volk_32fc_s32f_power_spectrum_32f(tempFFT, (lv_32fc_t*)fft_out, count, count);
|
||||
volk_32f_s32f_multiply_32f(FFTdata, tempFFT, 0.5f, count);
|
||||
|
||||
memcpy(tempFFT, &FFTdata[half], half * sizeof(float));
|
||||
memmove(&FFTdata[half], FFTdata, half * sizeof(float));
|
||||
memcpy(FFTdata, tempFFT, half * sizeof(float));
|
||||
|
||||
float* fftBuf = gui::waterfall.getFFTBuffer();
|
||||
if (fftBuf == NULL) {
|
||||
gui::waterfall.pushFFT();
|
||||
return;
|
||||
}
|
||||
float last = FFTdata[0];
|
||||
for (int i = 0; i < count; i++) {
|
||||
last = (FFTdata[i] * 0.1f) + (last * 0.9f);
|
||||
fftBuf[i] = last;
|
||||
}
|
||||
gui::waterfall.pushFFT();
|
||||
}
|
||||
|
||||
watcher<uint64_t> freq((uint64_t)90500000);
|
||||
watcher<double> vfoFreq(92000000.0);
|
||||
float fftMin = -70.0;
|
||||
float fftMax = 0.0;
|
||||
watcher<double> offset(0.0, true);
|
||||
float bw = 8000000;
|
||||
bool playing = false;
|
||||
watcher<bool> dcbias(false, false);
|
||||
bool showCredits = false;
|
||||
std::string audioStreamName = "";
|
||||
std::string sourceName = "";
|
||||
int menuWidth = 300;
|
||||
bool grabbingMenu = false;
|
||||
int newWidth = 300;
|
||||
int fftHeight = 300;
|
||||
bool showMenu = true;
|
||||
bool centerTuning = false;
|
||||
dsp::stream<dsp::complex_t> dummyStream;
|
||||
bool demoWindow = false;
|
||||
|
||||
void windowInit() {
|
||||
LoadingScreen::show("Initializing UI");
|
||||
gui::waterfall.init();
|
||||
gui::waterfall.setRawFFTSize(fftSize);
|
||||
|
||||
tempFFT = new float[fftSize];
|
||||
FFTdata = new float[fftSize];
|
||||
|
||||
credits::init();
|
||||
|
||||
core::configManager.aquire();
|
||||
gui::menu.order = core::configManager.conf["menuOrder"].get<std::vector<std::string>>();
|
||||
std::string modulesDir = core::configManager.conf["modulesDirectory"];
|
||||
std::string resourcesDir = core::configManager.conf["resourcesDirectory"];
|
||||
core::configManager.release();
|
||||
|
||||
gui::menu.registerEntry("Source", sourecmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Scripting", scriptingmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
|
||||
|
||||
gui::freqSelect.init();
|
||||
|
||||
// Set default values for waterfall in case no source init's it
|
||||
gui::waterfall.setBandwidth(8000000);
|
||||
gui::waterfall.setViewBandwidth(8000000);
|
||||
|
||||
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
p = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
sigpath::signalPath.init(8000000, 20, fftSize, &dummyStream, (dsp::complex_t*)fft_in, fftHandler);
|
||||
sigpath::signalPath.start();
|
||||
|
||||
spdlog::info("Loading modules");
|
||||
|
||||
// Load modules from /module directory
|
||||
if (std::filesystem::is_directory(modulesDir)) {
|
||||
for (const auto & file : std::filesystem::directory_iterator(modulesDir)) {
|
||||
std::string path = file.path().generic_string();
|
||||
if (file.path().extension().generic_string() != SDRPP_MOD_EXTENTSION) {
|
||||
continue;
|
||||
}
|
||||
if (!file.is_regular_file()) { continue; }
|
||||
spdlog::info("Loading {0}", path);
|
||||
LoadingScreen::show("Loading " + path);
|
||||
core::moduleManager.loadModule(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
spdlog::warn("Module directory {0} does not exist, not loading modules from directory", modulesDir);
|
||||
}
|
||||
|
||||
// Read module config
|
||||
core::configManager.aquire();
|
||||
std::vector<std::string> modules = core::configManager.conf["modules"];
|
||||
std::map<std::string, std::string> modList = core::configManager.conf["moduleInstances"];
|
||||
core::configManager.release();
|
||||
|
||||
// Load additional modules specified through config
|
||||
for (auto const& path : modules) {
|
||||
spdlog::info("Loading {0}", path);
|
||||
LoadingScreen::show("Loading " + path);
|
||||
core::moduleManager.loadModule(path);
|
||||
}
|
||||
|
||||
// Create module instances
|
||||
for (auto const& [name, module] : modList) {
|
||||
spdlog::info("Initializing {0} ({1})", name, module);
|
||||
LoadingScreen::show("Initializing " + name + " (" + module + ")");
|
||||
core::moduleManager.createInstance(name, module);
|
||||
}
|
||||
|
||||
// Load color maps
|
||||
LoadingScreen::show("Loading color maps");
|
||||
spdlog::info("Loading color maps");
|
||||
if (std::filesystem::is_directory(resourcesDir + "/colormaps")) {
|
||||
for (const auto & file : std::filesystem::directory_iterator(resourcesDir + "/colormaps")) {
|
||||
std::string path = file.path().generic_string();
|
||||
LoadingScreen::show("Loading " + path);
|
||||
spdlog::info("Loading {0}", path);
|
||||
if (file.path().extension().generic_string() != ".json") {
|
||||
continue;
|
||||
}
|
||||
if (!file.is_regular_file()) { continue; }
|
||||
colormaps::loadMap(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
spdlog::warn("Color map directory {0} does not exist, not loading modules from directory", modulesDir);
|
||||
}
|
||||
|
||||
gui::waterfall.updatePalletteFromArray(colormaps::maps["Turbo"].map, colormaps::maps["Turbo"].entryCount);
|
||||
|
||||
sourecmenu::init();
|
||||
sinkmenu::init();
|
||||
scriptingmenu::init();
|
||||
bandplanmenu::init();
|
||||
displaymenu::init();
|
||||
|
||||
// TODO for 0.2.5
|
||||
// Add "select file" option for the file source
|
||||
// Have a good directory system on both linux and windows
|
||||
// Switch to double buffering (should fix occassional underruns)
|
||||
// Fix gain not updated on startup, soapysdr
|
||||
|
||||
// TODO for 0.2.6
|
||||
// Add a module add/remove/change order menu
|
||||
|
||||
// Update UI settings
|
||||
LoadingScreen::show("Loading configuration");
|
||||
core::configManager.aquire();
|
||||
fftMin = core::configManager.conf["min"];
|
||||
fftMax = core::configManager.conf["max"];
|
||||
gui::waterfall.setFFTMin(fftMin);
|
||||
gui::waterfall.setWaterfallMin(fftMin);
|
||||
gui::waterfall.setFFTMax(fftMax);
|
||||
gui::waterfall.setWaterfallMax(fftMax);
|
||||
|
||||
double frequency = core::configManager.conf["frequency"];
|
||||
|
||||
gui::freqSelect.setFrequency(frequency);
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
sigpath::sourceManager.tune(frequency);
|
||||
gui::waterfall.setCenterFrequency(frequency);
|
||||
bw = gui::waterfall.getBandwidth();
|
||||
gui::waterfall.vfoFreqChanged = false;
|
||||
gui::waterfall.centerFreqMoved = false;
|
||||
gui::waterfall.selectFirstVFO();
|
||||
|
||||
menuWidth = core::configManager.conf["menuWidth"];
|
||||
newWidth = menuWidth;
|
||||
|
||||
fftHeight = core::configManager.conf["fftHeight"];
|
||||
gui::waterfall.setFFTHeight(fftHeight);
|
||||
|
||||
centerTuning = core::configManager.conf["centerTuning"];
|
||||
|
||||
core::configManager.release();
|
||||
}
|
||||
|
||||
void setVFO(double freq) {
|
||||
double viewBW = gui::waterfall.getViewBandwidth();
|
||||
double BW = gui::waterfall.getBandwidth();
|
||||
if (gui::waterfall.selectedVFO == "") {
|
||||
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
|
||||
gui::waterfall.setCenterFrequency(freq);
|
||||
sigpath::sourceManager.tune(freq);
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
|
||||
|
||||
double currentOff = vfo->centerOffset;
|
||||
double currentTune = gui::waterfall.getCenterFrequency() + vfo->generalOffset;
|
||||
double delta = freq - currentTune;
|
||||
|
||||
double newVFO = currentOff + delta;
|
||||
double vfoBW = vfo->bandwidth;
|
||||
double vfoBottom = newVFO - (vfoBW / 2.0);
|
||||
double vfoTop = newVFO + (vfoBW / 2.0);
|
||||
|
||||
double view = gui::waterfall.getViewOffset();
|
||||
double viewBottom = view - (viewBW / 2.0);
|
||||
double viewTop = view + (viewBW / 2.0);
|
||||
|
||||
double wholeFreq = gui::waterfall.getCenterFrequency();
|
||||
double bottom = -(BW / 2.0);
|
||||
double top = (BW / 2.0);
|
||||
|
||||
if (centerTuning) {
|
||||
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
|
||||
gui::waterfall.setCenterFrequency(freq);
|
||||
gui::waterfall.setViewOffset(0);
|
||||
sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, 0);
|
||||
sigpath::sourceManager.tune(freq);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO still fints in the view
|
||||
if (vfoBottom > viewBottom && vfoTop < viewTop) {
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO too low for current SDR tuning
|
||||
if (vfoBottom < bottom) {
|
||||
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
|
||||
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
|
||||
sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO too high for current SDR tuning
|
||||
if (vfoTop > top) {
|
||||
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
|
||||
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
|
||||
sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO is still without the SDR's bandwidth
|
||||
if (delta < 0) {
|
||||
double newViewOff = vfoTop - (viewBW / 2.0) + (viewBW / 10.0);
|
||||
double newViewBottom = newViewOff - (viewBW / 2.0);
|
||||
double newViewTop = newViewOff + (viewBW / 2.0);
|
||||
|
||||
if (newViewBottom > bottom) {
|
||||
gui::waterfall.setViewOffset(newViewOff);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
|
||||
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
}
|
||||
else {
|
||||
double newViewOff = vfoBottom + (viewBW / 2.0) - (viewBW / 10.0);
|
||||
double newViewBottom = newViewOff - (viewBW / 2.0);
|
||||
double newViewTop = newViewOff + (viewBW / 2.0);
|
||||
|
||||
if (newViewTop < top) {
|
||||
gui::waterfall.setViewOffset(newViewOff);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
|
||||
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
|
||||
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void drawWindow() {
|
||||
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
|
||||
|
||||
ImGui::WaterfallVFO* vfo = NULL;
|
||||
if (gui::waterfall.selectedVFO != "") {
|
||||
vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
|
||||
}
|
||||
|
||||
if (vfo != NULL) {
|
||||
if (vfo->centerOffsetChanged) {
|
||||
if (centerTuning) {
|
||||
setVFO(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
}
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
sigpath::vfoManager.updateFromWaterfall(&gui::waterfall);
|
||||
|
||||
if (gui::waterfall.selectedVFOChanged && vfo != NULL) {
|
||||
gui::waterfall.selectedVFOChanged = false;
|
||||
gui::freqSelect.setFrequency(vfo->generalOffset + gui::waterfall.getCenterFrequency());
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (gui::freqSelect.frequencyChanged) {
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
setVFO(gui::freqSelect.frequency);
|
||||
if (vfo != NULL) {
|
||||
vfo->centerOffsetChanged = false;
|
||||
vfo->lowerOffsetChanged = false;
|
||||
vfo->upperOffsetChanged = false;
|
||||
}
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (gui::waterfall.centerFreqMoved) {
|
||||
gui::waterfall.centerFreqMoved = false;
|
||||
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
|
||||
if (vfo != NULL) {
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
}
|
||||
else {
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency());
|
||||
}
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
int _fftHeight = gui::waterfall.getFFTHeight();
|
||||
if (fftHeight != _fftHeight) {
|
||||
fftHeight = _fftHeight;
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["fftHeight"] = fftHeight;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
|
||||
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
|
||||
|
||||
int width = vMax.x - vMin.x;
|
||||
int height = vMax.y - vMin.y;
|
||||
|
||||
// To Bar
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_menu_btn"));
|
||||
if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
|
||||
showMenu = !showMenu;
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (playing) {
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_stop_btn"));
|
||||
if (ImGui::ImageButton(icons::STOP, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
|
||||
sigpath::sourceManager.stop();
|
||||
playing = false;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
else { // TODO: Might need to check if there even is a device
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_play_btn"));
|
||||
if (ImGui::ImageButton(icons::PLAY, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
|
||||
sigpath::sourceManager.start();
|
||||
// TODO: tune in module instead
|
||||
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
|
||||
playing = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
//ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
|
||||
sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248, 30, 5, true);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::freqSelect.draw();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9);
|
||||
if (centerTuning) {
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_ena_st_btn"));
|
||||
if (ImGui::ImageButton(icons::CENTER_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
|
||||
centerTuning = false;
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["centerTuning"] = centerTuning;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
else { // TODO: Might need to check if there even is a device
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_dis_st_btn"));
|
||||
if (ImGui::ImageButton(icons::NORMAL_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
|
||||
centerTuning = true;
|
||||
setVFO(gui::freqSelect.frequency);
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["centerTuning"] = centerTuning;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Logo button
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48);
|
||||
ImGui::SetCursorPosY(10);
|
||||
if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) {
|
||||
showCredits = true;
|
||||
}
|
||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
showCredits = false;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) {
|
||||
showCredits = false;
|
||||
}
|
||||
|
||||
// Handle menu resize
|
||||
float curY = ImGui::GetCursorPosY();
|
||||
ImVec2 winSize = ImGui::GetWindowSize();
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
if (grabbingMenu) {
|
||||
newWidth = mousePos.x;
|
||||
newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250);
|
||||
ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive));
|
||||
}
|
||||
if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
|
||||
if (click) {
|
||||
grabbingMenu = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
|
||||
}
|
||||
if(!down && grabbingMenu) {
|
||||
grabbingMenu = false;
|
||||
menuWidth = newWidth;
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["menuWidth"] = menuWidth;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
// Left Column
|
||||
|
||||
if (showMenu) {
|
||||
ImGui::Columns(3, "WindowColumns", false);
|
||||
ImGui::SetColumnWidth(0, menuWidth);
|
||||
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
|
||||
ImGui::SetColumnWidth(2, 60);
|
||||
ImGui::BeginChild("Left Column");
|
||||
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
gui::menu.draw();
|
||||
|
||||
if(ImGui::CollapsingHeader("Debug")) {
|
||||
ImGui::Text("Frame time: %.3f ms/frame", 1000.0 / ImGui::GetIO().Framerate);
|
||||
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
|
||||
ImGui::Text("Center Frequency: %.0f Hz", gui::waterfall.getCenterFrequency());
|
||||
ImGui::Text("Source name: %s", sourceName.c_str());
|
||||
if (ImGui::Checkbox("Test technique", &dcbias.val)) {
|
||||
//sigpath::signalPath.setDCBiasCorrection(dcbias.val);
|
||||
}
|
||||
ImGui::Checkbox("Show demo window", &demoWindow);
|
||||
ImGui::Checkbox("Experimental zoom", &experimentalZoom);
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
else {
|
||||
// When hiding the menu bar
|
||||
ImGui::Columns(3, "WindowColumns", false);
|
||||
ImGui::SetColumnWidth(0, 8);
|
||||
ImGui::SetColumnWidth(1, winSize.x - 8 - 60);
|
||||
ImGui::SetColumnWidth(2, 60);
|
||||
}
|
||||
|
||||
// Right Column
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
ImGui::NextColumn();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::BeginChild("Waterfall");
|
||||
|
||||
gui::waterfall.draw();
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::BeginChild("WaterfallControls");
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Zoom").x / 2.0));
|
||||
ImGui::Text("Zoom");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
|
||||
if (ImGui::VSliderFloat("##_7_", ImVec2(20.0, 150.0), &bw, gui::waterfall.getBandwidth(), 1000.0, "", (experimentalZoom ? 2.0 : 1.0))) {
|
||||
gui::waterfall.setViewBandwidth(bw);
|
||||
if (vfo != NULL) {
|
||||
gui::waterfall.setViewOffset(vfo->centerOffset); // center vfo on screen
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Max").x / 2.0));
|
||||
ImGui::Text("Max");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
|
||||
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -100.0, "")) {
|
||||
fftMax = std::max<float>(fftMax, fftMin + 10);
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["max"] = fftMax;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Min").x / 2.0));
|
||||
ImGui::Text("Min");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
|
||||
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -100.0, "")) {
|
||||
fftMin = std::min<float>(fftMax - 10, fftMin);
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["min"] = fftMin;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
gui::waterfall.setFFTMin(fftMin);
|
||||
gui::waterfall.setFFTMax(fftMax);
|
||||
gui::waterfall.setWaterfallMin(fftMin);
|
||||
gui::waterfall.setWaterfallMax(fftMax);
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (showCredits) {
|
||||
credits::show();
|
||||
}
|
||||
|
||||
if (demoWindow) {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void setViewBandwidthSlider(float bandwidth) {
|
||||
bw = bandwidth;
|
||||
}
|
||||
|
||||
bool sdrIsRunning() {
|
||||
return playing;
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
#pragma once
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <stdio.h>
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
|
||||
|
||||
void windowInit();
|
||||
void drawWindow();
|
||||
void setViewBandwidthSlider(float bandwidth);
|
||||
bool sdrIsRunning();
|
51
core/src/gui/menus/bandplan.cpp
Normal file
51
core/src/gui/menus/bandplan.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include <gui/menus/bandplan.h>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <gui/gui.h>
|
||||
#include <core.h>
|
||||
|
||||
namespace bandplanmenu {
|
||||
int bandplanId;
|
||||
bool bandPlanEnabled;
|
||||
|
||||
void init() {
|
||||
// todo: check if the bandplan wasn't removed
|
||||
if (bandplan::bandplanNames.size() == 0) {
|
||||
gui::waterfall.hideBandplan();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bandplan::bandplans.find(core::configManager.conf["bandPlan"]) != bandplan::bandplans.end()) {
|
||||
std::string name = core::configManager.conf["bandPlan"];
|
||||
bandplanId = std::distance(bandplan::bandplanNames.begin(), std::find(bandplan::bandplanNames.begin(),
|
||||
bandplan::bandplanNames.end(), name));
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[name];
|
||||
}
|
||||
else {
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[0]];
|
||||
}
|
||||
|
||||
bandPlanEnabled = core::configManager.conf["bandPlanEnabled"];
|
||||
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||
ImGui::PushItemWidth(menuColumnWidth);
|
||||
if (ImGui::Combo("##_4_", &bandplanId, bandplan::bandplanNameTxt.c_str())) {
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["bandPlan"] = bandplan::bandplanNames[bandplanId];
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
if (ImGui::Checkbox("Enabled", &bandPlanEnabled)) {
|
||||
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["bandPlanEnabled"] = bandPlanEnabled;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
|
||||
ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str());
|
||||
ImGui::Text("Author: %s", plan.authorName.c_str());
|
||||
}
|
||||
};
|
6
core/src/gui/menus/bandplan.h
Normal file
6
core/src/gui/menus/bandplan.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace bandplanmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
};
|
59
core/src/gui/menus/display.cpp
Normal file
59
core/src/gui/menus/display.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
#include <gui/menus/display.h>
|
||||
#include <imgui.h>
|
||||
#include <gui/gui.h>
|
||||
#include <core.h>
|
||||
#include <gui/colormaps.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
namespace displaymenu {
|
||||
bool showWaterfall;
|
||||
int colorMapId = 0;
|
||||
std::vector<std::string> colorMapNames;
|
||||
std::string colorMapNamesTxt = "";
|
||||
std::string colorMapAuthor = "";
|
||||
|
||||
void init() {
|
||||
showWaterfall = core::configManager.conf["showWaterfall"];
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
std::string colormapName = core::configManager.conf["colorMap"];
|
||||
if (colormaps::maps.find(colormapName) != colormaps::maps.end()) {
|
||||
colormaps::Map map = colormaps::maps[colormapName];
|
||||
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
|
||||
}
|
||||
|
||||
for (auto const& [name, map] : colormaps::maps) {
|
||||
colorMapNames.push_back(name);
|
||||
colorMapNamesTxt += name;
|
||||
colorMapNamesTxt += '\0';
|
||||
if (name == colormapName) {
|
||||
colorMapId = (colorMapNames.size() - 1);
|
||||
colorMapAuthor = map.author;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
if (ImGui::Checkbox("Show Waterfall", &showWaterfall)) {
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["showWaterfall"] = showWaterfall;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (colorMapNames.size() > 0) {
|
||||
ImGui::Text("Color Map");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##_sdrpp_color_map_sel", &colorMapId, colorMapNamesTxt.c_str())) {
|
||||
colormaps::Map map = colormaps::maps[colorMapNames[colorMapId]];
|
||||
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["colorMap"] = colorMapNames[colorMapId];
|
||||
core::configManager.release(true);
|
||||
colorMapAuthor = map.author;
|
||||
}
|
||||
ImGui::Text("Color map Author: %s", colorMapAuthor.c_str());
|
||||
}
|
||||
}
|
||||
}
|
6
core/src/gui/menus/display.h
Normal file
6
core/src/gui/menus/display.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace displaymenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
}
|
22
core/src/gui/menus/scripting.cpp
Normal file
22
core/src/gui/menus/scripting.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include <gui/menus/scripting.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
namespace scriptingmenu {
|
||||
void init() {
|
||||
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
for (const auto& [name, script] : core::scriptManager.scripts) {
|
||||
bool running = script->running;
|
||||
if (running) { style::beginDisabled(); }
|
||||
if (ImGui::Button(name.c_str(), ImVec2(menuWidth, 0))) {
|
||||
script->run();
|
||||
}
|
||||
if (running) { style::endDisabled(); }
|
||||
}
|
||||
}
|
||||
}
|
6
core/src/gui/menus/scripting.h
Normal file
6
core/src/gui/menus/scripting.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace scriptingmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
}
|
15
core/src/gui/menus/sink.cpp
Normal file
15
core/src/gui/menus/sink.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#include <gui/menus/sink.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
|
||||
namespace sinkmenu {
|
||||
void init() {
|
||||
core::configManager.aquire();
|
||||
sigpath::sinkManager.loadSinksFromConfig();
|
||||
core::configManager.release();
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
sigpath::sinkManager.showMenu();
|
||||
}
|
||||
};
|
6
core/src/gui/menus/sink.h
Normal file
6
core/src/gui/menus/sink.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace sinkmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
};
|
61
core/src/gui/menus/source.cpp
Normal file
61
core/src/gui/menus/source.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include <gui/menus/source.h>
|
||||
#include <imgui.h>
|
||||
#include <gui/gui.h>
|
||||
#include <core.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/style.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
|
||||
namespace sourecmenu {
|
||||
int sourceId = 0;
|
||||
double freqOffset = 0.0;
|
||||
|
||||
void init() {
|
||||
core::configManager.aquire();
|
||||
std::string name = core::configManager.conf["source"];
|
||||
auto it = std::find(sigpath::sourceManager.sourceNames.begin(), sigpath::sourceManager.sourceNames.end(), name);
|
||||
if (it != sigpath::sourceManager.sourceNames.end()) {
|
||||
sigpath::sourceManager.selectSource(name);
|
||||
sourceId = std::distance(sigpath::sourceManager.sourceNames.begin(), it);
|
||||
}
|
||||
else if (sigpath::sourceManager.sourceNames.size() > 0) {
|
||||
sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[0]);
|
||||
}
|
||||
else {
|
||||
spdlog::warn("No source available...");
|
||||
}
|
||||
sigpath::sourceManager.setTuningOffset(core::configManager.conf["offset"]);
|
||||
core::configManager.release();
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
std::string items = "";
|
||||
for (std::string name : sigpath::sourceManager.sourceNames) {
|
||||
items += name;
|
||||
items += '\0';
|
||||
}
|
||||
float itemWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (sdrIsRunning()) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(itemWidth);
|
||||
if (ImGui::Combo("##source", &sourceId, items.c_str())) {
|
||||
sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[sourceId]);
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["source"] = sigpath::sourceManager.sourceNames[sourceId];
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (sdrIsRunning()) { style::endDisabled(); }
|
||||
|
||||
sigpath::sourceManager.showSelectedMenu();
|
||||
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::CalcTextSize("Offset (Hz)").x - 10);
|
||||
if (ImGui::InputDouble("Offset (Hz)##freq_offset", &freqOffset, 1.0, 100.0)) {
|
||||
sigpath::sourceManager.setTuningOffset(freqOffset);
|
||||
core::configManager.aquire();
|
||||
core::configManager.conf["offset"] = freqOffset;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
}
|
6
core/src/gui/menus/source.h
Normal file
6
core/src/gui/menus/source.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace sourecmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
}
|
121
core/src/gui/style.cpp
Normal file
121
core/src/gui/style.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include <gui/style.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
|
||||
namespace style {
|
||||
ImFont* baseFont;
|
||||
ImFont* bigFont;
|
||||
ImFont* hugeFont;
|
||||
|
||||
bool setDefaultStyle(std::string resDir) {
|
||||
if (!std::filesystem::is_directory(resDir)) {
|
||||
spdlog::error("Inavlid resource directory: {0}", resDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui::GetStyle().WindowRounding = 0.0f;
|
||||
ImGui::GetStyle().ChildRounding = 0.0f;
|
||||
ImGui::GetStyle().FrameRounding = 0.0f;
|
||||
ImGui::GetStyle().GrabRounding = 0.0f;
|
||||
ImGui::GetStyle().PopupRounding = 0.0f;
|
||||
ImGui::GetStyle().ScrollbarRounding = 0.0f;
|
||||
|
||||
baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f);
|
||||
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f);
|
||||
hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f);
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
//ImGui::StyleColorsLight();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testtt() {
|
||||
ImGui::StyleColorsLight();
|
||||
}
|
||||
|
||||
bool setDarkStyle(std::string resDir) {
|
||||
if (!std::filesystem::is_directory(resDir)) {
|
||||
spdlog::error("Inavlid resource directory: {0}", resDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui::GetStyle().WindowRounding = 0.0f;
|
||||
ImGui::GetStyle().ChildRounding = 0.0f;
|
||||
ImGui::GetStyle().FrameRounding = 0.0f;
|
||||
ImGui::GetStyle().GrabRounding = 0.0f;
|
||||
ImGui::GetStyle().PopupRounding = 0.0f;
|
||||
ImGui::GetStyle().ScrollbarRounding = 0.0f;
|
||||
|
||||
baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f);
|
||||
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f);
|
||||
hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f);
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
|
||||
ImVec4* colors = style.Colors;
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.45f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.63f, 0.63f, 0.70f, 0.40f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.72f, 0.72f, 0.72f, 0.78f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.91f, 0.91f, 0.91f, 0.25f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.81f, 0.81f, 0.81f, 0.67f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.46f, 0.46f, 0.46f, 0.95f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.4f, 0.9f, 1.0f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.73f, 0.60f, 0.15f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.87f, 0.87f, 0.87f, 0.35f);
|
||||
colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void beginDisabled() {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.44f, 0.44f, 0.44f, 0.15f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.20f, 0.21f, 0.22f, 0.30f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 0.65f));
|
||||
}
|
||||
|
||||
void endDisabled() {
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
}
|
15
core/src/gui/style.h
Normal file
15
core/src/gui/style.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
|
||||
namespace style {
|
||||
extern ImFont* baseFont;
|
||||
extern ImFont* bigFont;
|
||||
extern ImFont* hugeFont;
|
||||
|
||||
bool setDefaultStyle(std::string resDir);
|
||||
bool setDarkStyle(std::string resDir);
|
||||
void beginDisabled();
|
||||
void endDisabled();
|
||||
void testtt();
|
||||
}
|
114
core/src/gui/widgets/bandplan.cpp
Normal file
114
core/src/gui/widgets/bandplan.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <fstream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace bandplan {
|
||||
std::map<std::string, BandPlan_t> bandplans;
|
||||
std::vector<std::string> bandplanNames;
|
||||
std::string bandplanNameTxt;
|
||||
std::map<std::string, BandPlanColor_t> colorTable;
|
||||
|
||||
void generateTxt() {
|
||||
bandplanNameTxt = "";
|
||||
for (int i = 0; i < bandplanNames.size(); i++) {
|
||||
bandplanNameTxt += bandplanNames[i];
|
||||
bandplanNameTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& j, const Band_t& b) {
|
||||
j = json{
|
||||
{"name", b.name},
|
||||
{"type", b.type},
|
||||
{"start", b.start},
|
||||
{"end", b.end},
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, Band_t& b) {
|
||||
j.at("name").get_to(b.name);
|
||||
j.at("type").get_to(b.type);
|
||||
j.at("start").get_to(b.start);
|
||||
j.at("end").get_to(b.end);
|
||||
}
|
||||
|
||||
void to_json(json& j, const BandPlan_t& b) {
|
||||
j = json{
|
||||
{"name", b.name},
|
||||
{"country_name", b.countryName},
|
||||
{"country_code", b.countryCode},
|
||||
{"author_name", b.authorName},
|
||||
{"author_url", b.authorURL},
|
||||
{"bands", b.bands}
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, BandPlan_t& b) {
|
||||
j.at("name").get_to(b.name);
|
||||
j.at("country_name").get_to(b.countryName);
|
||||
j.at("country_code").get_to(b.countryCode);
|
||||
j.at("author_name").get_to(b.authorName);
|
||||
j.at("author_url").get_to(b.authorURL);
|
||||
j.at("bands").get_to(b.bands);
|
||||
}
|
||||
|
||||
void to_json(json& j, const BandPlanColor_t& ct) {
|
||||
spdlog::error("ImGui color to JSON not implemented!!!");
|
||||
}
|
||||
|
||||
void from_json(const json& j, BandPlanColor_t& ct) {
|
||||
std::string col = j.get<std::string>();
|
||||
if (col[0] != '#' || !std::all_of(col.begin() + 1, col.end(), ::isxdigit)) {
|
||||
return;
|
||||
}
|
||||
uint8_t r, g, b, a;
|
||||
r = std::stoi(col.substr(1, 2), NULL, 16);
|
||||
g = std::stoi(col.substr(3, 2), NULL, 16);
|
||||
b = std::stoi(col.substr(5, 2), NULL, 16);
|
||||
a = std::stoi(col.substr(7, 2), NULL, 16);
|
||||
ct.colorValue = IM_COL32(r, g, b, a);
|
||||
ct.transColorValue = IM_COL32(r, g, b, 100);
|
||||
}
|
||||
|
||||
void loadBandPlan(std::string path) {
|
||||
std::ifstream file(path.c_str());
|
||||
json data;
|
||||
file >> data;
|
||||
file.close();
|
||||
|
||||
BandPlan_t plan = data.get<BandPlan_t>();
|
||||
if (bandplans.find(plan.name) != bandplans.end()) {
|
||||
spdlog::error("Duplicate band plan name ({0}), not loading.", plan.name);
|
||||
return;
|
||||
}
|
||||
bandplans[plan.name] = plan;
|
||||
bandplanNames.push_back(plan.name);
|
||||
generateTxt();
|
||||
}
|
||||
|
||||
void loadFromDir(std::string path) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::error("Band Plan directory does not exist");
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
spdlog::error("Band Plan directory isn't a directory...");
|
||||
return;
|
||||
}
|
||||
bandplans.clear();
|
||||
for (const auto & file : std::filesystem::directory_iterator(path)) {
|
||||
std::string path = file.path().generic_string();
|
||||
if (file.path().extension().generic_string() != ".json") {
|
||||
continue;
|
||||
}
|
||||
loadBandPlan(path);
|
||||
}
|
||||
}
|
||||
|
||||
void loadColorTable(json table) {
|
||||
colorTable = table.get<std::map<std::string, BandPlanColor_t>>();
|
||||
}
|
||||
};
|
47
core/src/gui/widgets/bandplan.h
Normal file
47
core/src/gui/widgets/bandplan.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <json.hpp>
|
||||
#include <imgui/imgui.h>
|
||||
#include <stdint.h>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace bandplan {
|
||||
struct Band_t {
|
||||
std::string name;
|
||||
std::string type;
|
||||
double start;
|
||||
double end;
|
||||
};
|
||||
|
||||
void to_json(json& j, const Band_t& b);
|
||||
void from_json(const json& j, Band_t& b);
|
||||
|
||||
struct BandPlan_t {
|
||||
std::string name;
|
||||
std::string countryName;
|
||||
std::string countryCode;
|
||||
std::string authorName;
|
||||
std::string authorURL;
|
||||
std::vector<Band_t> bands;
|
||||
};
|
||||
|
||||
void to_json(json& j, const BandPlan_t& b);
|
||||
void from_json(const json& j, BandPlan_t& b);
|
||||
|
||||
struct BandPlanColor_t {
|
||||
uint32_t colorValue;
|
||||
uint32_t transColorValue;
|
||||
};
|
||||
|
||||
void to_json(json& j, const BandPlanColor_t& ct);
|
||||
void from_json(const json& j, BandPlanColor_t& ct);
|
||||
|
||||
void loadBandPlan(std::string path);
|
||||
void loadFromDir(std::string path);
|
||||
void loadColorTable(json table);
|
||||
|
||||
extern std::map<std::string, BandPlan_t> bandplans;
|
||||
extern std::vector<std::string> bandplanNames;
|
||||
extern std::string bandplanNameTxt;
|
||||
extern std::map<std::string, BandPlanColor_t> colorTable;
|
||||
};
|
@ -1,4 +1,11 @@
|
||||
#include <frequency_select.h>
|
||||
#include <gui/widgets/frequency_select.h>
|
||||
#include <config.h>
|
||||
#include <gui/style.h>
|
||||
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) {
|
||||
return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y;
|
||||
@ -9,24 +16,27 @@ FrequencySelect::FrequencySelect() {
|
||||
}
|
||||
|
||||
void FrequencySelect::init() {
|
||||
font = ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 42.0f);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
digits[i] = 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencySelect::onPosChange() {
|
||||
int digitHeight = ImGui::CalcTextSize("0").y;
|
||||
ImVec2 digitSz = ImGui::CalcTextSize("0");
|
||||
ImVec2 commaSz = ImGui::CalcTextSize(".");
|
||||
int digitHeight = digitSz.y;
|
||||
int digitWidth = digitSz.x;
|
||||
int commaOffset = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
digitTopMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y);
|
||||
digitBottomMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y + (digitHeight / 2));
|
||||
digitTopMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y);
|
||||
digitBottomMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y + (digitHeight / 2));
|
||||
|
||||
digitTopMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + (digitHeight / 2));
|
||||
digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + digitHeight);
|
||||
digitTopMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + (digitHeight / 2));
|
||||
digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + digitHeight);
|
||||
|
||||
if ((i + 1) % 3 == 0 && i < 11) {
|
||||
commaOffset += 12;
|
||||
commaOffset += commaSz.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,13 +77,14 @@ void FrequencySelect::draw() {
|
||||
window = ImGui::GetCurrentWindow();
|
||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||
widgetEndPos = ImGui::GetWindowContentRegionMax();
|
||||
widgetPos.x += window->Pos.x + 255;
|
||||
ImVec2 cursorPos = ImGui::GetCursorPos();
|
||||
widgetPos.x += window->Pos.x + cursorPos.x;
|
||||
widgetPos.y += window->Pos.y - 3;
|
||||
widgetEndPos.x += window->Pos.x + 255;
|
||||
widgetEndPos.x += window->Pos.x + cursorPos.x;
|
||||
widgetEndPos.y += window->Pos.y - 3;
|
||||
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
|
||||
|
||||
ImGui::PushFont(font);
|
||||
ImGui::PushFont(style::bigFont);
|
||||
|
||||
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
|
||||
lastWidgetPos = widgetPos;
|
||||
@ -84,19 +95,29 @@ void FrequencySelect::draw() {
|
||||
onResize();
|
||||
}
|
||||
|
||||
ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f);
|
||||
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
|
||||
ImVec2 digitSz = ImGui::CalcTextSize("0");
|
||||
ImVec2 commaSz = ImGui::CalcTextSize(".");
|
||||
int digitHeight = digitSz.y;
|
||||
int digitWidth = digitSz.x;
|
||||
int commaOffset = 0;
|
||||
bool zeros = true;
|
||||
|
||||
ImGui::ItemSize(ImRect(digitTopMins[0], ImVec2(digitBottomMaxs[11].x + 15, digitBottomMaxs[11].y)));
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (digits[i] != 0) {
|
||||
zeros = false;
|
||||
}
|
||||
sprintf(buf, "%d", digits[i]);
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y),
|
||||
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), buf);
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y),
|
||||
zeros ? disabledColor : textColor, buf);
|
||||
if ((i + 1) % 3 == 0 && i < 11) {
|
||||
commaOffset += 12;
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset + 10, widgetPos.y),
|
||||
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), ".");
|
||||
commaOffset += commaSz.x;
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + 11, widgetPos.y),
|
||||
zeros ? disabledColor : textColor, ".");
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,19 +159,22 @@ void FrequencySelect::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
long freq = 0;
|
||||
uint64_t freq = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
freq += digits[i] * pow(10, 11 - i);
|
||||
}
|
||||
frequency = freq;
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::SetCursorPosX(digitBottomMaxs[11].x + 17);
|
||||
|
||||
//ImGui::NewLine();
|
||||
}
|
||||
|
||||
void FrequencySelect::setFrequency(long freq) {
|
||||
void FrequencySelect::setFrequency(uint64_t freq) {
|
||||
int i = 11;
|
||||
for (long f = freq; i >= 0; i--) {
|
||||
for (uint64_t f = freq; i >= 0; i--) {
|
||||
digits[i] = f % 10;
|
||||
f -= digits[i];
|
||||
f /= 10;
|
@ -1,15 +1,16 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <stdint.h>
|
||||
|
||||
class FrequencySelect {
|
||||
public:
|
||||
FrequencySelect();
|
||||
void init();
|
||||
void draw();
|
||||
void setFrequency(long freq);
|
||||
void setFrequency(uint64_t freq);
|
||||
|
||||
long frequency;
|
||||
uint64_t frequency;
|
||||
bool frequencyChanged = false;
|
||||
|
||||
private:
|
||||
@ -26,7 +27,6 @@ private:
|
||||
ImVec2 lastWidgetSize;
|
||||
|
||||
ImGuiWindow* window;
|
||||
ImFont* font;
|
||||
|
||||
int digits[12];
|
||||
ImVec2 digitBottomMins[12];
|
77
core/src/gui/widgets/menu.cpp
Normal file
77
core/src/gui/widgets/menu.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include <gui/widgets/menu.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
Menu::Menu() {
|
||||
|
||||
}
|
||||
|
||||
void Menu::registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx, ModuleManager::Instance* inst) {
|
||||
MenuItem_t item;
|
||||
item.drawHandler = drawHandler;
|
||||
item.ctx = ctx;
|
||||
item.inst = inst;
|
||||
items[name] = item;
|
||||
if (!isInOrderList(name)) {
|
||||
order.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::removeEntry(std::string name) {
|
||||
items.erase(name);
|
||||
}
|
||||
|
||||
void Menu::draw() {
|
||||
MenuItem_t item;
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
for (std::string name : order) {
|
||||
if (items.find(name) == items.end()) {
|
||||
continue;
|
||||
}
|
||||
item = items[name];
|
||||
|
||||
ImRect orginalRect = window->WorkRect;
|
||||
if (item.inst != NULL) {
|
||||
|
||||
window->WorkRect = ImRect(orginalRect.Min, ImVec2(orginalRect.Max.x - ImGui::GetTextLineHeight() - 6, orginalRect.Max.y));
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (item.inst != NULL) {
|
||||
window->WorkRect = orginalRect;
|
||||
ImVec2 pos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
|
||||
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
|
||||
bool enabled = item.inst->isEnabled();
|
||||
if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) {
|
||||
enabled ? item.inst->enable() : item.inst->disable();
|
||||
}
|
||||
ImGui::SetCursorPos(pos);
|
||||
}
|
||||
|
||||
item.drawHandler(item.ctx);
|
||||
ImGui::Spacing();
|
||||
}
|
||||
else if (item.inst != NULL) {
|
||||
window->WorkRect = orginalRect;
|
||||
ImVec2 pos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
|
||||
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
|
||||
bool enabled = item.inst->isEnabled();
|
||||
if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) {
|
||||
enabled ? item.inst->enable() : item.inst->disable();
|
||||
}
|
||||
ImGui::SetCursorPos(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::isInOrderList(std::string name) {
|
||||
for (std::string _name : order) {
|
||||
if (_name == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
27
core/src/gui/widgets/menu.h
Normal file
27
core/src/gui/widgets/menu.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <module.h>
|
||||
|
||||
class Menu {
|
||||
public:
|
||||
Menu();
|
||||
|
||||
struct MenuItem_t {
|
||||
void (*drawHandler)(void* ctx);
|
||||
void* ctx;
|
||||
ModuleManager::Instance* inst;
|
||||
};
|
||||
|
||||
void registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx = NULL, ModuleManager::Instance* inst = NULL);
|
||||
void removeEntry(std::string name);
|
||||
void draw();
|
||||
|
||||
std::vector<std::string> order;
|
||||
|
||||
private:
|
||||
bool isInOrderList(std::string name);
|
||||
|
||||
std::map<std::string, MenuItem_t> items;
|
||||
};
|
23
core/src/gui/widgets/stepped_slider.cpp
Normal file
23
core/src/gui/widgets/stepped_slider.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include <gui/widgets/stepped_slider.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace ImGui {
|
||||
bool SliderFloatWithSteps(const char* label, float* v, float v_min, float v_max, float v_step, const char* display_format) {
|
||||
if (!display_format) {
|
||||
display_format = "%.3f";
|
||||
}
|
||||
|
||||
char text_buf[64] = {};
|
||||
ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), display_format, *v);
|
||||
|
||||
// Map from [v_min,v_max] to [0,N]
|
||||
const int countValues = int((v_max-v_min)/v_step);
|
||||
int v_i = int((*v - v_min)/v_step);
|
||||
const bool value_changed = ImGui::SliderInt(label, &v_i, 0, countValues, text_buf);
|
||||
|
||||
// Remap from [0,N] to [v_min,v_max]
|
||||
*v = v_min + float(v_i) * v_step;
|
||||
return value_changed;
|
||||
}
|
||||
}
|
5
core/src/gui/widgets/stepped_slider.h
Normal file
5
core/src/gui/widgets/stepped_slider.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace ImGui {
|
||||
bool SliderFloatWithSteps(const char* label, float* v, float v_min, float v_max, float v_step, const char* display_format = "%.3f");
|
||||
}
|
53
core/src/gui/widgets/volume_meter.cpp
Normal file
53
core/src/gui/widgets/volume_meter.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <gui/widgets/volume_meter.h>
|
||||
#include <algorithm>
|
||||
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
namespace ImGui {
|
||||
void VolumeMeter(float avg, float peak, float val_min, float val_max, const ImVec2& size_arg) {
|
||||
ImGuiWindow* window = GetCurrentWindow();
|
||||
ImGuiStyle& style = GImGui->Style;
|
||||
|
||||
avg = std::clamp<float>(avg, val_min, val_max);
|
||||
peak = std::clamp<float>(peak, val_min, val_max);
|
||||
|
||||
float pad = style.FramePadding.y;
|
||||
|
||||
ImVec2 min = window->DC.CursorPos;
|
||||
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (GImGui->FontSize / 2) + style.FramePadding.y);
|
||||
ImRect bb(min, min + size);
|
||||
|
||||
float lineHeight = size.y;
|
||||
|
||||
ItemSize(size, style.FramePadding.y);
|
||||
if (!ItemAdd(bb, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float zeroDb = roundf(((-val_min) / (val_max - val_min)) * size.x);
|
||||
|
||||
window->DrawList->AddRectFilled(min, min + ImVec2(zeroDb, lineHeight), IM_COL32( 0, 255, 0, 127 ));
|
||||
window->DrawList->AddRectFilled(min + ImVec2(zeroDb, 0), min + ImVec2(size.x, lineHeight), IM_COL32( 255, 0, 0, 127 ));
|
||||
|
||||
float end = roundf(((avg - val_min) / (val_max - val_min)) * size.x);
|
||||
float endP = roundf(((peak - val_min) / (val_max - val_min)) * size.x);
|
||||
|
||||
if (avg <= 0) {
|
||||
window->DrawList->AddRectFilled(min, min + ImVec2(end, lineHeight), IM_COL32( 0, 255, 0, 255 ));
|
||||
}
|
||||
else {
|
||||
window->DrawList->AddRectFilled(min, min + ImVec2(zeroDb, lineHeight), IM_COL32( 0, 255, 0, 255 ));
|
||||
window->DrawList->AddRectFilled(min + ImVec2(zeroDb, 0), min + ImVec2(end, lineHeight), IM_COL32( 255, 0, 0, 255 ));
|
||||
}
|
||||
|
||||
if (peak <= 0) {
|
||||
window->DrawList->AddLine(min + ImVec2(endP, -1), min + ImVec2(endP, lineHeight - 1), IM_COL32( 127, 255, 127, 255 ));
|
||||
}
|
||||
else {
|
||||
window->DrawList->AddLine(min + ImVec2(endP, -1), min + ImVec2(endP, lineHeight - 1), IM_COL32( 255, 127, 127, 255 ));
|
||||
}
|
||||
}
|
||||
}
|
6
core/src/gui/widgets/volume_meter.h
Normal file
6
core/src/gui/widgets/volume_meter.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
namespace ImGui {
|
||||
void VolumeMeter(float avg, float peak, float val_min, float val_max, const ImVec2& size_arg = ImVec2(0, 0));
|
||||
}
|
919
core/src/gui/widgets/waterfall.cpp
Normal file
919
core/src/gui/widgets/waterfall.cpp
Normal file
@ -0,0 +1,919 @@
|
||||
#include <gui/widgets/waterfall.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <GL/glew.h>
|
||||
#include <imutils.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
float DEFAULT_COLOR_MAP[][3] = {
|
||||
{0x00, 0x00, 0x20},
|
||||
{0x00, 0x00, 0x30},
|
||||
{0x00, 0x00, 0x50},
|
||||
{0x00, 0x00, 0x91},
|
||||
{0x1E, 0x90, 0xFF},
|
||||
{0xFF, 0xFF, 0xFF},
|
||||
{0xFF, 0xFF, 0x00},
|
||||
{0xFE, 0x6D, 0x16},
|
||||
{0xFF, 0x00, 0x00},
|
||||
{0xC6, 0x00, 0x00},
|
||||
{0x9F, 0x00, 0x00},
|
||||
{0x75, 0x00, 0x00},
|
||||
{0x4A, 0x00, 0x00}
|
||||
};
|
||||
|
||||
void doZoom(int offset, int width, int outWidth, float* data, float* out) {
|
||||
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
if (width > 65535) {
|
||||
width = 65535;
|
||||
}
|
||||
|
||||
float factor = (float)width / (float)outWidth;
|
||||
for (int i = 0; i < outWidth; i++) {
|
||||
out[i] = data[(int)(offset + ((float)i * factor))];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix this hacky BS
|
||||
|
||||
double freq_ranges[] = {
|
||||
1.0, 2.0, 2.5, 5.0,
|
||||
10.0, 20.0, 25.0, 50.0,
|
||||
100.0, 200.0, 250.0, 500.0,
|
||||
1000.0, 2000.0, 2500.0, 5000.0,
|
||||
10000.0, 20000.0, 25000.0, 50000.0,
|
||||
100000.0, 200000.0, 250000.0, 500000.0,
|
||||
1000000.0, 2000000.0, 2500000.0, 5000000.0,
|
||||
10000000.0, 20000000.0, 25000000.0, 50000000.0
|
||||
};
|
||||
|
||||
double findBestRange(double bandwidth, int maxSteps) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (bandwidth / freq_ranges[i] < (double)maxSteps) {
|
||||
return freq_ranges[i];
|
||||
}
|
||||
}
|
||||
return 50000000.0;
|
||||
}
|
||||
|
||||
void printAndScale(double freq, char* buf) {
|
||||
if (freq < 1000) {
|
||||
sprintf(buf, "%.3lf", freq);
|
||||
}
|
||||
else if (freq < 1000000) {
|
||||
sprintf(buf, "%.3lfK", freq / 1000.0);
|
||||
}
|
||||
else if (freq < 1000000000) {
|
||||
sprintf(buf, "%.3lfM", freq / 1000000.0);
|
||||
}
|
||||
else if (freq < 1000000000000) {
|
||||
sprintf(buf, "%.3lfG", freq / 1000000000.0);
|
||||
}
|
||||
for (int i = strlen(buf) - 2; i >= 0; i--) {
|
||||
if (buf[i] != '0') {
|
||||
if (buf[i] == '.') {
|
||||
i--;
|
||||
}
|
||||
char scale = buf[strlen(buf) - 1];
|
||||
buf[i + 1] = scale;
|
||||
buf[i + 2] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ImGui {
|
||||
WaterFall::WaterFall() {
|
||||
fftMin = -70.0;
|
||||
fftMax = 0.0;
|
||||
waterfallMin = -70.0;
|
||||
waterfallMax = 0.0;
|
||||
FFTAreaHeight = 300;
|
||||
newFFTAreaHeight = FFTAreaHeight;
|
||||
fftHeight = FFTAreaHeight - 50;
|
||||
dataWidth = 600;
|
||||
lastWidgetPos.x = 0;
|
||||
lastWidgetPos.y = 0;
|
||||
lastWidgetSize.x = 0;
|
||||
lastWidgetSize.y = 0;
|
||||
latestFFT = new float[1];
|
||||
waterfallFb = new uint32_t[1];
|
||||
|
||||
viewBandwidth = 1.0;
|
||||
wholeBandwidth = 1.0;
|
||||
|
||||
updatePallette(DEFAULT_COLOR_MAP, 13);
|
||||
}
|
||||
|
||||
void WaterFall::init() {
|
||||
glGenTextures(1, &textureId);
|
||||
}
|
||||
|
||||
void WaterFall::drawFFT() {
|
||||
// Calculate scaling factor
|
||||
float startLine = floorf(fftMax / vRange) * vRange;
|
||||
float vertRange = fftMax - fftMin;
|
||||
float scaleFactor = fftHeight / vertRange;
|
||||
char buf[100];
|
||||
|
||||
ImU32 trace = ImGui::GetColorU32(ImGuiCol_PlotLines);
|
||||
ImU32 shadow = ImGui::GetColorU32(ImGuiCol_PlotLines, 0.2);
|
||||
|
||||
// Vertical scale
|
||||
for (float line = startLine; line > fftMin; line -= vRange) {
|
||||
float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
|
||||
window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)),
|
||||
ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)),
|
||||
IM_COL32(50, 50, 50, 255), 1.0);
|
||||
sprintf(buf, "%d", (int)line);
|
||||
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2.0))), IM_COL32( 255, 255, 255, 255 ), buf);
|
||||
}
|
||||
|
||||
// Horizontal scale
|
||||
double startFreq = ceilf(lowerFreq / range) * range;
|
||||
double horizScale = (double)dataWidth / viewBandwidth;
|
||||
for (double freq = startFreq; freq < upperFreq; freq += range) {
|
||||
double xPos = widgetPos.x + 50 + ((freq - lowerFreq) * horizScale);
|
||||
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + 10),
|
||||
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
|
||||
IM_COL32(50, 50, 50, 255), 1.0);
|
||||
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
|
||||
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17),
|
||||
IM_COL32(255, 255, 255, 255), 1.0);
|
||||
printAndScale(freq, buf);
|
||||
ImVec2 txtSz = ImGui::CalcTextSize(buf);
|
||||
window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), widgetPos.y + fftHeight + 10 + txtSz.y), IM_COL32( 255, 255, 255, 255 ), buf);
|
||||
}
|
||||
|
||||
// Data
|
||||
if (latestFFT != NULL && fftLines != 0) {
|
||||
for (int i = 1; i < dataWidth; i++) {
|
||||
double aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor);
|
||||
double bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor);
|
||||
aPos = std::clamp<double>(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
|
||||
bPos = std::clamp<double>(bPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 49 + i, roundf(aPos)),
|
||||
ImVec2(widgetPos.x + 50 + i, roundf(bPos)), trace, 1.0);
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)),
|
||||
ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10), shadow, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// X Axis
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),
|
||||
ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
|
||||
IM_COL32(255, 255, 255, 255), 1.0);
|
||||
// Y Axis
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9),
|
||||
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9),
|
||||
IM_COL32(255, 255, 255, 255), 1.0);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void WaterFall::drawWaterfall() {
|
||||
if (waterfallUpdate) {
|
||||
waterfallUpdate = false;
|
||||
updateWaterfallTexture();
|
||||
}
|
||||
window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 51),
|
||||
ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight));
|
||||
}
|
||||
|
||||
void WaterFall::drawVFOs() {
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
if (vfo->redrawRequired) {
|
||||
vfo->redrawRequired = false;
|
||||
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
|
||||
}
|
||||
vfo->draw(window, name == selectedVFO);
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::selectFirstVFO() {
|
||||
bool available = false;
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
available = true;
|
||||
selectedVFO = name;
|
||||
return;
|
||||
}
|
||||
if (!available) {
|
||||
selectedVFO = "";
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::processInputs() {
|
||||
WaterfallVFO* vfo = NULL;
|
||||
if (selectedVFO != "") {
|
||||
vfo = vfos[selectedVFO];
|
||||
}
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
|
||||
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
|
||||
|
||||
bool mouseHovered, mouseHeld;
|
||||
bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, fftAreaMax), GetID("WaterfallID"), &mouseHovered, &mouseHeld,
|
||||
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick);
|
||||
|
||||
bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsWindowFocused();
|
||||
bool mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax);
|
||||
bool mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax);
|
||||
|
||||
|
||||
// If mouse was clicked on a VFO, select VFO and return
|
||||
// If mouse was clicked but not on a VFO, move selected VFO to position
|
||||
if (mouseClicked) {
|
||||
for (auto const& [name, _vfo] : vfos) {
|
||||
if (name == selectedVFO) {
|
||||
continue;
|
||||
}
|
||||
if (IS_IN_AREA(mousePos, _vfo->rectMin, _vfo->rectMax)) {
|
||||
selectedVFO = name;
|
||||
selectedVFOChanged = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (vfo != NULL) {
|
||||
int refCenter = mousePos.x - (widgetPos.x + 50);
|
||||
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) {
|
||||
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
|
||||
off += centerFreq;
|
||||
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
|
||||
vfo->setOffset(off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draging VFO
|
||||
if (draging && mouseInFFT) {
|
||||
int refCenter = mousePos.x - (widgetPos.x + 50);
|
||||
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y) && vfo != NULL) {
|
||||
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
|
||||
off += centerFreq;
|
||||
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
|
||||
vfo->setOffset(off);
|
||||
}
|
||||
}
|
||||
|
||||
// Draging frequency scale
|
||||
if (draging && mouseInFreq) {
|
||||
double deltax = drag.x - lastDrag;
|
||||
lastDrag = drag.x;
|
||||
double viewDelta = deltax * (viewBandwidth / (double)dataWidth);
|
||||
|
||||
viewOffset -= viewDelta;
|
||||
|
||||
if (viewOffset + (viewBandwidth / 2.0) > wholeBandwidth / 2.0) {
|
||||
double freqOffset = (viewOffset + (viewBandwidth / 2.0)) - (wholeBandwidth / 2.0);
|
||||
viewOffset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
|
||||
centerFreq += freqOffset;
|
||||
centerFreqMoved = true;
|
||||
}
|
||||
if (viewOffset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
|
||||
double freqOffset = (viewOffset - (viewBandwidth / 2.0)) + (wholeBandwidth / 2.0);
|
||||
viewOffset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
|
||||
centerFreq += freqOffset;
|
||||
centerFreqMoved = true;
|
||||
}
|
||||
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
|
||||
if (viewBandwidth != wholeBandwidth) {
|
||||
updateAllVFOs();
|
||||
updateWaterfallFb();
|
||||
}
|
||||
}
|
||||
else {
|
||||
lastDrag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::updateWaterfallFb() {
|
||||
if (!waterfallVisible || rawFFTs == NULL) {
|
||||
return;
|
||||
}
|
||||
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
|
||||
int drawDataSize;
|
||||
int drawDataStart;
|
||||
// TODO: Maybe put on the stack for faster alloc?
|
||||
float* tempData = new float[dataWidth];
|
||||
float pixel;
|
||||
float dataRange = waterfallMax - waterfallMin;
|
||||
int count = std::min<float>(waterfallHeight, fftLines);
|
||||
if (rawFFTs != NULL) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
|
||||
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
|
||||
for (int j = 0; j < dataWidth; j++) {
|
||||
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
|
||||
}
|
||||
}
|
||||
}
|
||||
delete[] tempData;
|
||||
waterfallUpdate = true;
|
||||
}
|
||||
|
||||
void WaterFall::drawBandPlan() {
|
||||
int count = bandplan->bands.size();
|
||||
double horizScale = (double)dataWidth / viewBandwidth;
|
||||
double start, end, center, aPos, bPos, cPos, width;
|
||||
ImVec2 txtSz;
|
||||
bool startVis, endVis;
|
||||
uint32_t color, colorTrans;
|
||||
for (int i = 0; i < count; i++) {
|
||||
start = bandplan->bands[i].start;
|
||||
end = bandplan->bands[i].end;
|
||||
if (start < lowerFreq && end < lowerFreq) {
|
||||
continue;
|
||||
}
|
||||
if (start > upperFreq && end > upperFreq) {
|
||||
continue;
|
||||
}
|
||||
startVis = (start > lowerFreq);
|
||||
endVis = (end < upperFreq);
|
||||
start = std::clamp<double>(start, lowerFreq, upperFreq);
|
||||
end = std::clamp<double>(end, lowerFreq, upperFreq);
|
||||
center = (start + end) / 2.0;
|
||||
aPos = widgetPos.x + 50 + ((start - lowerFreq) * horizScale);
|
||||
bPos = widgetPos.x + 50 + ((end - lowerFreq) * horizScale);
|
||||
cPos = widgetPos.x + 50 + ((center - lowerFreq) * horizScale);
|
||||
width = bPos - aPos;
|
||||
txtSz = ImGui::CalcTextSize(bandplan->bands[i].name.c_str());
|
||||
float height = txtSz.y * 2.5f;
|
||||
if (bandplan::colorTable.find(bandplan->bands[i].type.c_str()) != bandplan::colorTable.end()) {
|
||||
color = bandplan::colorTable[bandplan->bands[i].type].colorValue;
|
||||
colorTrans = bandplan::colorTable[bandplan->bands[i].type].transColorValue;
|
||||
}
|
||||
else {
|
||||
color = IM_COL32(255, 255, 255, 255);
|
||||
colorTrans = IM_COL32(255, 255, 255, 100);
|
||||
}
|
||||
if (aPos <= widgetPos.x + 50) {
|
||||
aPos = widgetPos.x + 51;
|
||||
}
|
||||
if (bPos <= widgetPos.x + 50) {
|
||||
bPos = widgetPos.x + 51;
|
||||
}
|
||||
if (width >= 1.0) {
|
||||
window->DrawList->AddRectFilled(ImVec2(roundf(aPos), widgetPos.y + fftHeight + 10 - height),
|
||||
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10), colorTrans);
|
||||
if (startVis) {
|
||||
window->DrawList->AddLine(ImVec2(roundf(aPos), widgetPos.y + fftHeight + 10 - height - 1),
|
||||
ImVec2(roundf(aPos), widgetPos.y + fftHeight + 9), color);
|
||||
}
|
||||
if (endVis) {
|
||||
window->DrawList->AddLine(ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10 - height - 1),
|
||||
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 9), color);
|
||||
}
|
||||
}
|
||||
if (txtSz.x <= width) {
|
||||
window->DrawList->AddText(ImVec2(cPos - (txtSz.x / 2.0), widgetPos.y + fftHeight + 10 - (height / 2.0f) - (txtSz.y / 2.0f)),
|
||||
IM_COL32(255, 255, 255, 255), bandplan->bands[i].name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::updateWaterfallTexture() {
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dataWidth, waterfallHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)waterfallFb);
|
||||
}
|
||||
|
||||
void WaterFall::onPositionChange() {
|
||||
// Nothing to see here...
|
||||
}
|
||||
|
||||
void WaterFall::onResize() {
|
||||
// return if widget is too small
|
||||
if (widgetSize.x < 100 || widgetSize.y < 100) {
|
||||
return;
|
||||
}
|
||||
|
||||
int lastWaterfallHeight = waterfallHeight;
|
||||
|
||||
if (waterfallVisible) {
|
||||
FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - 50);
|
||||
fftHeight = FFTAreaHeight - 50;
|
||||
waterfallHeight = widgetSize.y - fftHeight - 52;
|
||||
}
|
||||
else {
|
||||
fftHeight = widgetSize.y - 50;
|
||||
}
|
||||
dataWidth = widgetSize.x - 60.0;
|
||||
|
||||
if (waterfallVisible) {
|
||||
// Raw FFT resize
|
||||
fftLines = std::min<int>(fftLines, waterfallHeight) - 1;
|
||||
if (rawFFTs != NULL) {
|
||||
if (currentFFTLine != 0) {
|
||||
float* tempWF = new float[currentFFTLine * rawFFTSize];
|
||||
int moveCount = lastWaterfallHeight - currentFFTLine;
|
||||
memcpy(tempWF, rawFFTs, currentFFTLine * rawFFTSize * sizeof(float));
|
||||
memmove(rawFFTs, &rawFFTs[currentFFTLine * rawFFTSize], moveCount * rawFFTSize * sizeof(float));
|
||||
memcpy(&rawFFTs[moveCount * rawFFTSize], tempWF, currentFFTLine * rawFFTSize * sizeof(float));
|
||||
delete[] tempWF;
|
||||
}
|
||||
currentFFTLine = 0;
|
||||
rawFFTs = (float*)realloc(rawFFTs, waterfallHeight * rawFFTSize * sizeof(float));
|
||||
}
|
||||
else {
|
||||
rawFFTs = (float*)malloc(waterfallHeight * rawFFTSize * sizeof(float));
|
||||
}
|
||||
// ==============
|
||||
}
|
||||
|
||||
if (latestFFT != NULL) {
|
||||
delete[] latestFFT;
|
||||
}
|
||||
latestFFT = new float[dataWidth];
|
||||
|
||||
if (waterfallVisible) {
|
||||
delete[] waterfallFb;
|
||||
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
|
||||
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
|
||||
}
|
||||
for (int i = 0; i < dataWidth; i++) {
|
||||
latestFFT[i] = -1000.0; // Hide everything
|
||||
}
|
||||
|
||||
fftAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + 9);
|
||||
fftAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10);
|
||||
freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11);
|
||||
freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50);
|
||||
|
||||
maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10);
|
||||
maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y);
|
||||
|
||||
range = findBestRange(viewBandwidth, maxHSteps);
|
||||
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
||||
vRange = 10.0;
|
||||
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
void WaterFall::draw() {
|
||||
buf_mtx.lock();
|
||||
window = GetCurrentWindow();
|
||||
|
||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||
widgetEndPos = ImGui::GetWindowContentRegionMax();
|
||||
widgetPos.x += window->Pos.x;
|
||||
widgetPos.y += window->Pos.y;
|
||||
widgetEndPos.x += window->Pos.x - 4; // Padding
|
||||
widgetEndPos.y += window->Pos.y;
|
||||
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
|
||||
|
||||
if (selectedVFO == "" && vfos.size() > 0) {
|
||||
selectFirstVFO();
|
||||
}
|
||||
|
||||
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
|
||||
lastWidgetPos = widgetPos;
|
||||
onPositionChange();
|
||||
}
|
||||
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
|
||||
lastWidgetSize = widgetSize;
|
||||
onResize();
|
||||
}
|
||||
|
||||
window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 ));
|
||||
window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 ));
|
||||
window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0);
|
||||
|
||||
processInputs();
|
||||
|
||||
drawFFT();
|
||||
if (waterfallVisible) {
|
||||
drawWaterfall();
|
||||
}
|
||||
drawVFOs();
|
||||
if (bandplan != NULL && bandplanVisible) {
|
||||
drawBandPlan();
|
||||
}
|
||||
|
||||
if (!waterfallVisible) {
|
||||
buf_mtx.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle fft resize
|
||||
ImVec2 winSize = ImGui::GetWindowSize();
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
mousePos.x -= widgetPos.x;
|
||||
mousePos.y -= widgetPos.y;
|
||||
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
if (draggingFW) {
|
||||
newFFTAreaHeight = mousePos.y;
|
||||
newFFTAreaHeight = std::clamp<float>(newFFTAreaHeight, 150, widgetSize.y - 50);
|
||||
ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y),
|
||||
ImGui::GetColorU32(ImGuiCol_SeparatorActive));
|
||||
}
|
||||
if (mousePos.y >= newFFTAreaHeight - 2 && mousePos.y <= newFFTAreaHeight + 2 && mousePos.x > 0 && mousePos.x < widgetSize.x) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
|
||||
if (click) {
|
||||
draggingFW = true;
|
||||
}
|
||||
}
|
||||
if(!down && draggingFW) {
|
||||
draggingFW = false;
|
||||
FFTAreaHeight = newFFTAreaHeight;
|
||||
onResize();
|
||||
}
|
||||
|
||||
buf_mtx.unlock();
|
||||
}
|
||||
|
||||
float* WaterFall::getFFTBuffer() {
|
||||
if (rawFFTs == NULL) { return NULL; }
|
||||
buf_mtx.lock();
|
||||
if (waterfallVisible) {
|
||||
currentFFTLine--;
|
||||
fftLines++;
|
||||
currentFFTLine = ((currentFFTLine + waterfallHeight) % waterfallHeight);
|
||||
fftLines = std::min<float>(fftLines, waterfallHeight);
|
||||
return &rawFFTs[currentFFTLine * rawFFTSize];
|
||||
}
|
||||
return rawFFTs;
|
||||
}
|
||||
|
||||
void WaterFall::pushFFT() {
|
||||
if (rawFFTs == NULL) { return; }
|
||||
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
|
||||
int drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
|
||||
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||
|
||||
|
||||
|
||||
if (waterfallVisible) {
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
|
||||
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
|
||||
float pixel;
|
||||
float dataRange = waterfallMax - waterfallMin;
|
||||
for (int j = 0; j < dataWidth; j++) {
|
||||
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
|
||||
waterfallFb[j] = waterfallPallet[id];
|
||||
}
|
||||
waterfallUpdate = true;
|
||||
}
|
||||
else {
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
|
||||
fftLines = 1;
|
||||
}
|
||||
|
||||
buf_mtx.unlock();
|
||||
}
|
||||
|
||||
void WaterFall::updatePallette(float colors[][3], int colorCount) {
|
||||
std::lock_guard<std::mutex> lck(buf_mtx);
|
||||
for (int i = 0; i < WATERFALL_RESOLUTION; i++) {
|
||||
int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
|
||||
int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
|
||||
lowerId = std::clamp<int>(lowerId, 0, colorCount - 1);
|
||||
upperId = std::clamp<int>(upperId, 0, colorCount - 1);
|
||||
float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId;
|
||||
float r = (colors[lowerId][0] * (1.0 - ratio)) + (colors[upperId][0] * (ratio));
|
||||
float g = (colors[lowerId][1] * (1.0 - ratio)) + (colors[upperId][1] * (ratio));
|
||||
float b = (colors[lowerId][2] * (1.0 - ratio)) + (colors[upperId][2] * (ratio));
|
||||
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
|
||||
}
|
||||
updateWaterfallFb();
|
||||
}
|
||||
|
||||
void WaterFall::updatePalletteFromArray(float* colors, int colorCount) {
|
||||
std::lock_guard<std::mutex> lck(buf_mtx);
|
||||
for (int i = 0; i < WATERFALL_RESOLUTION; i++) {
|
||||
int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
|
||||
int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
|
||||
lowerId = std::clamp<int>(lowerId, 0, colorCount - 1);
|
||||
upperId = std::clamp<int>(upperId, 0, colorCount - 1);
|
||||
float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId;
|
||||
float r = (colors[(lowerId * 3) + 0] * (1.0 - ratio)) + (colors[(upperId * 3) + 0] * (ratio));
|
||||
float g = (colors[(lowerId * 3) + 1] * (1.0 - ratio)) + (colors[(upperId * 3) + 1] * (ratio));
|
||||
float b = (colors[(lowerId * 3) + 2] * (1.0 - ratio)) + (colors[(upperId * 3) + 2] * (ratio));
|
||||
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
|
||||
}
|
||||
updateWaterfallFb();
|
||||
}
|
||||
|
||||
void WaterFall::autoRange() {
|
||||
float min = INFINITY;
|
||||
float max = -INFINITY;
|
||||
for (int i = 0; i < dataWidth; i++) {
|
||||
if (latestFFT[i] < min) {
|
||||
min = latestFFT[i];
|
||||
}
|
||||
if (latestFFT[i] > max) {
|
||||
max = latestFFT[i];
|
||||
}
|
||||
}
|
||||
fftMin = min - 5;
|
||||
fftMax = max + 5;
|
||||
}
|
||||
|
||||
void WaterFall::setCenterFrequency(double freq) {
|
||||
centerFreq = freq;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double WaterFall::getCenterFrequency() {
|
||||
return centerFreq;
|
||||
}
|
||||
|
||||
void WaterFall::setBandwidth(double bandWidth) {
|
||||
double currentRatio = viewBandwidth / wholeBandwidth;
|
||||
wholeBandwidth = bandWidth;
|
||||
setViewBandwidth(bandWidth * currentRatio);
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
if (vfo->lowerOffset < -(bandWidth / 2)) {
|
||||
vfo->setCenterOffset(-(bandWidth / 2));
|
||||
}
|
||||
if (vfo->upperOffset > (bandWidth / 2)) {
|
||||
vfo->setCenterOffset(bandWidth / 2);
|
||||
}
|
||||
}
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double WaterFall::getBandwidth() {
|
||||
return wholeBandwidth;
|
||||
}
|
||||
|
||||
void WaterFall::setViewBandwidth(double bandWidth) {
|
||||
std::lock_guard<std::mutex> lck(buf_mtx);
|
||||
if (bandWidth == viewBandwidth) {
|
||||
return;
|
||||
}
|
||||
if (abs(viewOffset) + (bandWidth / 2.0) > wholeBandwidth / 2.0) {
|
||||
if (viewOffset < 0) {
|
||||
viewOffset = (bandWidth / 2.0) - (wholeBandwidth / 2.0);
|
||||
}
|
||||
else {
|
||||
viewOffset = (wholeBandwidth / 2.0) - (bandWidth / 2.0);
|
||||
}
|
||||
}
|
||||
viewBandwidth = bandWidth;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
range = findBestRange(bandWidth, maxHSteps);
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double WaterFall::getViewBandwidth() {
|
||||
return viewBandwidth;
|
||||
}
|
||||
|
||||
void WaterFall::setViewOffset(double offset) {
|
||||
std::lock_guard<std::mutex> lck(buf_mtx);
|
||||
if (offset == viewOffset) {
|
||||
return;
|
||||
}
|
||||
if (offset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
|
||||
offset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
|
||||
}
|
||||
if (offset + (viewBandwidth / 2.0) > (wholeBandwidth / 2.0)) {
|
||||
offset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
|
||||
}
|
||||
viewOffset = offset;
|
||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
|
||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
|
||||
updateWaterfallFb();
|
||||
updateAllVFOs();
|
||||
}
|
||||
|
||||
double WaterFall::getViewOffset() {
|
||||
return viewOffset;
|
||||
}
|
||||
|
||||
void WaterFall::setFFTMin(float min) {
|
||||
fftMin = min;
|
||||
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
||||
}
|
||||
|
||||
float WaterFall::getFFTMin() {
|
||||
return fftMin;
|
||||
}
|
||||
|
||||
void WaterFall::setFFTMax(float max) {
|
||||
fftMax = max;
|
||||
vRange = findBestRange(fftMax - fftMin, maxVSteps);
|
||||
}
|
||||
|
||||
float WaterFall::getFFTMax() {
|
||||
return fftMax;
|
||||
}
|
||||
|
||||
void WaterFall::setWaterfallMin(float min) {
|
||||
std::lock_guard<std::mutex> lck(buf_mtx);
|
||||
if (min == waterfallMin) {
|
||||
return;
|
||||
}
|
||||
waterfallMin = min;
|
||||
updateWaterfallFb();
|
||||
}
|
||||
|
||||
float WaterFall::getWaterfallMin() {
|
||||
return waterfallMin;
|
||||
}
|
||||
|
||||
void WaterFall::setWaterfallMax(float max) {
|
||||
std::lock_guard<std::mutex> lck(buf_mtx);
|
||||
if (max == waterfallMax) {
|
||||
return;
|
||||
}
|
||||
waterfallMax = max;
|
||||
updateWaterfallFb();
|
||||
}
|
||||
|
||||
float WaterFall::getWaterfallMax() {
|
||||
return waterfallMax;
|
||||
}
|
||||
|
||||
void WaterFall::updateAllVFOs() {
|
||||
for (auto const& [name, vfo] : vfos) {
|
||||
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::setRawFFTSize(int size, bool lock) {
|
||||
std::lock_guard<std::mutex> lck(buf_mtx);
|
||||
rawFFTSize = size;
|
||||
if (rawFFTs != NULL) {
|
||||
int wfSize = std::max<int>(1, waterfallHeight);
|
||||
rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * wfSize * sizeof(float));
|
||||
}
|
||||
else {
|
||||
int wfSize = std::max<int>(1, waterfallHeight);
|
||||
rawFFTs = (float*)malloc(rawFFTSize * wfSize * sizeof(float));
|
||||
}
|
||||
memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float));
|
||||
}
|
||||
|
||||
void WaterfallVFO::setOffset(double offset) {
|
||||
generalOffset = offset;
|
||||
if (reference == REF_CENTER) {
|
||||
centerOffset = offset;
|
||||
lowerOffset = offset - (bandwidth / 2.0);
|
||||
upperOffset = offset + (bandwidth / 2.0);
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
lowerOffset = offset;
|
||||
centerOffset = offset + (bandwidth / 2.0);
|
||||
upperOffset = offset + bandwidth;
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
upperOffset = offset;
|
||||
centerOffset = offset - (bandwidth / 2.0);
|
||||
lowerOffset = offset - bandwidth;
|
||||
}
|
||||
centerOffsetChanged = true;
|
||||
upperOffsetChanged = true;
|
||||
lowerOffsetChanged = true;
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setCenterOffset(double offset) {
|
||||
if (reference == REF_CENTER) {
|
||||
generalOffset = offset;
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
generalOffset = offset - (bandwidth / 2.0);
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
generalOffset = offset + (bandwidth / 2.0);
|
||||
}
|
||||
centerOffset = offset;
|
||||
lowerOffset = offset - (bandwidth / 2.0);
|
||||
upperOffset = offset + (bandwidth / 2.0);
|
||||
centerOffsetChanged = true;
|
||||
upperOffsetChanged = true;
|
||||
lowerOffsetChanged = true;
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setBandwidth(double bw) {
|
||||
if (bandwidth == bw || bw < 0) {
|
||||
return;
|
||||
}
|
||||
bandwidth = bw;
|
||||
if (reference == REF_CENTER) {
|
||||
lowerOffset = centerOffset - (bandwidth / 2.0);
|
||||
upperOffset = centerOffset + (bandwidth / 2.0);
|
||||
}
|
||||
else if (reference == REF_LOWER) {
|
||||
centerOffset = lowerOffset + (bandwidth / 2.0);
|
||||
upperOffset = lowerOffset + bandwidth;
|
||||
centerOffsetChanged = true;
|
||||
}
|
||||
else if (reference == REF_UPPER) {
|
||||
centerOffset = upperOffset - (bandwidth / 2.0);
|
||||
lowerOffset = upperOffset - bandwidth;
|
||||
centerOffsetChanged = true;
|
||||
}
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setReference(int ref) {
|
||||
if (reference == ref || ref < 0 || ref >= _REF_COUNT) {
|
||||
return;
|
||||
}
|
||||
reference = ref;
|
||||
setOffset(generalOffset);
|
||||
|
||||
}
|
||||
|
||||
void WaterfallVFO::updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight) {
|
||||
double width = (bandwidth / viewBandwidth) * (double)dataWidth;
|
||||
int center = roundf((((centerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
|
||||
int left = roundf((((lowerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
|
||||
int right = roundf((((upperOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
|
||||
|
||||
if (left >= 0 && left < dataWidth && reference == REF_LOWER) {
|
||||
lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9);
|
||||
lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9);
|
||||
lineVisible = true;
|
||||
}
|
||||
else if (center >= 0 && center < dataWidth && reference == REF_CENTER) {
|
||||
lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9);
|
||||
lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9);
|
||||
lineVisible = true;
|
||||
}
|
||||
else if (right >= 0 && right < dataWidth && reference == REF_UPPER) {
|
||||
lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9);
|
||||
lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9);
|
||||
lineVisible = true;
|
||||
}
|
||||
else {
|
||||
lineVisible = false;
|
||||
}
|
||||
|
||||
left = std::clamp<int>(left, 0, dataWidth - 1);
|
||||
right = std::clamp<int>(right, 0, dataWidth - 1);
|
||||
|
||||
rectMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10);
|
||||
rectMax = ImVec2(widgetPos.x + 51 + right, widgetPos.y + fftHeight + 10);
|
||||
}
|
||||
|
||||
void WaterfallVFO::draw(ImGuiWindow* window, bool selected) {
|
||||
window->DrawList->AddRectFilled(rectMin, rectMax, IM_COL32(255, 255, 255, 50));
|
||||
if (lineVisible) {
|
||||
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255));
|
||||
}
|
||||
};
|
||||
|
||||
void WaterFall::showWaterfall() {
|
||||
buf_mtx.lock();
|
||||
waterfallVisible = true;
|
||||
onResize();
|
||||
memset(rawFFTs, 0, waterfallHeight * rawFFTSize * sizeof(float));
|
||||
updateWaterfallFb();
|
||||
buf_mtx.unlock();
|
||||
}
|
||||
|
||||
void WaterFall::hideWaterfall() {
|
||||
buf_mtx.lock();
|
||||
waterfallVisible = false;
|
||||
onResize();
|
||||
buf_mtx.unlock();
|
||||
}
|
||||
|
||||
void WaterFall::setFFTHeight(int height) {
|
||||
FFTAreaHeight = height;
|
||||
newFFTAreaHeight = height;
|
||||
buf_mtx.lock();
|
||||
onResize();
|
||||
buf_mtx.unlock();
|
||||
}
|
||||
|
||||
int WaterFall::getFFTHeight() {
|
||||
return FFTAreaHeight;
|
||||
}
|
||||
|
||||
void WaterFall::showBandplan() {
|
||||
bandplanVisible = true;
|
||||
}
|
||||
|
||||
void WaterFall::hideBandplan() {
|
||||
bandplanVisible = false;
|
||||
}
|
||||
|
||||
void WaterfallVFO::setSnapInterval(double interval) {
|
||||
snapInterval = interval;
|
||||
}
|
||||
};
|
||||
|
203
core/src/gui/widgets/waterfall.h
Normal file
203
core/src/gui/widgets/waterfall.h
Normal file
@ -0,0 +1,203 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
#include <GL/glew.h>
|
||||
|
||||
#define WATERFALL_RESOLUTION 1000000
|
||||
|
||||
namespace ImGui {
|
||||
class WaterfallVFO {
|
||||
public:
|
||||
void setOffset(double offset);
|
||||
void setCenterOffset(double offset);
|
||||
void setBandwidth(double bw);
|
||||
void setReference(int ref);
|
||||
void setSnapInterval(double interval);
|
||||
void updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight); // NOTE: Datawidth double???
|
||||
void draw(ImGuiWindow* window, bool selected);
|
||||
|
||||
enum {
|
||||
REF_LOWER,
|
||||
REF_CENTER,
|
||||
REF_UPPER,
|
||||
_REF_COUNT
|
||||
};
|
||||
|
||||
double generalOffset;
|
||||
double centerOffset;
|
||||
double lowerOffset;
|
||||
double upperOffset;
|
||||
double bandwidth;
|
||||
double snapInterval = 5000;
|
||||
int reference = REF_CENTER;
|
||||
|
||||
ImVec2 rectMin;
|
||||
ImVec2 rectMax;
|
||||
ImVec2 lineMin;
|
||||
ImVec2 lineMax;
|
||||
|
||||
bool centerOffsetChanged = false;
|
||||
bool lowerOffsetChanged = false;
|
||||
bool upperOffsetChanged = false;
|
||||
bool redrawRequired = true;
|
||||
bool lineVisible = true;
|
||||
};
|
||||
|
||||
class WaterFall {
|
||||
public:
|
||||
WaterFall();
|
||||
|
||||
void init();
|
||||
|
||||
void draw();
|
||||
float* getFFTBuffer();
|
||||
void pushFFT();
|
||||
|
||||
void updatePallette(float colors[][3], int colorCount);
|
||||
void updatePalletteFromArray(float* colors, int colorCount);
|
||||
|
||||
void setCenterFrequency(double freq);
|
||||
double getCenterFrequency();
|
||||
|
||||
void setBandwidth(double bandWidth);
|
||||
double getBandwidth();
|
||||
|
||||
void setViewBandwidth(double bandWidth);
|
||||
double getViewBandwidth();
|
||||
|
||||
void setViewOffset(double offset);
|
||||
double getViewOffset();
|
||||
|
||||
void setFFTMin(float min);
|
||||
float getFFTMin();
|
||||
|
||||
void setFFTMax(float max);
|
||||
float getFFTMax();
|
||||
|
||||
void setWaterfallMin(float min);
|
||||
float getWaterfallMin();
|
||||
|
||||
void setWaterfallMax(float max);
|
||||
float getWaterfallMax();
|
||||
|
||||
void setZoom(double zoomLevel);
|
||||
void setOffset(double zoomOffset);
|
||||
|
||||
void autoRange();
|
||||
|
||||
void selectFirstVFO();
|
||||
|
||||
void showWaterfall();
|
||||
void hideWaterfall();
|
||||
|
||||
void showBandplan();
|
||||
void hideBandplan();
|
||||
|
||||
void setFFTHeight(int height);
|
||||
int getFFTHeight();
|
||||
|
||||
void setRawFFTSize(int size, bool lock = true);
|
||||
|
||||
bool centerFreqMoved = false;
|
||||
bool vfoFreqChanged = false;
|
||||
bool bandplanEnabled = false;
|
||||
bandplan::BandPlan_t* bandplan = NULL;
|
||||
|
||||
std::map<std::string, WaterfallVFO*> vfos;
|
||||
std::string selectedVFO = "";
|
||||
bool selectedVFOChanged = false;
|
||||
|
||||
enum {
|
||||
REF_LOWER,
|
||||
REF_CENTER,
|
||||
REF_UPPER,
|
||||
_REF_COUNT
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
void drawWaterfall();
|
||||
void drawFFT();
|
||||
void drawVFOs();
|
||||
void drawBandPlan();
|
||||
void processInputs();
|
||||
void onPositionChange();
|
||||
void onResize();
|
||||
void updateWaterfallFb();
|
||||
void updateWaterfallTexture();
|
||||
void updateAllVFOs();
|
||||
|
||||
bool waterfallUpdate = false;
|
||||
|
||||
uint32_t waterfallPallet[WATERFALL_RESOLUTION];
|
||||
|
||||
ImVec2 widgetPos;
|
||||
ImVec2 widgetEndPos;
|
||||
ImVec2 widgetSize;
|
||||
|
||||
ImVec2 lastWidgetPos;
|
||||
ImVec2 lastWidgetSize;
|
||||
|
||||
ImVec2 fftAreaMin;
|
||||
ImVec2 fftAreaMax;
|
||||
ImVec2 freqAreaMin;
|
||||
ImVec2 freqAreaMax;
|
||||
ImVec2 waterfallAreaMin;
|
||||
ImVec2 waterfallAreaMax;
|
||||
|
||||
ImGuiWindow* window;
|
||||
|
||||
GLuint textureId;
|
||||
|
||||
std::mutex buf_mtx;
|
||||
|
||||
float vRange;
|
||||
|
||||
int maxVSteps;
|
||||
int maxHSteps;
|
||||
|
||||
int dataWidth; // Width of the FFT and waterfall
|
||||
int fftHeight; // Height of the fft graph
|
||||
int waterfallHeight = 0; // Height of the waterfall
|
||||
|
||||
double viewBandwidth;
|
||||
double viewOffset;
|
||||
|
||||
double lowerFreq;
|
||||
double upperFreq;
|
||||
double range;
|
||||
|
||||
float lastDrag;
|
||||
|
||||
int vfoRef = REF_CENTER;
|
||||
|
||||
// Absolute values
|
||||
double centerFreq;
|
||||
double wholeBandwidth;
|
||||
|
||||
// Ranges
|
||||
float fftMin;
|
||||
float fftMax;
|
||||
float waterfallMin;
|
||||
float waterfallMax;
|
||||
|
||||
//std::vector<std::vector<float>> rawFFTs;
|
||||
int rawFFTSize;
|
||||
float* rawFFTs = NULL;
|
||||
float* latestFFT;
|
||||
int currentFFTLine = 0;
|
||||
int fftLines = 0;
|
||||
|
||||
uint32_t* waterfallFb;
|
||||
|
||||
bool draggingFW = false;
|
||||
int FFTAreaHeight;
|
||||
int newFFTAreaHeight;
|
||||
|
||||
bool waterfallVisible = true;
|
||||
bool bandplanVisible = false;
|
||||
};
|
||||
};
|
@ -2640,6 +2640,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
|
||||
|
||||
// Slider behavior
|
||||
ImRect grab_bb;
|
||||
|
||||
const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb);
|
||||
if (value_changed)
|
||||
MarkItemEdited(id);
|
2631
core/src/imgui/stb_image_resize.h
Normal file
2631
core/src/imgui/stb_image_resize.h
Normal file
File diff suppressed because it is too large
Load Diff
25347
core/src/json.hpp
Normal file
25347
core/src/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
145
core/src/module.cpp
Normal file
145
core/src/module.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#include <module.h>
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
ModuleManager::Module_t ModuleManager::loadModule(std::string path) {
|
||||
Module_t mod;
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::error("{0} does not exist", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
spdlog::error("{0} isn't a loadable module", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
mod.handle = LoadLibraryA(path.c_str());
|
||||
if (mod.handle == NULL) {
|
||||
spdlog::error("Couldn't load {0}.", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
mod.info = (ModuleInfo_t*)GetProcAddress(mod.handle, "_INFO_");
|
||||
mod.init = (void(*)())GetProcAddress(mod.handle, "_INIT_");
|
||||
mod.createInstance = (Instance*(*)(std::string))GetProcAddress(mod.handle, "_CREATE_INSTANCE_");
|
||||
mod.deleteInstance = (void(*)(Instance*))GetProcAddress(mod.handle, "_DELETE_INSTANCE_");
|
||||
mod.end = (void(*)())GetProcAddress(mod.handle, "_END_");
|
||||
#else
|
||||
mod.handle = dlopen(path.c_str(), RTLD_LAZY);
|
||||
if (mod.handle == NULL) {
|
||||
spdlog::error("Couldn't load {0}.", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
mod.info = (ModuleInfo_t*)dlsym(mod.handle, "_INFO_");
|
||||
mod.init = (void(*)())dlsym(mod.handle, "_INIT_");
|
||||
mod.createInstance = (Instance*(*)(std::string))dlsym(mod.handle, "_CREATE_INSTANCE_");
|
||||
mod.deleteInstance = (void(*)(Instance*))dlsym(mod.handle, "_DELETE_INSTANCE_");
|
||||
mod.end = (void(*)())dlsym(mod.handle, "_END_");
|
||||
#endif
|
||||
if (mod.info == NULL) {
|
||||
spdlog::error("{0} is missing _INFO_ symbol", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
if (mod.init == NULL) {
|
||||
spdlog::error("{0} is missing _INIT_ symbol", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
if (mod.createInstance == NULL) {
|
||||
spdlog::error("{0} is missing _CREATE_INSTANCE_ symbol", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
if (mod.deleteInstance == NULL) {
|
||||
spdlog::error("{0} is missing _DELETE_INSTANCE_ symbol", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
if (mod.end == NULL) {
|
||||
spdlog::error("{0} is missing _END_ symbol", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
if (modules.find(mod.info->name) != modules.end()) {
|
||||
spdlog::error("{0} has the same name as an already loaded module", path);
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
for (auto const& [name, _mod] : modules) {
|
||||
if (mod.handle == _mod.handle) {
|
||||
return _mod;
|
||||
}
|
||||
}
|
||||
mod.init();
|
||||
modules[mod.info->name] = mod;
|
||||
return mod;
|
||||
}
|
||||
|
||||
void ModuleManager::createInstance(std::string name, std::string module) {
|
||||
if (modules.find(module) == modules.end()) {
|
||||
spdlog::error("Module '{0}' doesn't exist", module);
|
||||
return;
|
||||
}
|
||||
if (instances.find(name) != instances.end()) {
|
||||
spdlog::error("A module instance with the name '{0}' already exists", name);
|
||||
return;
|
||||
}
|
||||
int maxCount = modules[module].info->maxInstances;
|
||||
if (countModuleInstances(module) >= maxCount && maxCount > 0) {
|
||||
spdlog::error("Maximum number of instances reached for '{0}'", module);
|
||||
return;
|
||||
}
|
||||
Instance_t inst;
|
||||
inst.module = modules[module];
|
||||
inst.instance = inst.module.createInstance(name);
|
||||
instances[name] = inst;
|
||||
}
|
||||
|
||||
void ModuleManager::deleteInstance(std::string name) {
|
||||
spdlog::error("DELETE INSTANCE NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
void ModuleManager::deleteInstance(ModuleManager::Instance* instance) {
|
||||
spdlog::error("DELETE INSTANCE NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
void ModuleManager::enableInstance(std::string name) {
|
||||
if (instances.find(name) == instances.end()) {
|
||||
spdlog::error("Cannot enable '{0}', instance doesn't exist", name);
|
||||
return;
|
||||
}
|
||||
instances[name].instance->enable();
|
||||
}
|
||||
|
||||
void ModuleManager::disableInstance(std::string name) {
|
||||
if (instances.find(name) == instances.end()) {
|
||||
spdlog::error("Cannot disable '{0}', instance doesn't exist", name);
|
||||
return;
|
||||
}
|
||||
instances[name].instance->disable();
|
||||
}
|
||||
|
||||
bool ModuleManager::instanceEnabled(std::string name) {
|
||||
if (instances.find(name) == instances.end()) {
|
||||
spdlog::error("Cannot check if '{0}' is enabled, instance doesn't exist", name);
|
||||
return false;
|
||||
}
|
||||
return instances[name].instance->isEnabled();
|
||||
}
|
||||
|
||||
int ModuleManager::countModuleInstances(std::string module) {
|
||||
if (modules.find(module) == modules.end()) {
|
||||
spdlog::error("Cannot count instances of '{0}', Module doesn't exist", module);
|
||||
return -1;
|
||||
}
|
||||
ModuleManager::Module_t mod = modules[module];
|
||||
int count = 0;
|
||||
for (auto const& [name, instance] : instances) {
|
||||
if (instance.module == mod) { count++; }
|
||||
}
|
||||
return count;
|
||||
}
|
91
core/src/module.h
Normal file
91
core/src/module.h
Normal file
@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <json.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef SDRPP_IS_CORE
|
||||
#define SDRPP_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define SDRPP_EXPORT extern "C" __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define SDRPP_EXPORT extern
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#define MOD_EXPORT extern "C" __declspec(dllexport)
|
||||
#define SDRPP_MOD_EXTENTSION ".dll"
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#define MOD_EXPORT extern "C"
|
||||
#define SDRPP_MOD_EXTENTSION ".so"
|
||||
#endif
|
||||
|
||||
class ModuleManager {
|
||||
public:
|
||||
struct ModuleInfo_t {
|
||||
const char* name;
|
||||
const char* description;
|
||||
const char* author;
|
||||
const int versionMajor;
|
||||
const int versionMinor;
|
||||
const int versionBuild;
|
||||
const int maxInstances;
|
||||
};
|
||||
|
||||
class Instance {
|
||||
public:
|
||||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
virtual bool isEnabled() = 0;
|
||||
};
|
||||
|
||||
struct Module_t {
|
||||
#ifdef _WIN32
|
||||
HMODULE handle;
|
||||
#else
|
||||
void* handle;
|
||||
#endif
|
||||
ModuleManager::ModuleInfo_t* info;
|
||||
void (*init)();
|
||||
ModuleManager::Instance* (*createInstance)(std::string name);
|
||||
void (*deleteInstance)(ModuleManager::Instance* instance);
|
||||
void (*end)();
|
||||
|
||||
friend bool operator==(const Module_t& a, const Module_t& b) {
|
||||
if (a.handle != b.handle) { return false; }
|
||||
if (a.info != b.info) { return false; }
|
||||
if (a.init != b.init) { return false; }
|
||||
if (a.createInstance != b.createInstance) { return false; }
|
||||
if (a.deleteInstance != b.deleteInstance) { return false; }
|
||||
if (a.end != b.end) { return false; }
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct Instance_t {
|
||||
ModuleManager::Module_t module;
|
||||
ModuleManager::Instance* instance;
|
||||
};
|
||||
|
||||
ModuleManager::Module_t loadModule(std::string path);
|
||||
|
||||
void createInstance(std::string name, std::string module);
|
||||
void deleteInstance(std::string name);
|
||||
void deleteInstance(ModuleManager::Instance* instance);
|
||||
|
||||
void enableInstance(std::string name);
|
||||
void disableInstance(std::string name);
|
||||
bool instanceEnabled(std::string name);
|
||||
|
||||
int countModuleInstances(std::string module);
|
||||
|
||||
private:
|
||||
std::map<std::string, ModuleManager::Module_t> modules;
|
||||
std::map<std::string, ModuleManager::Instance_t> instances;
|
||||
|
||||
};
|
||||
|
||||
#define SDRPP_MOD_INFO MOD_EXPORT const ModuleManager::ModuleInfo_t _INFO_
|
31
core/src/options.cpp
Normal file
31
core/src/options.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include <options.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace options {
|
||||
CMDLineOptions opts;
|
||||
|
||||
void loadDefaults() {
|
||||
#ifdef _WIN32
|
||||
opts.root = ".";
|
||||
#else
|
||||
std::string homedir = getenv("HOME");
|
||||
opts.root = homedir + "/.config/sdrpp";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool parse(int argc, char *argv[]) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
char* arg = argv[i];
|
||||
if (!strcmp(arg, "-r") || !strcmp(arg, "--root")) {
|
||||
if (i == argc - 1) { return false; }
|
||||
opts.root = argv[++i];
|
||||
}
|
||||
else {
|
||||
spdlog::error("Invalid command line option: {0}", arg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
14
core/src/options.h
Normal file
14
core/src/options.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <module.h>
|
||||
|
||||
namespace options {
|
||||
struct CMDLineOptions {
|
||||
std::string root;
|
||||
};
|
||||
|
||||
SDRPP_EXPORT CMDLineOptions opts;
|
||||
|
||||
void loadDefaults();
|
||||
bool parse(int argc, char *argv[]);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user