mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			186 Commits
		
	
	
		
			0.2.1_alph
			...
			0.2.5_beta
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 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 | 
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,11 @@ | ||||
| build/ | ||||
| .vscode/ | ||||
| *.old | ||||
| *.old | ||||
| *.dll | ||||
| *.exe | ||||
| *.zip | ||||
| *.wav | ||||
| .DS_Store | ||||
| root_dev/ | ||||
| sdrpp_v0.2.5_beta_x64 | ||||
| sdrpp_v0.2.5_beta_x64_new_wf | ||||
| @@ -1,48 +1,71 @@ | ||||
| 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_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" 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_PLUTOSDR_SOURCE) | ||||
| add_subdirectory("plutosdr_source") | ||||
| endif (OPT_BUILD_PLUTOSDR_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_AUDIO_SINK) | ||||
| add_subdirectory("audio_sink") | ||||
| endif (OPT_BUILD_AUDIO_SINK) | ||||
|  | ||||
| # PortAudio | ||||
| find_package(portaudio CONFIG REQUIRED) | ||||
| target_link_libraries(sdrpp PRIVATE portaudio portaudio_static) | ||||
| 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) | ||||
|  | ||||
|  | ||||
| # cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64" | ||||
| 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
									
								
								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(SOAPY REQUIRED airspyhf) | ||||
|  | ||||
|     target_include_directories(airspyhf_source PUBLIC ${AIRSPYHF_INCLUDE_DIRS}) | ||||
|     target_link_directories(airspyhf_source PUBLIC ${AIRSPYHF_LIBRARY_DIRS}) | ||||
|     target_link_libraries(airspyhf_source PUBLIC ${AIRSPYHF_LIBRARIES}) | ||||
| endif (MSVC) | ||||
							
								
								
									
										316
									
								
								airspyhf_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								airspyhf_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,316 @@ | ||||
| #include <imgui.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <new_module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <config.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 serString = config.conf["device"]; | ||||
|         // config.release(); | ||||
|  | ||||
|         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); | ||||
|         char buf[1024]; | ||||
|         sampleRateList.clear(); | ||||
|         sampleRateListTxt = ""; | ||||
|         for (int i = 0; i < n; i++) { | ||||
|             sampleRateList.push_back(sampleRates[i]); | ||||
|             sprintf(buf, "%d", sampleRates[i]); | ||||
|             sampleRateListTxt += buf; | ||||
|             sampleRateListTxt += '\0'; | ||||
|         } | ||||
|  | ||||
|         blockSize = airspyhf_get_output_size(dev); | ||||
|         spdlog::info("AirspyHF block size {0}", blockSize); | ||||
|  | ||||
|         srId = 0; | ||||
|  | ||||
|         airspyhf_close(dev); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     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; | ||||
|         } | ||||
|  | ||||
|         spdlog::warn("{0}", _this->sampleRateList[_this->srId]); | ||||
|  | ||||
|         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]); | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|         } | ||||
|  | ||||
|         ImGui::SameLine(); | ||||
|         float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX(); | ||||
|         if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) { | ||||
|             _this->refresh(); | ||||
|             _this->selectFirst(); | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|             } | ||||
|         }         | ||||
|     } | ||||
|  | ||||
|     static int callback(airspyhf_transfer_t* transfer) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)transfer->ctx; | ||||
|         if (_this->stream.aquire() < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|         memcpy(_this->stream.data, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t)); | ||||
|         _this->stream.write(transfer->sample_count); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     airspyhf_device_t* openDev; | ||||
|     bool enabled = true; | ||||
|     int blockSize = 0; | ||||
|     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::vector<uint64_t> devList; | ||||
|     std::string devListTxt; | ||||
|     std::vector<uint32_t> sampleRateList; | ||||
|     std::string sampleRateListTxt; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
| //    config.setPath(ROOT_DIR "/airspyhf_config.json"); | ||||
| //    json defConf; | ||||
| //    defConf["device"] = ""; | ||||
| //    defConf["devices"] = json::object(); | ||||
| //    config.load(defConf); | ||||
| //    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(); | ||||
| } | ||||
							
								
								
									
										16
									
								
								audio_sink/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								audio_sink/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| 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 "") | ||||
							
								
								
									
										287
									
								
								audio_sink/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								audio_sink/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | ||||
| #include <imgui.h> | ||||
| #include <new_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 | ||||
|         Pa_Initialize(); | ||||
|         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() { | ||||
|         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; | ||||
|         sigpath::sinkManager.registerSinkProvider("Audio", provider); | ||||
|     } | ||||
|  | ||||
|     ~AudioSinkModule() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     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_() { | ||||
|      | ||||
| } | ||||
							
								
								
									
										103
									
								
								core/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								core/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| 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) | ||||
|      | ||||
|     # SoapySDR | ||||
|     target_link_libraries(sdrpp_core PUBLIC SoapySDR) | ||||
|  | ||||
|     # 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) | ||||
|  | ||||
|     # PortAudio | ||||
|     find_package(portaudio CONFIG REQUIRED) | ||||
|     target_link_libraries(sdrpp_core PUBLIC portaudio portaudio_static) | ||||
|  | ||||
|     target_link_libraries(sdrpp_core PUBLIC volk) | ||||
|  | ||||
| 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) | ||||
|     pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0) | ||||
|  | ||||
|     target_include_directories(sdrpp_core PUBLIC | ||||
|         ${GLEW_INCLUDE_DIRS} | ||||
|         ${FFTW3_INCLUDE_DIRS} | ||||
|         ${GLFW3_INCLUDE_DIRS} | ||||
|         ${VOLK_INCLUDE_DIRS} | ||||
|         ${PORTAUDIO_INCLUDE_DIRS} | ||||
|         ) | ||||
|  | ||||
|     target_link_directories(sdrpp_core PUBLIC | ||||
|         ${GLEW_LIBRARY_DIRS} | ||||
|         ${FFTW3_LIBRARY_DIRS} | ||||
|         ${GLFW3_LIBRARY_DIRS} | ||||
|         ${VOLK_LIBRARY_DIRS} | ||||
|         ${PORTAUDIO_LIBRARY_DIRS} | ||||
|         ) | ||||
|  | ||||
|     target_link_libraries(sdrpp_core PUBLIC | ||||
|         ${OPENGL_LIBRARIES} | ||||
|         ${GLEW_STATIC_LIBRARIES} | ||||
|         ${FFTW3_STATIC_LIBRARIES} | ||||
|         ${GLFW3_STATIC_LIBRARIES} | ||||
|         ${VOLK_STATIC_LIBRARIES} | ||||
|         ${PORTAUDIO_STATIC_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; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										302
									
								
								core/src/core.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								core/src/core.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,302 @@ | ||||
| #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> | ||||
|  | ||||
| #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; } | ||||
|  | ||||
|     // ======== 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["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"]["Audio Sink"] = "audio_sink"; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"] = "plutosdr_source"; | ||||
|     defConfig["moduleInstances"]["RTL-TCP Source"] = "rtl_tcp_source"; | ||||
|     defConfig["moduleInstances"]["Radio"] = "radio"; | ||||
|     defConfig["moduleInstances"]["Recorder"] = "recorder"; | ||||
|     defConfig["moduleInstances"]["SoapySDR Source"] = "soapy_source"; | ||||
|     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; | ||||
|  | ||||
|     // Load config | ||||
|     spdlog::info("Loading config"); | ||||
|     core::configManager.setPath(options::opts.root + "/config.json"); | ||||
|     core::configManager.load(defConfig); | ||||
|     core::configManager.enableAutoSave(); | ||||
|  | ||||
|     // Setup window | ||||
|     glfwSetErrorCallback(glfw_error_callback); | ||||
|     if (!glfwInit()) { | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|      | ||||
|     core::configManager.aquire(); | ||||
|     int winWidth = core::configManager.conf["windowSize"]["w"]; | ||||
|     int winHeight = core::configManager.conf["windowSize"]["h"]; | ||||
|     maximized = core::configManager.conf["maximized"]; | ||||
|     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)(options::opts.root + "/res/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("#version 150"); | ||||
|  | ||||
|     style::setDarkStyle(); | ||||
|  | ||||
|     LoadingScreen::setWindow(window); | ||||
|  | ||||
|     LoadingScreen::show("Loading icons"); | ||||
|     spdlog::info("Loading icons"); | ||||
|     icons::load(); | ||||
|  | ||||
|     LoadingScreen::show("Loading band plans"); | ||||
|     spdlog::info("Loading band plans"); | ||||
|     bandplan::loadFromDir(options::opts.root + "/bandplans"); | ||||
|  | ||||
|     LoadingScreen::show("Loading band plan colors"); | ||||
|     spdlog::info("Loading band plans color table"); | ||||
|     bandplan::loadColorTable(options::opts.root + "/band_colors.json"); | ||||
|  | ||||
|     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 <new_module.h> | ||||
| #include <scripting.h> | ||||
| #include <new_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[]); | ||||
							
								
								
									
										30
									
								
								core/src/credits.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								core/src/credits.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| #include <credits.h> | ||||
|  | ||||
| namespace sdrpp_credits { | ||||
|     const char* contributors[] = { | ||||
|         "Ryzerth (Author)", | ||||
|         "aosync", | ||||
|         "Alexsey Shestacov", | ||||
|         "Benjamin Kyd", | ||||
|         "Tobias Mädel", | ||||
|         "Raov", | ||||
|         "Howard0su" | ||||
|     }; | ||||
|  | ||||
|     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 <new_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; | ||||
| } | ||||
							
								
								
									
										95
									
								
								core/src/dsp/audio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								core/src/dsp/audio.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| #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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; } | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out.data[i].l = _in->data[i]; | ||||
|                 out.data[i].r = _in->data[i]; | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; } | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out.data[i] = (_in->data[i].l + _in->data[i].r) / 2.0f; | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         stream<stereo_t>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										126
									
								
								core/src/dsp/block.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								core/src/dsp/block.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| #pragma once | ||||
| #include <stdio.h> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/types.h> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
| #include <algorithm> | ||||
|  | ||||
| #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 const& in : inputs) { | ||||
|                 in->stopReader(); | ||||
|             } | ||||
|             for (auto const& 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 const& in : inputs) { | ||||
|                 in->clearReadStop(); | ||||
|             } | ||||
|             for (auto const& 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,22 @@ | ||||
| #pragma once | ||||
| #include <condition_variable> | ||||
| #include <algorithm> | ||||
| #include <math.h> | ||||
| #include <dsp/block.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; | ||||
							
								
								
									
										138
									
								
								core/src/dsp/convertion.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								core/src/dsp/convertion.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| #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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|             memcpy(out.data, _in->data, count * sizeof(complex_t)); | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|             volk_32fc_deinterleave_real_32f(out.data, (lv_32fc_t*)_in->data, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|             volk_32fc_deinterleave_imag_32f(out.data, (lv_32fc_t*)_in->data, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         float avg; | ||||
|         int count; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										283
									
								
								core/src/dsp/demodulator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								core/src/dsp/demodulator.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,283 @@ | ||||
| #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; | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 currentPhase = fast_arctan2(_in->data[i].i, _in->data[i].q); | ||||
|                 diff = currentPhase - phase; | ||||
|                 if (diff > 3.1415926535f)        { diff -= 2 * 3.1415926535f; } | ||||
|                 else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; } | ||||
|                 out.data[i] = diff / phasorSpeed; | ||||
|                 phase = currentPhase; | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|             volk_32fc_magnitude_32f(out.data, (lv_32fc_t*)_in->data, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|  | ||||
|             volk_32f_accumulator_s32f(&avg, out.data, count); | ||||
|             avg /= (float)count; | ||||
|  | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out.data[i] -= avg; | ||||
|             } | ||||
|  | ||||
|             out.write(count); | ||||
|             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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|             volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->data, phaseDelta, &phase, count); | ||||
|             volk_32fc_deinterleave_real_32f(out.data, buffer, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             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; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										173
									
								
								core/src/dsp/filter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								core/src/dsp/filter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| #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->data, count * sizeof(T)); | ||||
|             _in->flush(); | ||||
|  | ||||
|             // Write to output | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|  | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 for (int i = 0; i < count; i++) { | ||||
|                     volk_32f_x2_dot_prod_32f((float*)&out.data[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.data[i], (lv_32fc_t*)&buffer[i+1], taps, tapCount); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             out.write(count); | ||||
|  | ||||
|             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) { | ||||
|                 if (out.aquire() < 0) { return -1; } | ||||
|                 memcpy(out.data, _in->data, count * sizeof(float)); | ||||
|                 _in->flush(); | ||||
|                 out.write(count); | ||||
|                 return count; | ||||
|             } | ||||
|  | ||||
|             if (isnan(lastOut)) { | ||||
|                 lastOut = 0.0f; | ||||
|             } | ||||
|             if (out.aquire() < 0) { return -1; }  | ||||
|             out.data[0] = (alpha * _in->data[0]) + ((1-alpha) * lastOut); | ||||
|             for (int i = 1; i < count; i++) { | ||||
|                 out.data[i] = (alpha * _in->data[i]) + ((1 - alpha) * out.data[i - 1]); | ||||
|             } | ||||
|             lastOut = out.data[count - 1]; | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         bool bypass = false; | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float lastOut = 0.0f; | ||||
|         float alpha; | ||||
|         float _tau; | ||||
|         float _sampleRate; | ||||
|         stream<float>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										107
									
								
								core/src/dsp/math.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								core/src/dsp/math.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| #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 (out.aquire() < 0) { return -1; } | ||||
|             if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) { | ||||
|                 volk_32fc_x2_add_32fc(out.data, _a->data, _b->data, a_count); | ||||
|             } | ||||
|             else { | ||||
|                 volk_32f_x2_add_32f(out.data, _a->data, _b->data, a_count); | ||||
|             } | ||||
|  | ||||
|             _a->flush(); | ||||
|             _b->flush(); | ||||
|             out.write(a_count); | ||||
|             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 (out.aquire() < 0) { return -1; } | ||||
|             if constexpr (std::is_same_v<T, complex_t>) { | ||||
|                 volk_32fc_x2_multiply_32fc(out.data, _a->data, _b->data, a_count); | ||||
|             } | ||||
|             else { | ||||
|                 volk_32f_x2_multiply_32f(out.data, _a->data, _b->data, a_count); | ||||
|             } | ||||
|  | ||||
|             _a->flush(); | ||||
|             _b->flush(); | ||||
|             out.write(a_count); | ||||
|             return a_count; | ||||
|         } | ||||
|  | ||||
|         stream<T> out; | ||||
|  | ||||
|     private: | ||||
|         int a_count, b_count; | ||||
|         stream<T>* _a; | ||||
|         stream<T>* _b; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										291
									
								
								core/src/dsp/processing.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								core/src/dsp/processing.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <fftw3.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; } | ||||
|  | ||||
|             if (out.aquire() < 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.data, (lv_32fc_t*)_in->data, phaseDelta, &phase, count); | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             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 ratio) { init(in, ratio); } | ||||
|  | ||||
|         ~AGC() { generic_block<AGC>::stop(); } | ||||
|  | ||||
|         void init(stream<float>* in, float ratio) { | ||||
|             _in = in; | ||||
|             _ratio = ratio; | ||||
|             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(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; } | ||||
|  | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 level = (fabsf(_in->data[i]) * _ratio) + (level * (1.0f - _ratio)); | ||||
|                 out.data[i] = _in->data[i] / level; | ||||
|             } | ||||
|  | ||||
|              | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float level = 1.0f; | ||||
|         float _ratio; | ||||
|         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 (out.aquire() < 0) { return -1; } | ||||
|  | ||||
|             if (_muted) { | ||||
|                 if constexpr (std::is_same_v<T, stereo_t>) { | ||||
|                     memset(out.data, 0, sizeof(stereo_t) * count); | ||||
|                 } | ||||
|                 else { | ||||
|                     memset(out.data, 0, sizeof(float) * count); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 if constexpr (std::is_same_v<T, stereo_t>) { | ||||
|                     volk_32f_s32f_multiply_32f((float*)out.data, (float*)_in->data, level, count * 2); | ||||
|                 } | ||||
|                 else { | ||||
|                     volk_32f_s32f_multiply_32f((float*)out.data, (float*)_in->data, level, count); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             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; } | ||||
|  | ||||
|             if (out.aquire() < 0) { return -1; } | ||||
|             float sum = 0.0f; | ||||
|             volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->data, count); | ||||
|             volk_32f_accumulator_s32f(&sum, normBuffer, count); | ||||
|             sum /= (float)count; | ||||
|  | ||||
|             if (10.0f * log10f(sum) >= _level) { | ||||
|                 memcpy(out.data, _in->data, count * sizeof(complex_t)); | ||||
|             } | ||||
|             else { | ||||
|                 memset(out.data, 0, count * sizeof(complex_t)); | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             out.write(count); | ||||
|             return count;  | ||||
|         } | ||||
|  | ||||
|         stream<complex_t> out; | ||||
|  | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float* normBuffer; | ||||
|         float _level = -50.0f; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										154
									
								
								core/src/dsp/resampling.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								core/src/dsp/resampling.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| #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); | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|  | ||||
|             buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment()); | ||||
|             memset(buffer, 0, STREAM_BUFFER_SIZE * sizeof(T) * 2); | ||||
|             bufStart = &buffer[tapCount]; | ||||
|             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; | ||||
|             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; | ||||
|             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); | ||||
|             bufStart = &buffer[tapCount]; | ||||
|             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[tapCount], _in->data, count * sizeof(T)); | ||||
|             _in->flush(); | ||||
|  | ||||
|             // Write to output | ||||
|             if (out.aquire() < 0) { | ||||
|                 return -1; | ||||
|             } | ||||
|             int outIndex = 0; | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 for (int i = 0; outIndex < outCount; i += _decim) { | ||||
|                     out.data[outIndex] = 0; | ||||
|                     for (int j = i % _interp; j < tapCount; j += _interp) { | ||||
|                         out.data[outIndex] += buffer[((i - j) / _interp) + tapCount] * taps[j]; | ||||
|                     } | ||||
|                     outIndex++; | ||||
|                 } | ||||
|             } | ||||
|             if constexpr (std::is_same_v<T, complex_t>) { | ||||
|                 for (int i = 0; outIndex < outCount; i += _decim) { | ||||
|                     out.data[outIndex].i = 0; | ||||
|                     out.data[outIndex].q = 0; | ||||
|                     for (int j = i % _interp; j < tapCount; j += _interp) { | ||||
|                         out.data[outIndex].i += buffer[((i - j) / _interp) + tapCount].i * taps[j]; | ||||
|                         out.data[outIndex].q += buffer[((i - j) / _interp) + tapCount].q * taps[j]; | ||||
|                     } | ||||
|                     outIndex++; | ||||
|                 } | ||||
|             } | ||||
|             out.write(outCount); | ||||
|  | ||||
|             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; | ||||
|         int _interp, _decim; | ||||
|         float _inSampleRate, _outSampleRate; | ||||
|         float* taps; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										191
									
								
								core/src/dsp/routing.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								core/src/dsp/routing.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| #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) { | ||||
|                 if (stream->aquire() < 0) { return -1; }  | ||||
|                 memcpy(stream->data, _in->data, count * sizeof(T)); | ||||
|                 stream->write(count); | ||||
|             } | ||||
|             _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->data, 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; }; | ||||
|                 if (out.aquire() < 0) { break; } | ||||
|                 memcpy(out.data, buf, _keep * sizeof(complex_t)); | ||||
|                 out.write(_keep); | ||||
|             } | ||||
|             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->data, 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->data, 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; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										75
									
								
								core/src/dsp/source.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								core/src/dsp/source.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| #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() { | ||||
|             if (out.aquire() < 0) { return -1; } | ||||
|             volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.data, zeroPhase, phaseDelta, &phase, _blockSize); | ||||
|             out.write(_blockSize); | ||||
|             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; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										108
									
								
								core/src/dsp/stream.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								core/src/dsp/stream.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| #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 int aquire() { return -1; } | ||||
|         virtual void write(int size) {} | ||||
|         virtual int read() { return -1; } | ||||
|         virtual void flush() {} | ||||
|         virtual void stopReader() {} | ||||
|         virtual void clearReadStop() {} | ||||
|         virtual void stopWriter() {} | ||||
|         virtual void clearWriteStop() {} | ||||
|     }; | ||||
|  | ||||
|     template <class T> | ||||
|     class stream : public untyped_steam { | ||||
|     public: | ||||
|         stream() { | ||||
|             data = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment()); | ||||
|         } | ||||
|  | ||||
|         int aquire() { | ||||
|             waitReady(); | ||||
|             if (writerStop) { | ||||
|                 return -1; | ||||
|             } | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         void write(int size) { | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(sigMtx); | ||||
|                 contentSize = size; | ||||
|                 dataReady = true; | ||||
|             } | ||||
|             cv.notify_one(); | ||||
|         } | ||||
|  | ||||
|         int read() { | ||||
|             waitData(); | ||||
|             if (readerStop) { | ||||
|                 return -1; | ||||
|             } | ||||
|             return contentSize; | ||||
|         } | ||||
|  | ||||
|         void flush() { | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(sigMtx); | ||||
|                 dataReady = false; | ||||
|             } | ||||
|             cv.notify_one(); | ||||
|         } | ||||
|  | ||||
|         void stopReader() { | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(sigMtx); | ||||
|                 readerStop = true; | ||||
|             } | ||||
|             cv.notify_one(); | ||||
|         } | ||||
|  | ||||
|         void clearReadStop() { | ||||
|             readerStop = false; | ||||
|         } | ||||
|  | ||||
|         void stopWriter() { | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(sigMtx); | ||||
|                 writerStop = true; | ||||
|             } | ||||
|             cv.notify_one(); | ||||
|         } | ||||
|  | ||||
|         void clearWriteStop() { | ||||
|             writerStop = false; | ||||
|         } | ||||
|  | ||||
|         T* data; | ||||
|  | ||||
|     private: | ||||
|         void waitReady() { | ||||
|             std::unique_lock<std::mutex> lck(sigMtx); | ||||
|             cv.wait(lck, [this]{ return (!dataReady || writerStop); }); | ||||
|         } | ||||
|  | ||||
|         void waitData() { | ||||
|             std::unique_lock<std::mutex> lck(sigMtx); | ||||
|             cv.wait(lck, [this]{ return (dataReady || readerStop); }); | ||||
|         } | ||||
|  | ||||
|         std::mutex sigMtx; | ||||
|         std::condition_variable cv; | ||||
|         bool dataReady = false; | ||||
|  | ||||
|         bool readerStop = false; | ||||
|         bool writerStop = false; | ||||
|  | ||||
|         int contentSize = 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; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										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(sdrpp_credits::contributors[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Libraries"); | ||||
|         for (int i = 0; i < sdrpp_credits::libraryCount; i++) { | ||||
|             ImGui::BulletText(sdrpp_credits::libraries[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Patrons"); | ||||
|         for (int i = 0; i < sdrpp_credits::patronCount; i++) { | ||||
|             ImGui::BulletText(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(sdrpp_credits::contributors[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Libraries"); | ||||
|         for (int i = 0; i < sdrpp_credits::libraryCount; i++) { | ||||
|             ImGui::BulletText(sdrpp_credits::libraries[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Patrons"); | ||||
|         for (int i = 0; i < sdrpp_credits::patronCount; i++) { | ||||
|             ImGui::BulletText(sdrpp_credits::patrons[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::Columns(1, "CreditColumnsEnd", true); | ||||
|  | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Text(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 <new_module.h> | ||||
|  | ||||
| namespace gui { | ||||
|     SDRPP_EXPORT ImGui::WaterFall waterfall; | ||||
|     SDRPP_EXPORT FrequencySelect freqSelect; | ||||
|     SDRPP_EXPORT Menu menu; | ||||
|  | ||||
|     void selectSource(std::string name); | ||||
| }; | ||||
							
								
								
									
										44
									
								
								core/src/gui/icons.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								core/src/gui/icons.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| #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> | ||||
|  | ||||
| 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; | ||||
|     } | ||||
|  | ||||
|     void load() { | ||||
|         LOGO = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/sdrpp.png"); | ||||
|         PLAY = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/play.png"); | ||||
|         STOP = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/stop.png"); | ||||
|         MENU = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/menu.png"); | ||||
|         MUTED = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/muted.png"); | ||||
|         UNMUTED = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/unmuted.png"); | ||||
|         NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/normal_tuning.png"); | ||||
|         CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(options::opts.root + "/res/icons/center_tuning.png"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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); | ||||
|     void load(); | ||||
| } | ||||
							
								
								
									
										612
									
								
								core/src/gui/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										612
									
								
								core/src/gui/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,612 @@ | ||||
| #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> | ||||
|  | ||||
| // 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]; | ||||
|  | ||||
|  | ||||
|  | ||||
| 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>>(); | ||||
|     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(options::opts.root + "/modules")) { | ||||
|         for (const auto & file : std::filesystem::directory_iterator(options::opts.root + "/modules")) { | ||||
|             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"); | ||||
|     } | ||||
|  | ||||
|     // 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); | ||||
|     } | ||||
|  | ||||
|     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.setCenterOffset(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.setCenterOffset(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::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, "")) { | ||||
|         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; | ||||
| } | ||||
| @@ -1,12 +1,8 @@ | ||||
| #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 drawWindow(); | ||||
| void setViewBandwidthSlider(float bandwidth); | ||||
							
								
								
									
										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); | ||||
| }; | ||||
							
								
								
									
										22
									
								
								core/src/gui/menus/display.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								core/src/gui/menus/display.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| #include <gui/menus/display.h> | ||||
| #include <imgui.h> | ||||
| #include <gui/gui.h> | ||||
| #include <core.h> | ||||
|  | ||||
| namespace displaymenu { | ||||
|     bool showWaterfall; | ||||
|  | ||||
|     void init() { | ||||
|         showWaterfall = core::configManager.conf["showWaterfall"]; | ||||
|         showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall(); | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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); | ||||
| }; | ||||
							
								
								
									
										54
									
								
								core/src/gui/menus/source.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								core/src/gui/menus/source.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| #include <gui/menus/source.h> | ||||
| #include <imgui.h> | ||||
| #include <gui/gui.h> | ||||
| #include <core.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(); | ||||
|          | ||||
|         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); | ||||
|         } | ||||
|  | ||||
|         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); | ||||
| } | ||||
							
								
								
									
										105
									
								
								core/src/gui/style.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								core/src/gui/style.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| #include <gui/style.h> | ||||
| #include <imgui.h> | ||||
| #include <imgui_internal.h> | ||||
| #include <config.h> | ||||
| #include <options.h> | ||||
|  | ||||
| namespace style { | ||||
|     ImFont* baseFont; | ||||
|     ImFont* bigFont; | ||||
|     ImFont* hugeFont; | ||||
|  | ||||
|     void setDefaultStyle() { | ||||
|         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)(options::opts.root + "/res/fonts/Roboto-Medium.ttf")).c_str(), 16.0f); | ||||
|         bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(options::opts.root + "/res/fonts/Roboto-Medium.ttf")).c_str(), 42.0f); | ||||
|         hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(options::opts.root + "/res/fonts/Roboto-Medium.ttf")).c_str(), 128.0f); | ||||
|  | ||||
|         ImGui::StyleColorsDark(); | ||||
|         //ImGui::StyleColorsLight(); | ||||
|     } | ||||
|  | ||||
|     void testtt() { | ||||
|         ImGui::StyleColorsLight(); | ||||
|     } | ||||
|  | ||||
|     void setDarkStyle() { | ||||
|         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)(options::opts.root + "/res/fonts/Roboto-Medium.ttf")).c_str(), 16.0f); | ||||
|         bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(options::opts.root + "/res/fonts/Roboto-Medium.ttf")).c_str(), 42.0f); | ||||
|         hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(options::opts.root + "/res/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); | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								core/src/gui/style.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								core/src/gui/style.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| #include <imgui.h> | ||||
|  | ||||
| namespace style { | ||||
|     extern ImFont* baseFont; | ||||
|     extern ImFont* bigFont; | ||||
|     extern ImFont* hugeFont; | ||||
|  | ||||
|     void setDefaultStyle(); | ||||
|     void setDarkStyle(); | ||||
|     void beginDisabled(); | ||||
|     void endDisabled(); | ||||
|     void testtt(); | ||||
| } | ||||
							
								
								
									
										127
									
								
								core/src/gui/widgets/bandplan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								core/src/gui/widgets/bandplan.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| #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(std::string path) { | ||||
|         if (!std::filesystem::exists(path)) { | ||||
|             spdlog::error("Band Plan Color Table file does not exist"); | ||||
|             return; | ||||
|         } | ||||
|         if (!std::filesystem::is_regular_file(path)) { | ||||
|             spdlog::error("Band Plan Color Table file isn't a file..."); | ||||
|             return; | ||||
|         } | ||||
|         std::ifstream file(path.c_str()); | ||||
|         json data; | ||||
|         file >> data; | ||||
|         file.close(); | ||||
|  | ||||
|         colorTable = data.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(std::string path); | ||||
|  | ||||
|     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,6 @@ | ||||
| #include <frequency_select.h> | ||||
| #include <gui/widgets/frequency_select.h> | ||||
| #include <config.h> | ||||
| #include <gui/style.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,9 +11,9 @@ 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; | ||||
|          | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @@ -67,13 +69,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,6 +87,9 @@ void FrequencySelect::draw() { | ||||
|         onResize(); | ||||
|     } | ||||
| 
 | ||||
|     ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f); | ||||
|     ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); | ||||
| 
 | ||||
|     int commaOffset = 0; | ||||
|     bool zeros = true; | ||||
|     for (int i = 0; i < 12; i++) { | ||||
| @@ -92,11 +98,11 @@ void FrequencySelect::draw() { | ||||
|         } | ||||
|         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); | ||||
|                                 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), "."); | ||||
|                                     zeros ? disabledColor : textColor, "."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @@ -138,19 +144,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 <new_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; | ||||
| }; | ||||
							
								
								
									
										885
									
								
								core/src/gui/widgets/waterfall.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										885
									
								
								core/src/gui/widgets/waterfall.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,885 @@ | ||||
| #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 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(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()); | ||||
|             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 - 25),  | ||||
|                                         ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10), colorTrans); | ||||
|                 if (startVis) { | ||||
|                     window->DrawList->AddLine(ImVec2(roundf(aPos), widgetPos.y + fftHeight - 26),  | ||||
|                                         ImVec2(roundf(aPos), widgetPos.y + fftHeight + 9), color); | ||||
|                 } | ||||
|                 if (endVis) { | ||||
|                     window->DrawList->AddLine(ImVec2(roundf(bPos), widgetPos.y + fftHeight - 26),  | ||||
|                                         ImVec2(roundf(bPos), widgetPos.y + fftHeight + 9), color); | ||||
|                 } | ||||
|             } | ||||
|             if (txtSz.x <= width) { | ||||
|                 window->DrawList->AddText(ImVec2(cPos - (txtSz.x / 2.0), widgetPos.y + fftHeight - 17),  | ||||
|                                     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(); | ||||
|         currentFFTLine--; | ||||
|         fftLines++; | ||||
|         currentFFTLine = ((currentFFTLine + waterfallHeight) % waterfallHeight); | ||||
|         fftLines = std::min<float>(fftLines, waterfallHeight); | ||||
|         return &rawFFTs[currentFFTLine * rawFFTSize]; | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|          | ||||
|         doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT); | ||||
|  | ||||
|         if (waterfallVisible) { | ||||
|             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; | ||||
|         } | ||||
|          | ||||
|         buf_mtx.unlock(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::updatePallette(float colors[][3], int colorCount) { | ||||
|         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; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|         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) { | ||||
|         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) { | ||||
|         if (min == waterfallMin) { | ||||
|             return; | ||||
|         } | ||||
|         waterfallMin = min; | ||||
|         updateWaterfallFb(); | ||||
|     } | ||||
|  | ||||
|     float WaterFall::getWaterfallMin() { | ||||
|         return waterfallMin; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setWaterfallMax(float max) { | ||||
|         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) { | ||||
|         if (lock) { buf_mtx.lock(); } | ||||
|         rawFFTSize = size; | ||||
|         if (rawFFTs != NULL) { | ||||
|             rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * waterfallHeight * sizeof(float)); | ||||
|         } | ||||
|         else { | ||||
|             rawFFTs = (float*)malloc(rawFFTSize * waterfallHeight * sizeof(float)); | ||||
|         } | ||||
|         memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float)); | ||||
|         if (lock) { buf_mtx.unlock(); } | ||||
|     } | ||||
|  | ||||
|     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() { | ||||
|         waterfallVisible = true; | ||||
|         buf_mtx.lock(); | ||||
|         onResize(); | ||||
|         buf_mtx.unlock(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::hideWaterfall() { | ||||
|         waterfallVisible = false; | ||||
|         buf_mtx.lock(); | ||||
|         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; | ||||
|     } | ||||
| }; | ||||
|  | ||||
							
								
								
									
										202
									
								
								core/src/gui/widgets/waterfall.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								core/src/gui/widgets/waterfall.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| #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 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/new_module.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								core/src/new_module.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| #include <new_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/new_module.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								core/src/new_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_ | ||||
							
								
								
									
										29
									
								
								core/src/options.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								core/src/options.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| #include <options.h> | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| namespace options { | ||||
|     CMDLineOptions opts; | ||||
|  | ||||
|     void loadDefaults() { | ||||
| #ifdef _WIN32 | ||||
|         opts.root = "."; | ||||
| #else | ||||
|         opts.root = "~/.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 <new_module.h> | ||||
|  | ||||
| namespace options { | ||||
|     struct CMDLineOptions { | ||||
|         std::string root; | ||||
|     }; | ||||
|  | ||||
|     SDRPP_EXPORT CMDLineOptions opts; | ||||
|  | ||||
|     void loadDefaults(); | ||||
|     bool parse(int argc, char *argv[]); | ||||
| } | ||||
							
								
								
									
										93
									
								
								core/src/scripting.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								core/src/scripting.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| #include <scripting.h> | ||||
| #include <duktape/duk_console.h> | ||||
| #include <version.h> | ||||
| #include <fstream> | ||||
| #include <sstream> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <imgui/imgui.h> | ||||
|  | ||||
| ScriptManager::ScriptManager() { | ||||
|  | ||||
| } | ||||
|  | ||||
| ScriptManager::Script* ScriptManager::createScript(std::string name, std::string path) { | ||||
|     ScriptManager::Script* script =  new ScriptManager::Script(this, name, path); | ||||
|     scripts[name] = script; | ||||
|     return script; | ||||
| } | ||||
|  | ||||
| ScriptManager::Script::Script(ScriptManager* man, std::string name, std::string path) { | ||||
|     this->name = name; | ||||
|     manager = man; | ||||
|     std::ifstream file(path, std::ios::in); | ||||
|     std::stringstream ss; | ||||
|     ss << file.rdbuf(); | ||||
|     code = ss.str();  | ||||
| } | ||||
|  | ||||
| void ScriptManager::Script::run() { | ||||
|     ctx = ScriptManager::createContext(manager, name); | ||||
|     running = true; | ||||
|     if (worker.joinable()) { | ||||
|         worker.join(); | ||||
|     } | ||||
|     worker = std::thread(scriptWorker, this); | ||||
| } | ||||
|  | ||||
| duk_context* ScriptManager::createContext(ScriptManager* _this, std::string name) { | ||||
|     duk_context* ctx = duk_create_heap_default(); | ||||
|  | ||||
|     duk_console_init(ctx, DUK_CONSOLE_PROXY_WRAPPER); | ||||
|  | ||||
|     duk_push_string(ctx, name.c_str()); | ||||
|     duk_put_global_string(ctx, "SCRIPT_NAME"); | ||||
|  | ||||
|     duk_idx_t sdrppBase = duk_push_object(ctx); | ||||
|  | ||||
|     // API | ||||
|  | ||||
|     duk_push_string(ctx, VERSION_STR); | ||||
|     duk_put_prop_string(ctx, sdrppBase, "version"); | ||||
|  | ||||
|     duk_push_c_function(ctx, ScriptManager::duk_setSource, 1); | ||||
|     duk_put_prop_string(ctx, sdrppBase, "selectSource"); | ||||
|  | ||||
|     // Modules | ||||
|  | ||||
|     duk_idx_t modObjId = duk_push_object(ctx); | ||||
|     for (const auto& [name, handler] : _this->handlers) { | ||||
|         duk_idx_t objId = duk_push_object(ctx); | ||||
|         handler.handler(handler.ctx, ctx, objId); | ||||
|         duk_put_prop_string(ctx, modObjId, name.c_str()); | ||||
|     } | ||||
|     duk_put_prop_string(ctx, sdrppBase, "modules"); | ||||
|  | ||||
|     duk_put_global_string(ctx, "sdrpp"); | ||||
|  | ||||
|     return ctx; | ||||
| } | ||||
|  | ||||
| // TODO: Switch to spdlog | ||||
|  | ||||
| void ScriptManager::Script::scriptWorker(Script* script) { | ||||
|     if (duk_peval_string(script->ctx, script->code.c_str()) != 0) { | ||||
|         printf("Error: %s\n", duk_safe_to_string(script->ctx, -1)); | ||||
|         //return; | ||||
|     } | ||||
|     duk_destroy_heap(script->ctx); | ||||
|     script->running = false; | ||||
| } | ||||
|  | ||||
| void ScriptManager::bindScriptRunHandler(std::string name, ScriptManager::ScriptRunHandler_t handler) { | ||||
|     // TODO: check if it exists and add a "unbind" function | ||||
|     handlers[name] = handler; | ||||
| } | ||||
|  | ||||
| duk_ret_t ScriptManager::duk_setSource(duk_context* dukCtx) { | ||||
|     const char* name = duk_require_string(dukCtx, -1); | ||||
|     sigpath::sourceManager.selectSource(name); | ||||
|  | ||||
|     duk_pop_n(dukCtx, 1); // Pop demod name, this and context | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										51
									
								
								core/src/scripting.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								core/src/scripting.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <duktape/duktape.h> | ||||
|  | ||||
| class ScriptManager { | ||||
| public: | ||||
|     ScriptManager(); | ||||
|  | ||||
|     friend class Script; | ||||
|  | ||||
|     class Script { | ||||
|     public: | ||||
|         Script(ScriptManager* man, std::string name, std::string path); | ||||
|         void run(); | ||||
|         bool running = false; | ||||
|  | ||||
|     private: | ||||
|         static void scriptWorker(Script* _this); | ||||
|  | ||||
|         duk_context* ctx; | ||||
|         std::thread worker; | ||||
|         std::string code; | ||||
|         std::string name; | ||||
|          | ||||
|  | ||||
|         ScriptManager* manager; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     struct ScriptRunHandler_t { | ||||
|         void (*handler)(void* ctx, duk_context* dukCtx, duk_idx_t objId); | ||||
|         void* ctx; | ||||
|     }; | ||||
|  | ||||
|     void bindScriptRunHandler(std::string name, ScriptManager::ScriptRunHandler_t handler); | ||||
|     ScriptManager::Script* createScript(std::string name, std::string path); | ||||
|  | ||||
|     std::map<std::string, ScriptManager::Script*> scripts; | ||||
|  | ||||
| private: | ||||
|     static duk_context* createContext(ScriptManager* _this, std::string name); | ||||
|  | ||||
|     // API | ||||
|     static duk_ret_t duk_setSource(duk_context* ctx); | ||||
|  | ||||
|     std::map<std::string, ScriptManager::ScriptRunHandler_t> handlers; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										111
									
								
								core/src/signal_path/dsp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								core/src/signal_path/dsp.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| #include <signal_path/dsp.h> | ||||
|  | ||||
| SignalPath::SignalPath() { | ||||
|      | ||||
| } | ||||
|  | ||||
| void SignalPath::init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream<dsp::complex_t>* input, dsp::complex_t* fftBuffer, void fftHandler(dsp::complex_t*,int,void*)) { | ||||
|     this->sampleRate = sampleRate; | ||||
|     this->fftRate = fftRate; | ||||
|     this->fftSize = fftSize; | ||||
|     inputBlockSize = sampleRate / 200.0f; | ||||
|  | ||||
|     split.init(input); | ||||
|  | ||||
|     reshape.init(&fftStream, fftSize, (sampleRate / fftRate) - fftSize); | ||||
|     split.bindStream(&fftStream); | ||||
|     fftHandlerSink.init(&reshape.out, fftHandler, NULL); | ||||
| } | ||||
|  | ||||
| void SignalPath::setSampleRate(double sampleRate) { | ||||
|     this->sampleRate = sampleRate; | ||||
|  | ||||
|     split.stop(); | ||||
|  | ||||
|     for (auto const& [name, vfo] : vfos) { | ||||
|         vfo.vfo->stop(); | ||||
|     } | ||||
|  | ||||
|     // Claculate skip to maintain a constant fft rate | ||||
|     int skip = (sampleRate / fftRate) - fftSize; | ||||
|     reshape.setSkip(skip); | ||||
|  | ||||
|     // TODO: Tell modules that the block size has changed (maybe?) | ||||
|  | ||||
|     for (auto const& [name, vfo] : vfos) { | ||||
|         vfo.vfo->setInSampleRate(sampleRate); | ||||
|         vfo.vfo->start(); | ||||
|     } | ||||
|  | ||||
|     split.start(); | ||||
| } | ||||
|  | ||||
| double SignalPath::getSampleRate() { | ||||
|     return sampleRate; | ||||
| } | ||||
|  | ||||
| void SignalPath::start() { | ||||
|     split.start(); | ||||
|     reshape.start(); | ||||
|     fftHandlerSink.start(); | ||||
| } | ||||
|  | ||||
| void SignalPath::stop() { | ||||
|     split.stop(); | ||||
|     reshape.stop(); | ||||
|     fftHandlerSink.stop(); | ||||
| } | ||||
|  | ||||
| dsp::VFO* SignalPath::addVFO(std::string name, double outSampleRate, double bandwidth, double offset) { | ||||
|     if (vfos.find(name) != vfos.end()) { | ||||
|         return NULL; | ||||
|     } | ||||
|     VFO_t vfo; | ||||
|     vfo.inputStream = new dsp::stream<dsp::complex_t>; | ||||
|     split.bindStream(vfo.inputStream); | ||||
|     vfo.vfo = new dsp::VFO(); | ||||
|     vfo.vfo->init(vfo.inputStream, offset, sampleRate, outSampleRate, bandwidth); | ||||
|     vfo.vfo->start(); | ||||
|     vfos[name] = vfo; | ||||
|     return vfo.vfo; | ||||
| } | ||||
|  | ||||
| void SignalPath::removeVFO(std::string name) { | ||||
|     if (vfos.find(name) == vfos.end()) { | ||||
|         return; | ||||
|     } | ||||
|     VFO_t vfo = vfos[name]; | ||||
|     vfo.vfo->stop(); | ||||
|     split.unbindStream(vfo.inputStream); | ||||
|     delete vfo.vfo; | ||||
|     delete vfo.inputStream; | ||||
|     vfos.erase(name); | ||||
| } | ||||
|  | ||||
| void SignalPath::setInput(dsp::stream<dsp::complex_t>* input) { | ||||
|     split.setInput(input); | ||||
| } | ||||
|  | ||||
| void SignalPath::bindIQStream(dsp::stream<dsp::complex_t>* stream) { | ||||
|     split.bindStream(stream); | ||||
| } | ||||
|  | ||||
| void SignalPath::unbindIQStream(dsp::stream<dsp::complex_t>* stream) { | ||||
|     split.unbindStream(stream); | ||||
| } | ||||
|  | ||||
| void SignalPath::setFFTSize(int size) { | ||||
|     fftSize = size; | ||||
|     int skip = (sampleRate / fftRate) - fftSize; | ||||
|     reshape.setSkip(skip); | ||||
| } | ||||
|  | ||||
| void SignalPath::startFFT() { | ||||
|     reshape.start(); | ||||
|     fftHandlerSink.start(); | ||||
| } | ||||
|  | ||||
| void SignalPath::stopFFT() { | ||||
|     reshape.stop(); | ||||
|     fftHandlerSink.stop(); | ||||
| } | ||||
							
								
								
									
										44
									
								
								core/src/signal_path/dsp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								core/src/signal_path/dsp.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| #pragma once | ||||
| #include <dsp/routing.h> | ||||
| #include <dsp/vfo.h> | ||||
| #include <map> | ||||
| #include <dsp/sink.h> | ||||
|  | ||||
| class SignalPath { | ||||
| public: | ||||
|     SignalPath(); | ||||
|     void init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream<dsp::complex_t>* input, dsp::complex_t* fftBuffer, void fftHandler(dsp::complex_t*,int,void*)); | ||||
|     void start(); | ||||
|     void stop(); | ||||
|     void setSampleRate(double sampleRate); | ||||
|     double getSampleRate(); | ||||
|     dsp::VFO* addVFO(std::string name, double outSampleRate, double bandwidth, double offset); | ||||
|     void removeVFO(std::string name); | ||||
|     void setInput(dsp::stream<dsp::complex_t>* input); | ||||
|     void bindIQStream(dsp::stream<dsp::complex_t>* stream); | ||||
|     void unbindIQStream(dsp::stream<dsp::complex_t>* stream); | ||||
|     void setFFTSize(int size); | ||||
|     void startFFT(); | ||||
|     void stopFFT(); | ||||
|  | ||||
| private: | ||||
|     struct VFO_t { | ||||
|         dsp::stream<dsp::complex_t>* inputStream; | ||||
|         dsp::VFO* vfo; | ||||
|     }; | ||||
|  | ||||
|     dsp::Splitter<dsp::complex_t> split; | ||||
|  | ||||
|     // FFT | ||||
|     dsp::stream<dsp::complex_t> fftStream; | ||||
|     dsp::Reshaper<dsp::complex_t> reshape; | ||||
|     dsp::HandlerSink<dsp::complex_t> fftHandlerSink; | ||||
|  | ||||
|     // VFO | ||||
|     std::map<std::string, VFO_t> vfos; | ||||
|  | ||||
|     double sampleRate; | ||||
|     double fftRate; | ||||
|     int fftSize; | ||||
|     int inputBlockSize; | ||||
| }; | ||||
							
								
								
									
										8
									
								
								core/src/signal_path/signal_path.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								core/src/signal_path/signal_path.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #include <signal_path/signal_path.h> | ||||
|  | ||||
| namespace sigpath { | ||||
|     SignalPath signalPath; | ||||
|     VFOManager vfoManager; | ||||
|     SourceManager sourceManager; | ||||
|     SinkManager sinkManager; | ||||
| }; | ||||
							
								
								
									
										13
									
								
								core/src/signal_path/signal_path.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								core/src/signal_path/signal_path.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| #pragma once | ||||
| #include <signal_path/dsp.h> | ||||
| #include <signal_path/vfo_manager.h> | ||||
| #include <signal_path/source.h> | ||||
| #include <signal_path/sink.h> | ||||
| #include <new_module.h> | ||||
|  | ||||
| namespace sigpath { | ||||
|     SDRPP_EXPORT SignalPath signalPath; | ||||
|     SDRPP_EXPORT VFOManager vfoManager; | ||||
|     SDRPP_EXPORT SourceManager sourceManager; | ||||
|     SDRPP_EXPORT SinkManager sinkManager; | ||||
| }; | ||||
							
								
								
									
										313
									
								
								core/src/signal_path/sink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								core/src/signal_path/sink.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,313 @@ | ||||
| #include <signal_path/sink.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <imgui/imgui.h> | ||||
| #include <gui/style.h> | ||||
| #include <gui/icons.h> | ||||
| #include <core.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SinkManager::SinkManager() { | ||||
|     SinkManager::SinkProvider prov; | ||||
|     prov.create = SinkManager::NullSink::create; | ||||
|     registerSinkProvider("None", prov); | ||||
| } | ||||
|  | ||||
| SinkManager::Stream::Stream(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate) { | ||||
|     init(in, srChangeHandler, sampleRate); | ||||
| } | ||||
|  | ||||
| void SinkManager::Stream::init(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate) { | ||||
|     _in = in; | ||||
|     srChange.bindHandler(srChangeHandler); | ||||
|     _sampleRate = sampleRate; | ||||
|     splitter.init(_in); | ||||
|     splitter.bindStream(&volumeInput); | ||||
|     volumeAjust.init(&volumeInput, 1.0f); | ||||
|     sinkOut = &volumeAjust.out; | ||||
| } | ||||
|  | ||||
| void SinkManager::Stream::start() { | ||||
|     if (running) { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     splitter.start(); | ||||
|     volumeAjust.start(); | ||||
|     sink->start(); | ||||
|     running = true; | ||||
| } | ||||
|  | ||||
| void SinkManager::Stream::stop() { | ||||
|     if (!running) { | ||||
|         return; | ||||
|     } | ||||
|     splitter.stop(); | ||||
|     volumeAjust.stop(); | ||||
|     sink->stop(); | ||||
|     running = false; | ||||
| } | ||||
|  | ||||
| void SinkManager::Stream::setVolume(float volume) { | ||||
|     guiVolume = volume; | ||||
|     volumeAjust.setVolume(volume); | ||||
| } | ||||
|  | ||||
| float SinkManager::Stream::getVolume() { | ||||
|     return guiVolume; | ||||
| } | ||||
|  | ||||
| float SinkManager::Stream::getSampleRate() { | ||||
|     return _sampleRate; | ||||
| } | ||||
|  | ||||
| void SinkManager::Stream::setInput(dsp::stream<dsp::stereo_t>* in) { | ||||
|     std::lock_guard<std::mutex> lck(ctrlMtx); | ||||
|     _in = in; | ||||
|     splitter.setInput(_in); | ||||
| } | ||||
|  | ||||
| dsp::stream<dsp::stereo_t>* SinkManager::Stream::bindStream() { | ||||
|     dsp::stream<dsp::stereo_t>* stream = new dsp::stream<dsp::stereo_t>; | ||||
|     splitter.bindStream(stream); | ||||
|     return stream; | ||||
| } | ||||
|  | ||||
| void SinkManager::Stream::unbindStream(dsp::stream<dsp::stereo_t>* stream) { | ||||
|     splitter.unbindStream(stream); | ||||
|     delete stream; | ||||
| } | ||||
|  | ||||
| void SinkManager::Stream::setSampleRate(float sampleRate) { | ||||
|     std::lock_guard<std::mutex> lck(ctrlMtx); | ||||
|     _sampleRate = sampleRate; | ||||
|     srChange.emit(sampleRate); | ||||
| } | ||||
|  | ||||
| void SinkManager::registerSinkProvider(std::string name, SinkProvider provider) { | ||||
|     if (providers.find(name) != providers.end()) { | ||||
|         spdlog::error("Cannot create sink provider '{0}', this name is already taken", name); | ||||
|         return; | ||||
|     } | ||||
|     providers[name] = provider; | ||||
|     providerNames.push_back(name); | ||||
| } | ||||
|  | ||||
| void SinkManager::registerStream(std::string name, SinkManager::Stream* stream) { | ||||
|     if (streams.find(name) != streams.end()) { | ||||
|         spdlog::error("Cannot register stream '{0}', this name is already taken", name); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     SinkManager::SinkProvider provider; | ||||
|  | ||||
|     provider = providers["None"]; | ||||
|  | ||||
|     stream->sink = provider.create(stream, name, provider.ctx); | ||||
|  | ||||
|     streams[name] = stream; | ||||
|     streamNames.push_back(name); | ||||
| } | ||||
|  | ||||
| void SinkManager::unregisterStream(std::string name) { | ||||
|     if (streams.find(name) == streams.end()) { | ||||
|         spdlog::error("Cannot unregister stream '{0}', this stream doesn't exist", name); | ||||
|         return; | ||||
|     } | ||||
|     spdlog::error("unregisterStream NOT IMPLEMENTED!!!!!!!"); | ||||
|     SinkManager::Stream* stream = streams[name]; | ||||
|     delete stream->sink; | ||||
|     delete stream; | ||||
| } | ||||
|  | ||||
| void SinkManager::startStream(std::string name) { | ||||
|     if (streams.find(name) == streams.end()) { | ||||
|         spdlog::error("Cannot start stream '{0}', this stream doesn't exist", name); | ||||
|         return; | ||||
|     } | ||||
|     streams[name]->start(); | ||||
| } | ||||
|  | ||||
| void SinkManager::stopStream(std::string name) { | ||||
|     if (streams.find(name) == streams.end()) { | ||||
|         spdlog::error("Cannot stop stream '{0}', this stream doesn't exist", name); | ||||
|         return; | ||||
|     } | ||||
|     streams[name]->stop(); | ||||
| } | ||||
|  | ||||
| float SinkManager::getStreamSampleRate(std::string name) { | ||||
|     if (streams.find(name) == streams.end()) { | ||||
|         spdlog::error("Cannot get sample rate of stream '{0}', this stream doesn't exist", name); | ||||
|         return -1.0f; | ||||
|     } | ||||
|     return streams[name]->getSampleRate(); | ||||
| } | ||||
|  | ||||
| dsp::stream<dsp::stereo_t>* SinkManager::bindStream(std::string name) { | ||||
|     if (streams.find(name) == streams.end()) { | ||||
|         spdlog::error("Cannot bind to stream '{0}'. Stream doesn't exist", name); | ||||
|         return NULL; | ||||
|     } | ||||
|     return streams[name]->bindStream(); | ||||
| } | ||||
|  | ||||
| void SinkManager::unbindStream(std::string name, dsp::stream<dsp::stereo_t>* stream) { | ||||
|     if (streams.find(name) == streams.end()) { | ||||
|         spdlog::error("Cannot unbind from stream '{0}'. Stream doesn't exist", name); | ||||
|         return; | ||||
|     } | ||||
|     streams[name]->unbindStream(stream); | ||||
| } | ||||
|  | ||||
| void SinkManager::setStreamSink(std::string name, std::string providerName) { | ||||
|     spdlog::warn("setStreamSink is NOT implemented!!!"); | ||||
| } | ||||
|  | ||||
| void SinkManager::showVolumeSlider(std::string name, std::string prefix, float width, float btnHeight, int btwBorder, bool sameLine) { | ||||
|     // TODO: Replace map with some hashmap for it to be faster | ||||
|     float height = ImGui::GetTextLineHeightWithSpacing() + 2; | ||||
|     float sliderHeight = height; | ||||
|     if (btnHeight > 0) { | ||||
|         height = btnHeight; | ||||
|     } | ||||
|  | ||||
|     float ypos = ImGui::GetCursorPosY(); | ||||
|  | ||||
|     if (streams.find(name) == streams.end() || name == "") { | ||||
|         float dummy = 0.0f; | ||||
|         style::beginDisabled(); | ||||
|         ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + name).c_str())); | ||||
|         ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), btwBorder); | ||||
|         ImGui::PopID(); | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetNextItemWidth(width - height - 8); | ||||
|         ImGui::SetCursorPosY(ypos + ((height - sliderHeight) / 2.0f) + btwBorder); | ||||
|         ImGui::SliderFloat((prefix + name).c_str(), &dummy, 0.0f, 1.0f, ""); | ||||
|         style::endDisabled(); | ||||
|         if (sameLine) { ImGui::SetCursorPosY(ypos); } | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     SinkManager::Stream* stream = streams[name]; | ||||
|  | ||||
|     if (stream->volumeAjust.getMuted()) { | ||||
|         ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + name).c_str())); | ||||
|         if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), btwBorder)) { | ||||
|             stream->volumeAjust.setMuted(false); | ||||
|             core::configManager.aquire(); | ||||
|             saveStreamConfig(name); | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::PopID(); | ||||
|     } | ||||
|     else { | ||||
|         ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + name).c_str())); | ||||
|         if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), btwBorder)) { | ||||
|             stream->volumeAjust.setMuted(true); | ||||
|             core::configManager.aquire(); | ||||
|             saveStreamConfig(name); | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::PopID(); | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|  | ||||
|     ImGui::SetNextItemWidth(width - height - 8); | ||||
|     ImGui::SetCursorPosY(ypos + ((height - sliderHeight) / 2.0f) + btwBorder); | ||||
|     if (ImGui::SliderFloat((prefix + name).c_str(), &stream->guiVolume, 0.0f, 1.0f, "")) { | ||||
|         stream->setVolume(stream->guiVolume); | ||||
|         core::configManager.aquire(); | ||||
|         saveStreamConfig(name); | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|     if (sameLine) { ImGui::SetCursorPosY(ypos); } | ||||
|     //ImGui::SetCursorPosY(ypos); | ||||
| } | ||||
|  | ||||
| void SinkManager::loadStreamConfig(std::string name) { | ||||
|     json conf = core::configManager.conf["streams"][name]; | ||||
|     SinkManager::Stream* stream = streams[name]; | ||||
|     std::string provName = conf["sink"]; | ||||
|     if (providers.find(provName) == providers.end()) { | ||||
|         provName = providerNames[0]; | ||||
|     } | ||||
|     if (stream->running) { | ||||
|         stream->sink->stop(); | ||||
|     } | ||||
|     delete stream->sink; | ||||
|     SinkManager::SinkProvider prov = providers[provName]; | ||||
|     stream->providerId = std::distance(providerNames.begin(), std::find(providerNames.begin(), providerNames.end(), provName)); | ||||
|     stream->sink = prov.create(stream, name, prov.ctx); | ||||
|     if (stream->running) { | ||||
|         stream->sink->start(); | ||||
|     } | ||||
|     stream->setVolume(conf["volume"]); | ||||
|     stream->volumeAjust.setMuted(conf["muted"]); | ||||
| } | ||||
|  | ||||
| void SinkManager::saveStreamConfig(std::string name) { | ||||
|     SinkManager::Stream* stream = streams[name]; | ||||
|     json conf; | ||||
|     conf["sink"] = providerNames[stream->providerId]; | ||||
|     conf["volume"] = stream->getVolume(); | ||||
|     conf["muted"] = stream->volumeAjust.getMuted(); | ||||
|     core::configManager.conf["streams"][name] = conf; | ||||
| } | ||||
|  | ||||
| // Note: aquire and release config before running this | ||||
| void SinkManager::loadSinksFromConfig() { | ||||
|     for (auto const& [name, stream] : streams) { | ||||
|         if (!core::configManager.conf["streams"].contains(name)) { continue; } | ||||
|         loadStreamConfig(name); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void SinkManager::showMenu() { | ||||
|     float menuWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|     int count = 0; | ||||
|     int maxCount = streams.size(); | ||||
|      | ||||
|     std::string provStr = ""; | ||||
|     for (auto const& name : providerNames) { | ||||
|         provStr += name; | ||||
|         provStr += '\0'; | ||||
|     } | ||||
|  | ||||
|     for (auto const& [name, stream] : streams) { | ||||
|         ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f)); | ||||
|         ImGui::Text("%s", name.c_str()); | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(CONCAT("##_sdrpp_sink_select_", name), &stream->providerId, provStr.c_str())) { | ||||
|             if (stream->running) { | ||||
|                 stream->sink->stop(); | ||||
|             } | ||||
|             delete stream->sink; | ||||
|             SinkManager::SinkProvider prov = providers[providerNames[stream->providerId]]; | ||||
|             stream->sink = prov.create(stream, name, prov.ctx); | ||||
|             if (stream->running) { | ||||
|                 stream->sink->start(); | ||||
|             } | ||||
|             core::configManager.aquire(); | ||||
|             saveStreamConfig(name); | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         stream->sink->menuHandler(); | ||||
|  | ||||
|         showVolumeSlider(name, "##_sdrpp_sink_menu_vol_", menuWidth); | ||||
|  | ||||
|         count++; | ||||
|         if (count < maxCount) { | ||||
|             ImGui::Spacing(); | ||||
|             ImGui::Separator(); | ||||
|         } | ||||
|         ImGui::Spacing(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::vector<std::string> SinkManager::getStreamNames() { | ||||
|     return streamNames; | ||||
| } | ||||
							
								
								
									
										121
									
								
								core/src/signal_path/sink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								core/src/signal_path/sink.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| #pragma once | ||||
| #include <map> | ||||
| #include <string> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/types.h> | ||||
| #include <dsp/routing.h> | ||||
| #include <dsp/processing.h> | ||||
| #include <dsp/sink.h> | ||||
| #include <mutex> | ||||
| #include <event.h> | ||||
| #include <vector> | ||||
|  | ||||
| class SinkManager { | ||||
| public: | ||||
|     SinkManager(); | ||||
|  | ||||
|     class Sink { | ||||
|     public: | ||||
|         virtual void start() = 0; | ||||
|         virtual void stop() = 0; | ||||
|         virtual void menuHandler() = 0; | ||||
|     }; | ||||
|  | ||||
|     class Stream { | ||||
|     public: | ||||
|         Stream() {} | ||||
|         Stream(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate); | ||||
|  | ||||
|         void init(dsp::stream<dsp::stereo_t>* in, const Event<float>::EventHandler& srChangeHandler, float sampleRate); | ||||
|  | ||||
|         void start(); | ||||
|         void stop(); | ||||
|  | ||||
|         void setVolume(float volume); | ||||
|         float getVolume(); | ||||
|  | ||||
|         void setSampleRate(float sampleRate); | ||||
|         float getSampleRate(); | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::stereo_t>* in); | ||||
|  | ||||
|         dsp::stream<dsp::stereo_t>* bindStream(); | ||||
|         void unbindStream(dsp::stream<dsp::stereo_t>* stream); | ||||
|  | ||||
|         friend SinkManager; | ||||
|         friend SinkManager::Sink; | ||||
|  | ||||
|         dsp::stream<dsp::stereo_t>* sinkOut; | ||||
|  | ||||
|         Event<float> srChange;         | ||||
|  | ||||
|     private: | ||||
|         dsp::stream<dsp::stereo_t>* _in; | ||||
|         dsp::Splitter<dsp::stereo_t> splitter; | ||||
|         SinkManager::Sink* sink; | ||||
|         dsp::stream<dsp::stereo_t> volumeInput; | ||||
|         dsp::Volume<dsp::stereo_t> volumeAjust; | ||||
|         std::mutex ctrlMtx; | ||||
|         float _sampleRate; | ||||
|         int providerId = 0; | ||||
|         bool running = false; | ||||
|  | ||||
|         float guiVolume = 1.0f; | ||||
|     }; | ||||
|  | ||||
|     struct SinkProvider { | ||||
|         SinkManager::Sink* (*create)(SinkManager::Stream* stream, std::string streamName, void* ctx); | ||||
|         void* ctx; | ||||
|     }; | ||||
|  | ||||
|     class NullSink : SinkManager::Sink { | ||||
|     public: | ||||
|         NullSink(SinkManager::Stream* stream) { | ||||
|             ns.init(stream->sinkOut); | ||||
|         } | ||||
|         void start() { ns.start(); } | ||||
|         void stop() { ns.stop(); } | ||||
|         void menuHandler() {} | ||||
|  | ||||
|         static SinkManager::Sink* create(SinkManager::Stream* stream, std::string streamName, void* ctx) { | ||||
|             stream->srChange.emit(48000); | ||||
|             return new SinkManager::NullSink(stream); | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         dsp::NullSink<dsp::stereo_t> ns; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     void registerSinkProvider(std::string name, SinkProvider provider); | ||||
|  | ||||
|     void registerStream(std::string name, Stream* stream); | ||||
|     void unregisterStream(std::string name); | ||||
|  | ||||
|     void startStream(std::string name); | ||||
|     void stopStream(std::string name); | ||||
|  | ||||
|     float getStreamSampleRate(std::string name); | ||||
|  | ||||
|     void setStreamSink(std::string name, std::string providerName); | ||||
|  | ||||
|     void showVolumeSlider(std::string name, std::string prefix, float width, float btnHeight = -1.0f, int btwBorder = 0, bool sameLine = false); | ||||
|  | ||||
|     dsp::stream<dsp::stereo_t>* bindStream(std::string name); | ||||
|     void unbindStream(std::string name, dsp::stream<dsp::stereo_t>* stream); | ||||
|  | ||||
|     void loadSinksFromConfig(); | ||||
|     void showMenu(); | ||||
|  | ||||
|     std::vector<std::string> getStreamNames(); | ||||
|  | ||||
| private: | ||||
|     void loadStreamConfig(std::string name); | ||||
|     void saveStreamConfig(std::string name); | ||||
|  | ||||
|     std::map<std::string, SinkProvider> providers; | ||||
|     std::map<std::string, Stream*> streams; | ||||
|     std::vector<std::string> providerNames; | ||||
|     std::vector<std::string> streamNames; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										64
									
								
								core/src/signal_path/source.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								core/src/signal_path/source.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #include <signal_path/source.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <signal_path/signal_path.h> | ||||
|  | ||||
| SourceManager::SourceManager() { | ||||
|  | ||||
| } | ||||
|  | ||||
| void SourceManager::registerSource(std::string name, SourceHandler* handler) { | ||||
|     if (sources.find(name) != sources.end()) { | ||||
|         spdlog::error("Tried to register new source with existing name: {0}", name); | ||||
|         return; | ||||
|     } | ||||
|     sources[name] = handler; | ||||
|     sourceNames.push_back(name); | ||||
| } | ||||
|  | ||||
| void SourceManager::selectSource(std::string  name) { | ||||
|     if (sources.find(name) == sources.end()) { | ||||
|         spdlog::error("Tried to select non existant source: {0}", name); | ||||
|         return; | ||||
|     } | ||||
|     if (selectedName != "") { | ||||
|         sources[selectedName]->deselectHandler(sources[selectedName]->ctx); | ||||
|     } | ||||
|     selectedHandler = sources[name]; | ||||
|     selectedHandler->selectHandler(selectedHandler->ctx); | ||||
|     selectedName = name; | ||||
|     sigpath::signalPath.setInput(selectedHandler->stream); | ||||
| } | ||||
|  | ||||
| void SourceManager::showSelectedMenu() { | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     selectedHandler->menuHandler(selectedHandler->ctx); | ||||
| } | ||||
|  | ||||
| void SourceManager::start() { | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     selectedHandler->startHandler(selectedHandler->ctx); | ||||
| } | ||||
|  | ||||
| void SourceManager::stop() { | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     selectedHandler->stopHandler(selectedHandler->ctx); | ||||
| } | ||||
|  | ||||
| void SourceManager::tune(double freq) { | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     selectedHandler->tuneHandler(freq + tuneOffset, selectedHandler->ctx); | ||||
|     currentFreq = freq; | ||||
| } | ||||
|  | ||||
| void SourceManager::setTuningOffset(double offset) { | ||||
|     tuneOffset = offset; | ||||
|     tune(currentFreq); | ||||
| } | ||||
							
								
								
									
										40
									
								
								core/src/signal_path/source.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								core/src/signal_path/source.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/types.h> | ||||
|  | ||||
| class SourceManager { | ||||
| public: | ||||
|     SourceManager(); | ||||
|  | ||||
|     struct SourceHandler { | ||||
|         dsp::stream<dsp::complex_t>* stream; | ||||
|         void (*menuHandler)(void* ctx); | ||||
|         void (*selectHandler)(void* ctx); | ||||
|         void (*deselectHandler)(void* ctx); | ||||
|         void (*startHandler)(void* ctx); | ||||
|         void (*stopHandler)(void* ctx); | ||||
|         void (*tuneHandler)(double freq, void* ctx); | ||||
|         void* ctx; | ||||
|     }; | ||||
|  | ||||
|     void registerSource(std::string name, SourceHandler* handler); | ||||
|     void selectSource(std::string  name); | ||||
|     void showSelectedMenu(); | ||||
|     void start(); | ||||
|     void stop(); | ||||
|     void tune(double freq); | ||||
|     void setTuningOffset(double offset); | ||||
|  | ||||
|     std::vector<std::string> sourceNames; | ||||
|  | ||||
| private: | ||||
|     std::map<std::string, SourceHandler*> sources; | ||||
|     std::string selectedName; | ||||
|     SourceHandler* selectedHandler = NULL; | ||||
|     double tuneOffset; | ||||
|     double currentFreq; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										136
									
								
								core/src/signal_path/vfo_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								core/src/signal_path/vfo_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| #include <signal_path/vfo_manager.h> | ||||
| #include <signal_path/signal_path.h> | ||||
|  | ||||
| VFOManager::VFO::VFO(std::string name, int reference, double offset, double bandwidth, double sampleRate, int blockSize) { | ||||
|     this->name = name; | ||||
|     dspVFO = sigpath::signalPath.addVFO(name, sampleRate, bandwidth, offset); | ||||
|     wtfVFO = new ImGui::WaterfallVFO; | ||||
|     wtfVFO->setReference(reference); | ||||
|     wtfVFO->setBandwidth(bandwidth); | ||||
|     wtfVFO->setOffset(offset); | ||||
|     output = dspVFO->out; | ||||
|     gui::waterfall.vfos[name] = wtfVFO; | ||||
| } | ||||
|  | ||||
| VFOManager::VFO::~VFO() { | ||||
|     dspVFO->stop(); | ||||
|     gui::waterfall.vfos.erase(name); | ||||
|     if (gui::waterfall.selectedVFO == name) { | ||||
|         gui::waterfall.selectFirstVFO(); | ||||
|     } | ||||
|     sigpath::signalPath.removeVFO(name); | ||||
|     delete wtfVFO; | ||||
| } | ||||
|  | ||||
| void VFOManager::VFO::setOffset(double offset) { | ||||
|     wtfVFO->setOffset(offset); | ||||
|     dspVFO->setOffset(wtfVFO->centerOffset); | ||||
| } | ||||
|  | ||||
| void VFOManager::VFO::setCenterOffset(double offset) { | ||||
|     wtfVFO->setCenterOffset(offset); | ||||
|     dspVFO->setOffset(offset); | ||||
| } | ||||
|  | ||||
| void VFOManager::VFO::setBandwidth(double bandwidth) { | ||||
|     wtfVFO->setBandwidth(bandwidth); | ||||
|     dspVFO->setBandwidth(bandwidth); | ||||
| } | ||||
|  | ||||
| void VFOManager::VFO::setSampleRate(double sampleRate, double bandwidth) { | ||||
|     dspVFO->setOutSampleRate(sampleRate, bandwidth); | ||||
|     wtfVFO->setBandwidth(bandwidth); | ||||
| } | ||||
|  | ||||
| void VFOManager::VFO::setReference(int ref) { | ||||
|     wtfVFO->setReference(ref); | ||||
| } | ||||
|  | ||||
| int VFOManager::VFO::getOutputBlockSize() { | ||||
|     // NOTE: This shouldn't be needed anymore | ||||
|     return 1; //dspVFO->getOutputBlockSize(); | ||||
| } | ||||
|  | ||||
| void VFOManager::VFO::setSnapInterval(double interval) { | ||||
|     wtfVFO->setSnapInterval(interval); | ||||
| } | ||||
|  | ||||
|  | ||||
| VFOManager::VFOManager() { | ||||
|      | ||||
| } | ||||
|  | ||||
| VFOManager::VFO* VFOManager::createVFO(std::string name, int reference, double offset, double bandwidth, double sampleRate, int blockSize) { | ||||
|     if (vfos.find(name) != vfos.end() || name == "") { | ||||
|         return NULL; | ||||
|     } | ||||
|     VFOManager::VFO* vfo = new VFO(name, reference, offset, bandwidth, sampleRate, blockSize); | ||||
|     vfos[name] = vfo; | ||||
|     return vfo; | ||||
| } | ||||
|  | ||||
| void VFOManager::deleteVFO(VFOManager::VFO* vfo) { | ||||
|     std::string name = ""; | ||||
|     for (auto const& [_name, _vfo] : vfos) { | ||||
|         if (_vfo == vfo) { | ||||
|             name = _name; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     if (name == "") { | ||||
|         return; | ||||
|     } | ||||
|     vfos.erase(name); | ||||
|     delete vfo; | ||||
| } | ||||
|  | ||||
| void VFOManager::setOffset(std::string name, double offset) { | ||||
|     if (vfos.find(name) == vfos.end()) { | ||||
|         return; | ||||
|     } | ||||
|     vfos[name]->setOffset(offset); | ||||
| } | ||||
|  | ||||
| void VFOManager::setCenterOffset(std::string name, double offset) { | ||||
|     if (vfos.find(name) == vfos.end()) { | ||||
|         return; | ||||
|     } | ||||
|     vfos[name]->setCenterOffset(offset); | ||||
| } | ||||
|  | ||||
| void VFOManager::setBandwidth(std::string name, double bandwidth) { | ||||
|     if (vfos.find(name) == vfos.end()) { | ||||
|         return; | ||||
|     } | ||||
|     vfos[name]->setBandwidth(bandwidth); | ||||
| } | ||||
|  | ||||
| void VFOManager::setSampleRate(std::string name, double sampleRate, double bandwidth) { | ||||
|     if (vfos.find(name) == vfos.end()) { | ||||
|         return; | ||||
|     } | ||||
|     vfos[name]->setSampleRate(sampleRate, bandwidth); | ||||
| } | ||||
|  | ||||
| void VFOManager::setReference(std::string name, int ref) { | ||||
|     if (vfos.find(name) == vfos.end()) { | ||||
|         return; | ||||
|     } | ||||
|     vfos[name]->setReference(ref); | ||||
| } | ||||
|  | ||||
| int VFOManager::getOutputBlockSize(std::string name) { | ||||
|     if (vfos.find(name) == vfos.end()) { | ||||
|         return -1; | ||||
|     } | ||||
|     return vfos[name]->getOutputBlockSize(); | ||||
| } | ||||
|  | ||||
| void VFOManager::updateFromWaterfall(ImGui::WaterFall* wtf) { | ||||
|     for (auto const& [name, vfo] : vfos) { | ||||
|         if (vfo->wtfVFO->centerOffsetChanged) { | ||||
|             vfo->wtfVFO->centerOffsetChanged = false; | ||||
|             vfo->dspVFO->setOffset(vfo->wtfVFO->centerOffset); | ||||
|         } | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user