mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			275 Commits
		
	
	
		
			0.2.1_alph
			...
			0.2.5_beta
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a2d93915e8 | ||
|  | 29e9db184f | ||
|  | 2f93c7ae58 | ||
|  | 4abfe407da | ||
|  | 9b27e81091 | ||
|  | 39787743fd | ||
|  | 22e47807b8 | ||
|  | 898525a6d8 | ||
|  | 1ebcfe7d80 | ||
|  | 1dbc39b970 | ||
|  | 80f5f6c288 | ||
|  | b18acd469f | ||
|  | cefcd18269 | ||
|  | 4de3ac176d | ||
|  | afd5699ff1 | ||
|  | b79461e3ce | ||
|  | de6ab8ecdf | ||
|  | d0180d42a8 | ||
|  | 2e504b40f6 | ||
|  | 9b00304c29 | ||
|  | 979928ded8 | ||
|  | 7c4e442432 | ||
|  | 0dd445f101 | ||
|  | f217804838 | ||
|  | 9a630fff06 | ||
|  | db508214d7 | ||
|  | 8e764f48ae | ||
|  | 2583063f5f | ||
|  | dd4ec22b39 | ||
|  | 42dbcec93f | ||
|  | 9bbf634f5d | ||
|  | d6b09759de | ||
|  | 5bb8a943ad | ||
|  | b370eda0d5 | ||
|  | 69bcbf6f27 | ||
|  | 04823abb83 | ||
|  | 7269a0ea12 | ||
|  | 153b58fbbd | ||
|  | 149af55e61 | ||
|  | 9cac95fd82 | ||
|  | 09498f3b18 | ||
|  | bb919d0f32 | ||
|  | 6d0abd73a5 | ||
|  | 717f2a822b | ||
|  | 8946b4b4b6 | ||
|  | db279d2b36 | ||
|  | bb7965b3c4 | ||
|  | a33fe5a4cc | ||
|  | 4a03f0870c | ||
|  | bfe15aff19 | ||
|  | 42bc2d01f7 | ||
|  | 0cb9fc0df8 | ||
|  | 450896b122 | ||
|  | cc0b89dbe2 | ||
|  | c887b96a77 | ||
|  | 22541ae0f4 | ||
|  | d83da38d79 | ||
|  | b21f8abbd6 | ||
|  | e9aade4d0d | ||
|  | 2bf2fff3d6 | ||
|  | 463a22fdfb | ||
|  | 3175022b31 | ||
|  | 504d910226 | ||
|  | c2769e1a72 | ||
|  | 7577253dbf | ||
|  | e4c5b2dbd1 | ||
|  | fafd76ff94 | ||
|  | e354d11820 | ||
|  | a3374c7eca | ||
|  | 552b886cea | ||
|  | ff9a19381b | ||
|  | 77aacc2e5d | ||
|  | 6a1fa2c13b | ||
|  | c3bb64bf6e | ||
|  | da68ab4ed0 | ||
|  | 1aedf92bcd | ||
|  | abcf484506 | ||
|  | a93681a980 | ||
|  | a08758ea54 | ||
|  | 0a0f5b8e8c | ||
|  | 84f67a3ac1 | ||
|  | 22d18a9e58 | ||
|  | d1a8425d43 | ||
|  | 65d94f03e4 | ||
|  | 3a49041f27 | ||
|  | eec2f7c4a0 | ||
|  | 720df5ce89 | ||
|  | d7cea16d4a | ||
|  | d5c0fdd525 | ||
|  | 4a86d6073c | ||
|  | 98b6e580b4 | ||
|  | c96c69c112 | ||
|  | bd545feb2c | ||
|  | e90b6656c3 | ||
|  | 5099c16a12 | ||
|  | d9dcfa4a88 | ||
|  | 1fcd783dd9 | ||
|  | 2c84123158 | ||
|  | c3d39029b8 | ||
|  | 20b703f8bf | ||
|  | 89c579880c | ||
|  | db389372ad | ||
|  | 3a6eaf6526 | ||
|  | 3e27af472b | ||
|  | 7bea6058fe | ||
|  | b02b6c30b5 | ||
|  | 46e9266752 | ||
|  | e3db19b16a | ||
|  | 774663d70d | ||
|  | 2c729bf646 | ||
|  | 9b1c9e9e29 | ||
|  | 80badebb37 | ||
|  | fc9e155481 | ||
|  | 16d8a31c12 | ||
|  | 7ba6081cb3 | ||
|  | c3a8865dd3 | ||
|  | 929ca50b06 | ||
|  | e5123dd8bf | ||
|  | fe1de4bed9 | ||
|  | c612620ca5 | ||
|  | ca9d2c01af | ||
|  | 51d90c1898 | ||
|  | a6a4193fbb | ||
|  | f4f8c77ffa | ||
|  | 9b8c1a3072 | ||
|  | 92b77904f6 | ||
|  | 9805e4a395 | ||
|  | 6a01c9d426 | ||
|  | 48df92c8a5 | ||
|  | 5bb2f9bf05 | ||
|  | e5dbac4345 | ||
|  | 618d4ac4cc | ||
|  | 19e516f206 | ||
|  | afadb71d64 | ||
|  | b3d1eabbad | ||
|  | eac0a7a13f | ||
|  | e06ed84330 | ||
|  | 4eae7c3ba5 | ||
|  | 61a612cf30 | ||
|  | f1084157a3 | ||
|  | de3b056133 | ||
|  | 02ae50905d | ||
|  | 0a5fd5c271 | ||
|  | ef968ac1fb | ||
|  | 3156236745 | ||
|  | 5d320fdd53 | ||
|  | cee6af1e14 | ||
|  | 35c7f0e3cf | ||
|  | fc9bc496cb | ||
|  | 75f8a45119 | ||
|  | 50a73a380d | ||
|  | e62042d26a | ||
|  | c109de3949 | ||
|  | 39c87782db | ||
|  | b6566dde14 | ||
|  | ef36283370 | ||
|  | 922a226028 | ||
|  | ba81f25933 | ||
|  | da9528576a | ||
|  | 3fdd2477e5 | ||
|  | 62368e35a7 | ||
|  | 82d3431f1d | ||
|  | edbc0c149d | ||
|  | b8987e6d2d | ||
|  | 6296b8865b | ||
|  | 2df185e340 | ||
|  | 6262c64daa | ||
|  | 0fe5af9816 | ||
|  | e94888d533 | ||
|  | 6130428989 | ||
|  | 5400a4e18a | ||
|  | 0d45217dfd | ||
|  | fa1e647235 | ||
|  | d637cb9e75 | ||
|  | 406f18bf11 | ||
|  | 72611b5fa7 | ||
|  | 313b786d88 | ||
|  | 801a56787f | ||
|  | 62868b2533 | ||
|  | 4bf88739b5 | ||
|  | 087380c966 | ||
|  | fbd7321b48 | ||
|  | f6cfe83d45 | ||
|  | 71f6be8d08 | ||
|  | 6e5450ed24 | ||
|  | 027054b57e | ||
|  | 3b6a3ff94d | ||
|  | 46d5b8a750 | ||
|  | ac068036b8 | ||
|  | ace1fe1e5e | ||
|  | 14a8e81662 | ||
|  | eff7bbdd5a | ||
|  | 60342de9c0 | ||
|  | 47b04ffef4 | ||
|  | 1507e6ec31 | ||
|  | 524f20bc2f | ||
|  | 2c4d7cbf09 | ||
|  | 5fedda08d7 | ||
|  | 2056eae139 | ||
|  | 48a8b04eaa | ||
|  | 51ee02f9da | ||
|  | ab8ce4c53f | ||
|  | 2aaf254565 | ||
|  | 109696c65a | ||
|  | 35ef99c6e9 | ||
|  | 91d382ca0c | ||
|  | ec234e99a1 | ||
|  | 9de585190f | ||
|  | d6b9e1d86a | ||
|  | 1ef31f0f8b | ||
|  | c1052b1b28 | ||
|  | e497122c06 | ||
|  | 407fcaadc6 | ||
|  | 7e6f24d203 | ||
|  | c0825dbeeb | ||
|  | 39a65b51fb | ||
|  | acf3fe0297 | ||
|  | 70c2ef36f5 | ||
|  | 7190acfe9e | ||
|  | bf6210721d | ||
|  | 27731f376a | ||
|  | d82260d4d4 | ||
|  | 78086a79f4 | ||
|  | aa2caa67ad | ||
|  | 709627a738 | ||
|  | 649be86cb6 | ||
|  | b56aab8f74 | ||
|  | 53ec38766a | ||
|  | dbe811b47a | ||
|  | f08515420e | ||
|  | 11913ab683 | ||
|  | 1cecc78c0c | ||
|  | 6717c43fc2 | ||
|  | e44d20bdbc | ||
|  | e50ed1b960 | ||
|  | cff5987329 | ||
|  | b2191c5d2c | ||
|  | 03dc5d2042 | ||
|  | 19e07eb767 | ||
|  | c4f49203a1 | ||
|  | 9830337103 | ||
|  | eadaf3ce6b | ||
|  | 31a95031e4 | ||
|  | cdea80f8c5 | ||
|  | b65bddc1b3 | ||
|  | 7759de96da | ||
|  | 9d2b60b88e | ||
|  | a9c82ecc81 | ||
|  | 3aa8ce80db | ||
|  | 294337aa63 | ||
|  | 908abf0743 | ||
|  | 509e4aa7f7 | ||
|  | 13fbd6a800 | ||
|  | b430f88245 | ||
|  | e24e3cbc2a | ||
|  | 08292c279e | ||
|  | 10f1b992dd | ||
|  | 022898c61d | ||
|  | cd7e5cf1bc | ||
|  | f5d6e609d7 | ||
|  | ed49ad6f7f | ||
|  | f8c9aa1be4 | ||
|  | e364ebac6d | ||
|  | 1a42421d92 | ||
|  | e8ae20232a | ||
|  | 3216daec2b | ||
|  | 0dc14c663a | ||
|  | 07d3fa2034 | ||
|  | f1fd6fce7a | ||
|  | 355a348ba0 | ||
|  | d85fed00a0 | ||
|  | d47679c5cf | ||
|  | 379d6f8101 | ||
|  | 43f6802199 | ||
|  | 6bfe15b14e | 
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # These are supported funding model platforms | ||||
|  | ||||
| patreon: ryzerth | ||||
							
								
								
									
										45
									
								
								.github/workflows/cmake.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								.github/workflows/cmake.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| name: Linux Build | ||||
|  | ||||
| on: [push] | ||||
|  | ||||
| env: | ||||
|   # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) | ||||
|   BUILD_TYPE: Release | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|     # The CMake configure and build commands are platform agnostic and should work equally | ||||
|     # well on Windows or Mac.  You can convert this to a matrix build if you need | ||||
|     # cross-platform coverage. | ||||
|     # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix | ||||
|     runs-on: ubuntu-20.04 | ||||
|  | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|      | ||||
|     - name: Update repositories | ||||
|       run: sudo apt update  | ||||
|      | ||||
|     - name: Install dependencies | ||||
|       run: sudo apt install libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev portaudio19-dev libhackrf-dev | ||||
|  | ||||
|     - name: Create Build Environment | ||||
|       # Some projects don't allow in-source building, so create a separate build directory | ||||
|       # We'll use this as our working directory for all subsequent commands | ||||
|       run: cmake -E make_directory ${{runner.workspace}}/build | ||||
|  | ||||
|     - name: Configure CMake | ||||
|       # Use a bash shell so we can use the same syntax for environment variable | ||||
|       # access regardless of the host operating system | ||||
|       shell: bash | ||||
|       working-directory: ${{runner.workspace}}/build | ||||
|       # Note the current convention is to use the -S and -B options here to specify source  | ||||
|       # and build directories, but this is only available with CMake 3.13 and higher.   | ||||
|       # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 | ||||
|       run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE | ||||
|  | ||||
|     - name: Build | ||||
|       working-directory: ${{runner.workspace}}/build | ||||
|       shell: bash | ||||
|       # Execute the build.  You can specify a specific target with "--target <NAME>" | ||||
|       run: cmake --build . --config $BUILD_TYPE | ||||
							
								
								
									
										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 | ||||
							
								
								
									
										103
									
								
								CMakeLists.txt
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								CMakeLists.txt
									
									
									
									
									
								
							| @@ -1,48 +1,81 @@ | ||||
| cmake_minimum_required(VERSION 3.9) | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(sdrpp) | ||||
|  | ||||
| set(CMAKE_BUILD_TYPE "RelWithDebInfo") | ||||
| option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Depedencies: soapysdr)" ON) | ||||
| option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Depedencies: libairspyhf)" ON) | ||||
| option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Depedencies: libairspy)" ON) | ||||
| option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" ON) | ||||
| option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON) | ||||
| option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: portaudio)" ON) | ||||
|  | ||||
| # Compiler config | ||||
| set(CMAKE_CXX_FLAGS "-O2 /std:c++17") | ||||
| # Core of SDR++ | ||||
| add_subdirectory("core") | ||||
|  | ||||
| # PothosSDR | ||||
| link_directories(sdrpp "C:/Program Files/PothosSDR/lib/") | ||||
| # Base modules | ||||
| add_subdirectory("radio") | ||||
| add_subdirectory("recorder")  | ||||
|  | ||||
| # Volk | ||||
| include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/") | ||||
| link_libraries(volk) | ||||
| # Source modules | ||||
| if (OPT_BUILD_RTL_TCP_SOURCE) | ||||
| add_subdirectory("rtl_tcp_source") | ||||
| endif (OPT_BUILD_RTL_TCP_SOURCE) | ||||
|  | ||||
| # SoapySDR | ||||
| include_directories(sdrpp "C:/Program Files/PothosSDR/include/") | ||||
| link_libraries(SoapySDR) | ||||
| if (OPT_BUILD_SPYSERVER_SOURCE) | ||||
| add_subdirectory("spyserver_source") | ||||
| endif (OPT_BUILD_SPYSERVER_SOURCE) | ||||
|  | ||||
| # Main code | ||||
| include_directories(sdrpp "src/") | ||||
| include_directories(sdrpp "src/imgui") | ||||
| file(GLOB SRC "src/*.cpp") | ||||
| file(GLOB IMGUI "src/imgui/*.cpp") | ||||
| add_executable(sdrpp ${SRC} ${IMGUI}) | ||||
| if (OPT_BUILD_SOAPY_SOURCE) | ||||
| add_subdirectory("soapy_source") | ||||
| endif (OPT_BUILD_SOAPY_SOURCE) | ||||
|  | ||||
| # Glew | ||||
| find_package(GLEW REQUIRED) | ||||
| target_link_libraries(sdrpp PRIVATE GLEW::GLEW) | ||||
| if (OPT_BUILD_AIRSPYHF_SOURCE) | ||||
| add_subdirectory("airspyhf_source") | ||||
| endif (OPT_BUILD_AIRSPYHF_SOURCE) | ||||
|  | ||||
| # GLFW3 | ||||
| find_package(glfw3 CONFIG REQUIRED) | ||||
| target_link_libraries(sdrpp PRIVATE glfw) | ||||
| if (OPT_BUILD_AIRSPY_SOURCE) | ||||
| add_subdirectory("airspy_source") | ||||
| endif (OPT_BUILD_AIRSPY_SOURCE) | ||||
|  | ||||
| # FFTW3 | ||||
| find_package(FFTW3 CONFIG REQUIRED) | ||||
| target_link_libraries(sdrpp PRIVATE FFTW3::fftw3) | ||||
| find_package(FFTW3f CONFIG REQUIRED) | ||||
| target_link_libraries(sdrpp PRIVATE FFTW3::fftw3f) | ||||
| find_package(FFTW3l CONFIG REQUIRED) | ||||
| target_link_libraries(sdrpp PRIVATE FFTW3::fftw3l) | ||||
| if (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
| add_subdirectory("plutosdr_source") | ||||
| endif (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
|  | ||||
| # PortAudio | ||||
| find_package(portaudio CONFIG REQUIRED) | ||||
| target_link_libraries(sdrpp PRIVATE portaudio portaudio_static) | ||||
| if (OPT_BUILD_HACKRF_SOURCE) | ||||
| add_subdirectory("hackrf_source") | ||||
| endif (OPT_BUILD_HACKRF_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_AUDIO_SINK) | ||||
| add_subdirectory("audio_sink") | ||||
| endif (OPT_BUILD_AUDIO_SINK) | ||||
|  | ||||
| if (MSVC) | ||||
|     set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") | ||||
| else() | ||||
|     set(CMAKE_CXX_FLAGS "-O3 -std=c++17") | ||||
| endif (MSVC) | ||||
|  | ||||
| add_executable(sdrpp "src/main.cpp" "win32/resources.rc") | ||||
| target_link_libraries(sdrpp PRIVATE sdrpp_core) | ||||
|  | ||||
| # Copy dynamic libs over | ||||
| if (MSVC) | ||||
|     add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) | ||||
|     add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) | ||||
| endif (MSVC) | ||||
|  | ||||
|  | ||||
| # 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
									
								
								airspy_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								airspy_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(airspy_source) | ||||
|  | ||||
| if (MSVC) | ||||
|     set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") | ||||
| else() | ||||
|     set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") | ||||
| endif (MSVC) | ||||
|  | ||||
| include_directories("src/") | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| add_library(airspy_source SHARED ${SRC}) | ||||
| target_link_libraries(airspy_source PRIVATE sdrpp_core) | ||||
| set_target_properties(airspy_source PROPERTIES PREFIX "") | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/") | ||||
|  | ||||
|     target_link_libraries(airspy_source PUBLIC airspy) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBAIRSPYHF REQUIRED libairspy) | ||||
|  | ||||
|     target_include_directories(airspy_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS}) | ||||
|     target_link_directories(airspy_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS}) | ||||
|     target_link_libraries(airspy_source PUBLIC ${LIBAIRSPYHF_LIBRARIES}) | ||||
| endif (MSVC) | ||||
							
								
								
									
										597
									
								
								airspy_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										597
									
								
								airspy_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,597 @@ | ||||
| #include <imgui.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <config.h> | ||||
| #include <options.h> | ||||
| #include <libairspy/airspy.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO { | ||||
|     /* Name:            */ "airspy_source", | ||||
|     /* Description:     */ "Airspy source module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| class AirspySourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     AirspySourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 10000000.0; | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected;  | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &stream; | ||||
|  | ||||
|         refresh(); | ||||
|         if (sampleRateList.size() > 0) { | ||||
|             sampleRate = sampleRateList[0]; | ||||
|         } | ||||
|  | ||||
|         // Select device from config | ||||
|         config.aquire(); | ||||
|         std::string devSerial = config.conf["device"]; | ||||
|         config.release(); | ||||
|         selectByString(devSerial); | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("Airspy", &handler); | ||||
|     } | ||||
|  | ||||
|     ~AirspySourceModule() { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
|     void refresh() { | ||||
|         devList.clear(); | ||||
|         devListTxt = ""; | ||||
|  | ||||
|         uint64_t serials[256]; | ||||
|         int n = airspy_list_devices(serials, 256); | ||||
|  | ||||
|         char buf[1024]; | ||||
|         for (int i = 0; i < n; i++) { | ||||
|             sprintf(buf, "%016" PRIX64, serials[i]); | ||||
|             devList.push_back(serials[i]); | ||||
|             devListTxt += buf; | ||||
|             devListTxt += '\0'; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void selectFirst() { | ||||
|         if (devList.size() != 0) { | ||||
|             selectBySerial(devList[0]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void selectByString(std::string serial) { | ||||
|         char buf[1024]; | ||||
|         for (int i = 0; i < devList.size(); i++) { | ||||
|             sprintf(buf, "%016" PRIX64, devList[i]); | ||||
|             std::string str = buf; | ||||
|             if (serial == str) { | ||||
|                 selectBySerial(devList[i]); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         selectFirst(); | ||||
|     } | ||||
|  | ||||
|     void selectBySerial(uint64_t serial) { | ||||
|         selectedSerial = serial; | ||||
|         airspy_device* dev; | ||||
|         int err = airspy_open_sn(&dev, selectedSerial); | ||||
|         if (err != 0) { | ||||
|             char buf[1024]; | ||||
|             sprintf(buf, "%016" PRIX64, selectedSerial); | ||||
|             spdlog::error("Could not open Airspy HF+ {0}", buf); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         uint32_t sampleRates[256]; | ||||
|         airspy_get_samplerates(dev, sampleRates, 0); | ||||
|         int n = sampleRates[0]; | ||||
|         airspy_get_samplerates(dev, sampleRates, n); | ||||
|         sampleRateList.clear(); | ||||
|         sampleRateListTxt = ""; | ||||
|         for (int i = 0; i < n; i++) { | ||||
|             sampleRateList.push_back(sampleRates[i]); | ||||
|             sampleRateListTxt += getBandwdithScaled(sampleRates[i]); | ||||
|             sampleRateListTxt += '\0'; | ||||
|         } | ||||
|  | ||||
|         char buf[1024]; | ||||
|         sprintf(buf, "%016" PRIX64, serial); | ||||
|         selectedSerStr = std::string(buf); | ||||
|  | ||||
|         // Load config here | ||||
|         config.aquire(); | ||||
|         bool created = false; | ||||
|         if (!config.conf["devices"].contains(selectedSerStr)) { | ||||
|             created = true; | ||||
|             config.conf["devices"][selectedSerStr]["sampleRate"] = 10000000; | ||||
|             config.conf["devices"][selectedSerStr]["gainMode"] = 0; | ||||
|             config.conf["devices"][selectedSerStr]["sensitiveGain"] = 0; | ||||
|             config.conf["devices"][selectedSerStr]["linearGain"] = 0; | ||||
|             config.conf["devices"][selectedSerStr]["lnaGain"] = 0; | ||||
|             config.conf["devices"][selectedSerStr]["mixerGain"] = 0; | ||||
|             config.conf["devices"][selectedSerStr]["vgaGain"] = 0; | ||||
|             config.conf["devices"][selectedSerStr]["lnaAgc"] = false; | ||||
|             config.conf["devices"][selectedSerStr]["mixerAgc"] = false; | ||||
|             config.conf["devices"][selectedSerStr]["biasT"] = false; | ||||
|         } | ||||
|  | ||||
|         // Load sample rate | ||||
|         srId = 0; | ||||
|         sampleRate = sampleRateList[0]; | ||||
|         if (config.conf["devices"][selectedSerStr].contains("sampleRate")) { | ||||
|             int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"]; | ||||
|             for (int i = 0; i < sampleRateList.size(); i++) { | ||||
|                 if (sampleRateList[i] == selectedSr) { | ||||
|                     srId = i; | ||||
|                     sampleRate = selectedSr; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Load gains | ||||
|         if (config.conf["devices"][selectedSerStr].contains("gainMode")) { | ||||
|             gainMode = config.conf["devices"][selectedSerStr]["gainMode"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("sensitiveGain")) { | ||||
|             sensitiveGain = config.conf["devices"][selectedSerStr]["sensitiveGain"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("linearGain")) { | ||||
|             linearGain = config.conf["devices"][selectedSerStr]["linearGain"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("lnaGain")) { | ||||
|             lnaGain = config.conf["devices"][selectedSerStr]["lnaGain"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("mixerGain")) { | ||||
|             mixerGain = config.conf["devices"][selectedSerStr]["mixerGain"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("vgaGain")) { | ||||
|             vgaGain = config.conf["devices"][selectedSerStr]["vgaGain"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("lnaAgc")) { | ||||
|             lnaAgc = config.conf["devices"][selectedSerStr]["lnaAgc"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("mixerAgc")) { | ||||
|             mixerAgc = config.conf["devices"][selectedSerStr]["mixerAgc"]; | ||||
|         } | ||||
|  | ||||
|         // Load Bias-T | ||||
|         if (config.conf["devices"][selectedSerStr].contains("biasT")) { | ||||
|             biasT = config.conf["devices"][selectedSerStr]["biasT"]; | ||||
|         } | ||||
|  | ||||
|         config.release(created); | ||||
|  | ||||
|         airspy_close(dev); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string getBandwdithScaled(double bw) { | ||||
|         char buf[1024]; | ||||
|         if (bw >= 1000000.0) { | ||||
|             sprintf(buf, "%.1lfMHz", bw / 1000000.0); | ||||
|         } | ||||
|         else if (bw >= 1000.0) { | ||||
|             sprintf(buf, "%.1lfKHz", bw / 1000.0); | ||||
|         } | ||||
|         else { | ||||
|             sprintf(buf, "%.1lfHz", bw); | ||||
|         } | ||||
|         return std::string(buf); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         spdlog::info("AirspySourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         spdlog::info("AirspySourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|      | ||||
|     static void start(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             return; | ||||
|         } | ||||
|         if (_this->selectedSerial == 0) { | ||||
|             spdlog::error("Tried to start AirspyHF+ source with null serial"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         int err = airspy_open_sn(&_this->openDev, _this->selectedSerial); | ||||
|         if (err != 0) { | ||||
|             char buf[1024]; | ||||
|             sprintf(buf, "%016" PRIX64, _this->selectedSerial); | ||||
|             spdlog::error("Could not open Airspy {0}", buf); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         airspy_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]); | ||||
|         airspy_set_freq(_this->openDev, _this->freq); | ||||
|          | ||||
|         if (_this->gainMode == 0) { | ||||
|             airspy_set_lna_agc(_this->openDev, 0); | ||||
|             airspy_set_mixer_agc(_this->openDev, 0); | ||||
|             airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain); | ||||
|         } | ||||
|         else if (_this->gainMode == 1) { | ||||
|             airspy_set_lna_agc(_this->openDev, 0); | ||||
|             airspy_set_mixer_agc(_this->openDev, 0); | ||||
|             airspy_set_linearity_gain(_this->openDev, _this->linearGain); | ||||
|         } | ||||
|         else if (_this->gainMode == 2) { | ||||
|             if (_this->lnaAgc) { | ||||
|                 airspy_set_lna_agc(_this->openDev, 1); | ||||
|             } | ||||
|             else { | ||||
|                 airspy_set_lna_agc(_this->openDev, 0); | ||||
|                 airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|             } | ||||
|             if (_this->mixerAgc) { | ||||
|                 airspy_set_mixer_agc(_this->openDev, 1); | ||||
|             } | ||||
|             else { | ||||
|                 airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                 airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|             } | ||||
|             airspy_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|         } | ||||
|  | ||||
|         airspy_set_rf_bias(_this->openDev, _this->biasT); | ||||
|  | ||||
|         airspy_start_rx(_this->openDev, callback, _this); | ||||
|  | ||||
|         _this->running = true; | ||||
|         spdlog::info("AirspySourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|      | ||||
|     static void stop(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         if (!_this->running) { | ||||
|             return; | ||||
|         } | ||||
|         _this->running = false; | ||||
|         _this->stream.stopWriter(); | ||||
|         airspy_close(_this->openDev); | ||||
|         _this->stream.clearWriteStop(); | ||||
|         spdlog::info("AirspySourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|      | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             airspy_set_freq(_this->openDev, freq); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         spdlog::info("AirspySourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|      | ||||
|     static void menuHandler(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         float menuWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|  | ||||
|         if (_this->running) { style::beginDisabled(); } | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(CONCAT("##_airspy_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) { | ||||
|             _this->selectBySerial(_this->devList[_this->devId]); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["device"] = _this->selectedSerStr; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (ImGui::Combo(CONCAT("##_airspy_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) { | ||||
|             _this->sampleRate = _this->sampleRateList[_this->srId]; | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ImGui::SameLine(); | ||||
|         float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX(); | ||||
|         if (ImGui::Button(CONCAT("Refresh##_airspy_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) { | ||||
|             _this->refresh(); | ||||
|             config.aquire(); | ||||
|             std::string devSerial = config.conf["device"]; | ||||
|             config.release(); | ||||
|             _this->selectByString(devSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { style::endDisabled(); } | ||||
|  | ||||
|         ImGui::BeginGroup(); | ||||
|         ImGui::Columns(3, CONCAT("AirspyGainModeColumns##_", _this->name), false); | ||||
|         if (ImGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", _this->name), _this->gainMode == 0)) { | ||||
|             _this->gainMode = 0; | ||||
|             if (_this->running) { | ||||
|                 airspy_set_lna_agc(_this->openDev, 0); | ||||
|                 airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                 airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         ImGui::NextColumn(); | ||||
|         if (ImGui::RadioButton(CONCAT("Linear##_airspy_gm_", _this->name), _this->gainMode == 1)) { | ||||
|             _this->gainMode = 1; | ||||
|             if (_this->running) { | ||||
|                 airspy_set_lna_agc(_this->openDev, 0); | ||||
|                 airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                 airspy_set_linearity_gain(_this->openDev, _this->linearGain); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         ImGui::NextColumn(); | ||||
|         if (ImGui::RadioButton(CONCAT("Free##_airspy_gm_", _this->name), _this->gainMode == 2)) { | ||||
|             _this->gainMode = 2; | ||||
|             if (_this->running) { | ||||
|                 if (_this->lnaAgc) { | ||||
|                     airspy_set_lna_agc(_this->openDev, 1); | ||||
|                 } | ||||
|                 else { | ||||
|                     airspy_set_lna_agc(_this->openDev, 0); | ||||
|                     airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                 } | ||||
|                 if (_this->mixerAgc) { | ||||
|                     airspy_set_mixer_agc(_this->openDev, 1); | ||||
|                 } | ||||
|                 else { | ||||
|                     airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                     airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|                 } | ||||
|                 airspy_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         ImGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", _this->name), false); | ||||
|         ImGui::EndGroup(); | ||||
|  | ||||
|         // Gain menus | ||||
|  | ||||
|         if (_this->gainMode == 0) { | ||||
|             ImGui::Text("Gain"); | ||||
|             ImGui::SameLine(); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             if (ImGui::SliderInt(CONCAT("##_airspy_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.aquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if (_this->gainMode == 1) { | ||||
|             ImGui::Text("Gain"); | ||||
|             ImGui::SameLine(); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             if (ImGui::SliderInt(CONCAT("##_airspy_lin_gain_", _this->name), &_this->linearGain, 0, 21)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_linearity_gain(_this->openDev, _this->linearGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.aquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if (_this->gainMode == 2) { | ||||
|             // Calculate position of sliders | ||||
|             float pos = ImGui::CalcTextSize("Mixer Gain").x + 10; | ||||
|  | ||||
|             if (_this->lnaAgc) { style::beginDisabled(); } | ||||
|             ImGui::Text("LNA Gain"); | ||||
|             ImGui::SameLine(); | ||||
|             ImGui::SetCursorPosX(pos); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             if (ImGui::SliderInt(CONCAT("##_airspy_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.aquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             if (_this->lnaAgc) { style::endDisabled(); } | ||||
|  | ||||
|             if (_this->mixerAgc) { style::beginDisabled(); } | ||||
|             ImGui::Text("Mixer Gain"); | ||||
|             ImGui::SameLine(); | ||||
|             ImGui::SetCursorPosX(pos); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             if (ImGui::SliderInt(CONCAT("##_airspy_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.aquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             if (_this->mixerAgc) { style::endDisabled(); } | ||||
|  | ||||
|             ImGui::Text("VGA Gain"); | ||||
|             ImGui::SameLine(); | ||||
|             ImGui::SetCursorPosX(pos); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             if (ImGui::SliderInt(CONCAT("##_airspy_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.aquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // AGC Control | ||||
|             if (ImGui::Checkbox(CONCAT("LNA AGC##_airspy_", _this->name), &_this->lnaAgc)) { | ||||
|                 if (_this->running) { | ||||
|                     if (_this->lnaAgc) { | ||||
|                         airspy_set_lna_agc(_this->openDev, 1); | ||||
|                     } | ||||
|                     else { | ||||
|                         airspy_set_lna_agc(_this->openDev, 0); | ||||
|                         airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                     } | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.aquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             if (ImGui::Checkbox(CONCAT("Mixer AGC##_airspy_", _this->name), &_this->mixerAgc)) { | ||||
|                 if (_this->running) { | ||||
|                     if (_this->mixerAgc) { | ||||
|                         airspy_set_mixer_agc(_this->openDev, 1); | ||||
|                     } | ||||
|                     else { | ||||
|                         airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                         airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|                     } | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.aquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Bias T | ||||
|  | ||||
|         if (ImGui::Checkbox(CONCAT("Bias T##_airspy_", _this->name), &_this->biasT)) { | ||||
|             if (_this->running) { | ||||
|                 airspy_set_rf_bias(_this->openDev, _this->biasT); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|          | ||||
|     } | ||||
|  | ||||
|     static int callback(airspy_transfer_t* transfer) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)transfer->ctx; | ||||
|         memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t)); | ||||
|         if (!_this->stream.swap(transfer->sample_count)) { return -1; } | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     airspy_device* openDev; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|     uint64_t selectedSerial = 0; | ||||
|     std::string selectedSerStr = ""; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|  | ||||
|     bool biasT = false; | ||||
|  | ||||
|     int lnaGain = 0; | ||||
|     int vgaGain = 0; | ||||
|     int mixerGain = 0; | ||||
|     int linearGain = 0; | ||||
|     int sensitiveGain = 0; | ||||
|  | ||||
|     int gainMode = 0; | ||||
|  | ||||
|     bool lnaAgc = false; | ||||
|     bool mixerAgc = false; | ||||
|  | ||||
|     std::vector<uint64_t> devList; | ||||
|     std::string devListTxt; | ||||
|     std::vector<uint32_t> sampleRateList; | ||||
|     std::string sampleRateListTxt; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     json def = json({}); | ||||
|     def["devices"] = json({}); | ||||
|     def["device"] = ""; | ||||
|     config.setPath(options::opts.root + "/airspy_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new AirspySourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { | ||||
|     delete (AirspySourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
							
								
								
									
										31
									
								
								airspyhf_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								airspyhf_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(airspyhf_source) | ||||
|  | ||||
| if (MSVC) | ||||
|     set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") | ||||
| else() | ||||
|     set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") | ||||
| endif (MSVC) | ||||
|  | ||||
| include_directories("src/") | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| add_library(airspyhf_source SHARED ${SRC}) | ||||
| target_link_libraries(airspyhf_source PRIVATE sdrpp_core) | ||||
| set_target_properties(airspyhf_source PROPERTIES PREFIX "") | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/") | ||||
|  | ||||
|     target_link_libraries(airspyhf_source PUBLIC airspyhf) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBAIRSPYHF REQUIRED libairspyhf) | ||||
|  | ||||
|     target_include_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS}) | ||||
|     target_link_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS}) | ||||
|     target_link_libraries(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARIES}) | ||||
| endif (MSVC) | ||||
							
								
								
									
										393
									
								
								airspyhf_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								airspyhf_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,393 @@ | ||||
| #include <imgui.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <config.h> | ||||
| #include <options.h> | ||||
| #include <libairspyhf/airspyhf.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO { | ||||
|     /* Name:            */ "airspyhf_source", | ||||
|     /* Description:     */ "Airspy HF+ source module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| const char* AGG_MODES_STR = "Off\0Low\0High\0"; | ||||
|  | ||||
| class AirspyHFSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     AirspyHFSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 768000.0; | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &stream; | ||||
|  | ||||
|         refresh(); | ||||
|  | ||||
|         config.aquire(); | ||||
|         std::string devSerial = config.conf["device"]; | ||||
|         config.release(); | ||||
|         selectByString(devSerial); | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("Airspy HF+", &handler); | ||||
|     } | ||||
|  | ||||
|     ~AirspyHFSourceModule() { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     enum AGCMode { | ||||
|         AGC_MODE_OFF, | ||||
|         AGC_MODE_LOW, | ||||
|         AGC_MODE_HIGG | ||||
|     }; | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
|     void refresh() { | ||||
|         devList.clear(); | ||||
|         devListTxt = ""; | ||||
|  | ||||
|         uint64_t serials[256]; | ||||
|         int n = airspyhf_list_devices(serials, 256); | ||||
|  | ||||
|         char buf[1024]; | ||||
|         for (int i = 0; i < n; i++) { | ||||
|             sprintf(buf, "%016" PRIX64, serials[i]); | ||||
|             devList.push_back(serials[i]); | ||||
|             devListTxt += buf; | ||||
|             devListTxt += '\0'; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void selectFirst() { | ||||
|         if (devList.size() != 0) { | ||||
|             selectBySerial(devList[0]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void selectByString(std::string serial) { | ||||
|         char buf[1024]; | ||||
|         for (int i = 0; i < devList.size(); i++) { | ||||
|             sprintf(buf, "%016" PRIX64, devList[i]); | ||||
|             std::string str = buf; | ||||
|             if (serial == str) { | ||||
|                 selectBySerial(devList[i]); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         selectFirst(); | ||||
|     } | ||||
|  | ||||
|     void selectBySerial(uint64_t serial) { | ||||
|         selectedSerial = serial; | ||||
|         airspyhf_device_t* dev; | ||||
|         int err = airspyhf_open_sn(&dev, selectedSerial); | ||||
|         if (err != 0) { | ||||
|             char buf[1024]; | ||||
|             sprintf(buf, "%016" PRIX64, selectedSerial); | ||||
|             spdlog::error("Could not open Airspy HF+ {0}", buf); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         uint32_t sampleRates[256]; | ||||
|         airspyhf_get_samplerates(dev, sampleRates, 0); | ||||
|         int n = sampleRates[0]; | ||||
|         airspyhf_get_samplerates(dev, sampleRates, n); | ||||
|         sampleRateList.clear(); | ||||
|         sampleRateListTxt = ""; | ||||
|         for (int i = 0; i < n; i++) { | ||||
|             sampleRateList.push_back(sampleRates[i]); | ||||
|             sampleRateListTxt += getBandwdithScaled(sampleRates[i]); | ||||
|             sampleRateListTxt += '\0'; | ||||
|         } | ||||
|  | ||||
|         char buf[1024]; | ||||
|         sprintf(buf, "%016" PRIX64, serial); | ||||
|         selectedSerStr = std::string(buf); | ||||
|  | ||||
|         // Load config here | ||||
|         config.aquire(); | ||||
|         bool created = false; | ||||
|         if (!config.conf["devices"].contains(selectedSerStr)) { | ||||
|             created = true; | ||||
|             config.conf["devices"][selectedSerStr]["sampleRate"] = 768000; | ||||
|             config.conf["devices"][selectedSerStr]["agcMode"] = 0; | ||||
|             config.conf["devices"][selectedSerStr]["lna"] = false; | ||||
|             config.conf["devices"][selectedSerStr]["attenuation"] = 0; | ||||
|         } | ||||
|  | ||||
|         // Load sample rate | ||||
|         srId = 0; | ||||
|         sampleRate = sampleRateList[0]; | ||||
|         if (config.conf["devices"][selectedSerStr].contains("sampleRate")) { | ||||
|             int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"]; | ||||
|             for (int i = 0; i < sampleRateList.size(); i++) { | ||||
|                 if (sampleRateList[i] == selectedSr) { | ||||
|                     srId = i; | ||||
|                     sampleRate = selectedSr; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Load Gains | ||||
|         if (config.conf["devices"][selectedSerStr].contains("agcMode")) { | ||||
|             agcMode = config.conf["devices"][selectedSerStr]["agcMode"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("lna")) { | ||||
|             hfLNA = config.conf["devices"][selectedSerStr]["lna"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerStr].contains("attenuation")) { | ||||
|             atten = config.conf["devices"][selectedSerStr]["attenuation"]; | ||||
|         } | ||||
|  | ||||
|         config.release(created); | ||||
|  | ||||
|         airspyhf_close(dev); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string getBandwdithScaled(double bw) { | ||||
|         char buf[1024]; | ||||
|         if (bw >= 1000000.0) { | ||||
|             sprintf(buf, "%.1lfMHz", bw / 1000000.0); | ||||
|         } | ||||
|         else if (bw >= 1000.0) { | ||||
|             sprintf(buf, "%.1lfKHz", bw / 1000.0); | ||||
|         } | ||||
|         else { | ||||
|             sprintf(buf, "%.1lfHz", bw); | ||||
|         } | ||||
|         return std::string(buf); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; | ||||
|         spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|      | ||||
|     static void start(void* ctx) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             return; | ||||
|         } | ||||
|         if (_this->selectedSerial == 0) { | ||||
|             spdlog::error("Tried to start AirspyHF+ source with null serial"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         int err = airspyhf_open_sn(&_this->openDev, _this->selectedSerial); | ||||
|         if (err != 0) { | ||||
|             char buf[1024]; | ||||
|             sprintf(buf, "%016" PRIX64, _this->selectedSerial); | ||||
|             spdlog::error("Could not open Airspy HF+ {0}", buf); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         airspyhf_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]); | ||||
|         airspyhf_set_freq(_this->openDev, _this->freq); | ||||
|         airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0)); | ||||
|         if (_this->agcMode > 0) { | ||||
|             airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1); | ||||
|         } | ||||
|         airspyhf_set_hf_att(_this->openDev, _this->atten / 6); | ||||
|         airspyhf_set_hf_lna(_this->openDev, _this->hfLNA); | ||||
|  | ||||
|         airspyhf_start(_this->openDev, callback, _this); | ||||
|  | ||||
|         _this->running = true; | ||||
|         spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|      | ||||
|     static void stop(void* ctx) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; | ||||
|         if (!_this->running) { | ||||
|             return; | ||||
|         } | ||||
|         _this->running = false; | ||||
|         _this->stream.stopWriter(); | ||||
|         airspyhf_close(_this->openDev); | ||||
|         _this->stream.clearWriteStop(); | ||||
|         spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|      | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             airspyhf_set_freq(_this->openDev, freq); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|      | ||||
|     static void menuHandler(void* ctx) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; | ||||
|         float menuWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|  | ||||
|         if (_this->running) { style::beginDisabled(); } | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) { | ||||
|             _this->selectBySerial(_this->devList[_this->devId]); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["device"] = _this->selectedSerStr; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (ImGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) { | ||||
|             _this->sampleRate = _this->sampleRateList[_this->srId]; | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ImGui::SameLine(); | ||||
|         float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX(); | ||||
|         if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) { | ||||
|             _this->refresh(); | ||||
|             config.aquire(); | ||||
|             std::string devSerial = config.conf["device"]; | ||||
|             config.release(); | ||||
|             _this->selectByString(devSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { style::endDisabled(); } | ||||
|  | ||||
|         ImGui::Text("AGC Mode"); | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo(CONCAT("##_airspyhf_agc_", _this->name), &_this->agcMode, AGG_MODES_STR)) { | ||||
|             if (_this->running) { | ||||
|                 airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0)); | ||||
|                 if (_this->agcMode > 0) { | ||||
|                     airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1); | ||||
|                 } | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["agcMode"] = _this->agcMode; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ImGui::Text("HF LNA"); | ||||
|         ImGui::SameLine(); | ||||
|         if (ImGui::Checkbox(CONCAT("##_airspyhf_lna_", _this->name), &_this->hfLNA)) { | ||||
|             if (_this->running) { | ||||
|                 airspyhf_set_hf_lna(_this->openDev, _this->hfLNA); | ||||
|             }       | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["lna"] = _this->hfLNA; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ImGui::Text("Attenuation"); | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::SliderInt(CONCAT("##_airspyhf_attn_", _this->name), &_this->atten, 0, 48, "%d dB")) { | ||||
|             _this->atten = (_this->atten / 6) * 6; | ||||
|             if (_this->running) { | ||||
|                 airspyhf_set_hf_att(_this->openDev, _this->atten / 6); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.aquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["attenuation"] = _this->atten; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         }         | ||||
|     } | ||||
|  | ||||
|     static int callback(airspyhf_transfer_t* transfer) { | ||||
|         AirspyHFSourceModule* _this = (AirspyHFSourceModule*)transfer->ctx; | ||||
|         memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t)); | ||||
|         if (!_this->stream.swap(transfer->sample_count)) { return -1; } | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     airspyhf_device_t* openDev; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|     uint64_t selectedSerial = 0; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int agcMode = AGC_MODE_OFF; | ||||
|     bool hfLNA = false; | ||||
|     int atten = 0; | ||||
|     std::string selectedSerStr = ""; | ||||
|  | ||||
|     std::vector<uint64_t> devList; | ||||
|     std::string devListTxt; | ||||
|     std::vector<uint32_t> sampleRateList; | ||||
|     std::string sampleRateListTxt; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     json def = json({}); | ||||
|     def["devices"] = json({}); | ||||
|     def["device"] = ""; | ||||
|     config.setPath(options::opts.root + "/airspyhf_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new AirspyHFSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { | ||||
|     delete (AirspyHFSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
							
								
								
									
										32
									
								
								audio_sink/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								audio_sink/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(audio_sink) | ||||
|  | ||||
| if (MSVC) | ||||
|     set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") | ||||
| else() | ||||
|     set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") | ||||
| endif (MSVC) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include_directories("src/") | ||||
|  | ||||
| add_library(audio_sink SHARED ${SRC}) | ||||
| target_link_libraries(audio_sink PRIVATE sdrpp_core) | ||||
| set_target_properties(audio_sink PROPERTIES PREFIX "") | ||||
|  | ||||
| if (MSVC) | ||||
|     find_package(portaudio CONFIG REQUIRED) | ||||
|     target_link_libraries(sdrpp_core PUBLIC portaudio) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0) | ||||
|  | ||||
|     target_include_directories(sdrpp_core PUBLIC ${PORTAUDIO_INCLUDE_DIRS}) | ||||
|  | ||||
|     target_link_directories(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARY_DIRS}) | ||||
|  | ||||
|     target_link_libraries(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARIES}) | ||||
|  | ||||
| endif (MSVC) | ||||
							
								
								
									
										290
									
								
								audio_sink/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								audio_sink/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,290 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <signal_path/sink.h> | ||||
| #include <portaudio.h> | ||||
| #include <dsp/audio.h> | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO { | ||||
|     /* Name:            */ "audio_sink", | ||||
|     /* Description:     */ "Audio sink module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| class AudioSink : SinkManager::Sink { | ||||
| public: | ||||
|     struct AudioDevice_t { | ||||
|         std::string name; | ||||
|         int index; | ||||
|         int channels; | ||||
|         int srId; | ||||
|         std::vector<double> sampleRates; | ||||
|         std::string txtSampleRates; | ||||
|     }; | ||||
|      | ||||
|     AudioSink(SinkManager::Stream* stream, std::string streamName) { | ||||
|         _stream = stream; | ||||
|         _streamName = streamName; | ||||
|         s2m.init(_stream->sinkOut); | ||||
|         monoRB.init(&s2m.out); | ||||
|         stereoRB.init(_stream->sinkOut); | ||||
|  | ||||
|         // Initialize PortAudio | ||||
|         devCount = Pa_GetDeviceCount(); | ||||
|         devId = Pa_GetDefaultOutputDevice(); | ||||
|         const PaDeviceInfo *deviceInfo; | ||||
|         PaStreamParameters outputParams; | ||||
|         outputParams.sampleFormat = paFloat32; | ||||
|         outputParams.hostApiSpecificStreamInfo = NULL; | ||||
|  | ||||
|         // Gather hardware info | ||||
|         for(int i = 0; i < devCount; i++) { | ||||
|             deviceInfo = Pa_GetDeviceInfo(i); | ||||
|             if (deviceInfo->maxOutputChannels < 1) { | ||||
|                 continue; | ||||
|             } | ||||
|             AudioDevice_t dev; | ||||
|             dev.name = deviceInfo->name; | ||||
|             dev.index = i; | ||||
|             dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2); | ||||
|             dev.sampleRates.clear(); | ||||
|             dev.txtSampleRates = ""; | ||||
|             for (int j = 0; j < 6; j++) { | ||||
|                 outputParams.channelCount = dev.channels; | ||||
|                 outputParams.device = dev.index; | ||||
|                 outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency; | ||||
|                 PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]); | ||||
|                 if (err != paFormatIsSupported) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]); | ||||
|                 dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]); | ||||
|                 dev.txtSampleRates += '\0'; | ||||
|             } | ||||
|             if (dev.sampleRates.size() == 0) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (i == devId) { | ||||
|                 devListId = devices.size(); | ||||
|                 defaultDev = devListId; | ||||
|             } | ||||
|             dev.srId = 0; | ||||
|  | ||||
|             AudioDevice_t* _dev = new AudioDevice_t; | ||||
|             *_dev = dev; | ||||
|             devices.push_back(_dev); | ||||
|  | ||||
|             deviceNames.push_back(deviceInfo->name); | ||||
|             txtDevList += deviceInfo->name; | ||||
|             txtDevList += '\0'; | ||||
|         } | ||||
|  | ||||
|         // Load config from file | ||||
|     } | ||||
|  | ||||
|     ~AudioSink() { | ||||
|         for (auto const& dev : devices) { | ||||
|             delete dev; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void start() { | ||||
|         if (running) { | ||||
|             return; | ||||
|         } | ||||
|         doStart(); | ||||
|         running = true; | ||||
|     } | ||||
|  | ||||
|     void stop() { | ||||
|         if (!running) { | ||||
|             return; | ||||
|         } | ||||
|         doStop(); | ||||
|         running = false; | ||||
|     } | ||||
|      | ||||
|     void menuHandler() { | ||||
|         float menuWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devListId, txtDevList.c_str())) { | ||||
|             // TODO: Load SR from config | ||||
|             if (running) { | ||||
|                 doStop(); | ||||
|                 doStart(); | ||||
|             } | ||||
|             // TODO: Save to config | ||||
|         } | ||||
|  | ||||
|         AudioDevice_t* dev = devices[devListId]; | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &dev->srId, dev->txtSampleRates.c_str())) { | ||||
|             _stream->setSampleRate(dev->sampleRates[dev->srId]); | ||||
|             if (running) { | ||||
|                 doStop(); | ||||
|                 doStart(); | ||||
|             } | ||||
|             // TODO: Save to config | ||||
|         } | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void doStart() { | ||||
|         const PaDeviceInfo *deviceInfo; | ||||
|         AudioDevice_t* dev = devices[devListId]; | ||||
|         PaStreamParameters outputParams; | ||||
|         deviceInfo = Pa_GetDeviceInfo(dev->index); | ||||
|         outputParams.channelCount = 2; | ||||
|         outputParams.sampleFormat = paFloat32; | ||||
|         outputParams.hostApiSpecificStreamInfo = NULL; | ||||
|         outputParams.device = dev->index; | ||||
|         outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency; | ||||
|         PaError err; | ||||
|  | ||||
|         float sampleRate = dev->sampleRates[dev->srId]; | ||||
|         int bufferSize = sampleRate / 60.0f; | ||||
|  | ||||
|         if (dev->channels == 2) { | ||||
|             stereoRB.data.setMaxLatency(bufferSize * 2); | ||||
|             stereoRB.start(); | ||||
|             err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _stereo_cb, this); | ||||
|         } | ||||
|         else { | ||||
|             monoRB.data.setMaxLatency(bufferSize * 2); | ||||
|             monoRB.start(); | ||||
|             err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _mono_cb, this); | ||||
|         } | ||||
|  | ||||
|         if (err != 0) { | ||||
|             spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         err = Pa_StartStream(stream); | ||||
|         if (err != 0) { | ||||
|             spdlog::error("Error while starting audio stream: ({0}) => {1}", err, Pa_GetErrorText(err)); | ||||
|             return; | ||||
|         } | ||||
|         spdlog::info("Audio device open."); | ||||
|         running = true; | ||||
|     } | ||||
|  | ||||
|     void doStop() { | ||||
|         s2m.stop(); | ||||
|         monoRB.stop(); | ||||
|         stereoRB.stop(); | ||||
|         monoRB.data.stopReader(); | ||||
|         stereoRB.data.stopReader(); | ||||
|         Pa_StopStream(stream); | ||||
|         Pa_CloseStream(stream); | ||||
|         monoRB.data.clearReadStop(); | ||||
|         stereoRB.data.clearReadStop(); | ||||
|     } | ||||
|  | ||||
|     static int _mono_cb(const void *input, void *output, unsigned long frameCount, | ||||
|         const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { | ||||
|         AudioSink* _this = (AudioSink*)userData; | ||||
|         _this->monoRB.data.read((float*)output, frameCount); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     static int _stereo_cb(const void *input, void *output, unsigned long frameCount, | ||||
|         const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { | ||||
|         AudioSink* _this = (AudioSink*)userData; | ||||
|         _this->stereoRB.data.read((dsp::stereo_t*)output, frameCount); | ||||
|         return 0; | ||||
|     } | ||||
|      | ||||
|     SinkManager::Stream* _stream; | ||||
|     dsp::StereoToMono s2m; | ||||
|     dsp::RingBufferSink<float> monoRB; | ||||
|     dsp::RingBufferSink<dsp::stereo_t> stereoRB; | ||||
|     std::string _streamName; | ||||
|     PaStream *stream; | ||||
|  | ||||
|     int srId = 0; | ||||
|     int devCount; | ||||
|     int devId = 0; | ||||
|     int devListId = 0; | ||||
|     int defaultDev = 0; | ||||
|     bool running = false; | ||||
|  | ||||
|     const double POSSIBLE_SAMP_RATE[6] = { | ||||
|         48000.0f, | ||||
|         44100.0f, | ||||
|         24000.0f, | ||||
|         22050.0f, | ||||
|         12000.0f, | ||||
|         11025.0f | ||||
|     }; | ||||
|  | ||||
|     std::vector<AudioDevice_t*> devices; | ||||
|     std::vector<std::string> deviceNames; | ||||
|     std::string txtDevList; | ||||
|  | ||||
| }; | ||||
|  | ||||
| class AudioSinkModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     AudioSinkModule(std::string name) { | ||||
|         this->name = name; | ||||
|         provider.create = create_sink; | ||||
|         provider.ctx = this; | ||||
|  | ||||
|         Pa_Initialize(); | ||||
|  | ||||
|         sigpath::sinkManager.registerSinkProvider("Audio", provider); | ||||
|     } | ||||
|  | ||||
|     ~AudioSinkModule() { | ||||
|         Pa_Terminate(); | ||||
|     } | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { | ||||
|         return (SinkManager::Sink*)(new AudioSink(stream, streamName)); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     SinkManager::SinkProvider provider; | ||||
|  | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Nothing here | ||||
|     // TODO: Do instancing here (in source modules as well) to prevent multiple loads | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) { | ||||
|     AudioSinkModule* instance = new AudioSinkModule(name); | ||||
|     return instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_() { | ||||
|      | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|      | ||||
| } | ||||
							
								
								
									
										90
									
								
								core/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								core/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(sdrpp_core) | ||||
|  | ||||
| # Set compiler options | ||||
| if (MSVC) | ||||
|     set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") | ||||
|     set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) | ||||
| else() | ||||
|     set(CMAKE_CXX_FLAGS "-g -O3 -std=c++17 -fpermissive") | ||||
| endif (MSVC) | ||||
| add_definitions(-DSDRPP_IS_CORE) | ||||
|  | ||||
| # Main code | ||||
| file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") | ||||
|  | ||||
| # Add code to dyn lib | ||||
| add_library(sdrpp_core SHARED ${SRC}) | ||||
|  | ||||
| # Include core headers | ||||
| target_include_directories(sdrpp_core PUBLIC "src/") | ||||
| target_include_directories(sdrpp_core PUBLIC "src/imgui") | ||||
|  | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/lib/") | ||||
|  | ||||
|     # Misc headers | ||||
|     target_include_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/include/") | ||||
|  | ||||
|     # Volk | ||||
|     target_link_libraries(sdrpp_core PUBLIC volk) | ||||
|  | ||||
|     # Glew | ||||
|     find_package(GLEW REQUIRED) | ||||
|     target_link_libraries(sdrpp_core PUBLIC GLEW::GLEW) | ||||
|  | ||||
|     # GLFW3 | ||||
|     find_package(glfw3 CONFIG REQUIRED) | ||||
|     target_link_libraries(sdrpp_core PUBLIC glfw) | ||||
|  | ||||
|     # FFTW3 | ||||
|     find_package(FFTW3f CONFIG REQUIRED) | ||||
|     target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f) | ||||
|  | ||||
| else() | ||||
|     find_package(PkgConfig) | ||||
|     find_package(OpenGL REQUIRED) | ||||
|  | ||||
|     pkg_check_modules(GLEW REQUIRED glew) | ||||
|     pkg_check_modules(FFTW3 REQUIRED fftw3f) | ||||
|     pkg_check_modules(VOLK REQUIRED volk) | ||||
|     pkg_check_modules(GLFW3 REQUIRED glfw3) | ||||
|  | ||||
|     target_include_directories(sdrpp_core PUBLIC | ||||
|         ${GLEW_INCLUDE_DIRS} | ||||
|         ${FFTW3_INCLUDE_DIRS} | ||||
|         ${GLFW3_INCLUDE_DIRS} | ||||
|         ${VOLK_INCLUDE_DIRS} | ||||
|     ) | ||||
|  | ||||
|     target_link_directories(sdrpp_core PUBLIC | ||||
|         ${GLEW_LIBRARY_DIRS} | ||||
|         ${FFTW3_LIBRARY_DIRS} | ||||
|         ${GLFW3_LIBRARY_DIRS} | ||||
|         ${VOLK_LIBRARY_DIRS} | ||||
|     ) | ||||
|  | ||||
|     target_link_libraries(sdrpp_core PUBLIC | ||||
|         ${OPENGL_LIBRARIES} | ||||
|         ${GLEW_LIBRARIES} | ||||
|         ${FFTW3_LIBRARIES} | ||||
|         ${GLFW3_LIBRARIES} | ||||
|         ${VOLK_LIBRARIES} | ||||
|     ) | ||||
|  | ||||
|     if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") | ||||
|         target_link_libraries(sdrpp_core PUBLIC stdc++fs) | ||||
|     endif () | ||||
|  | ||||
|     if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") | ||||
|         set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") | ||||
|         set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++") | ||||
|     endif () | ||||
|  | ||||
| endif () | ||||
|  | ||||
| set(CORE_FILES ${RUNTIME_OUTPUT_DIRECTORY} PARENT_SCOPE) | ||||
|  | ||||
| # cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64" | ||||
							
								
								
									
										118
									
								
								core/src/config.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								core/src/config.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| #include <config.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <fstream> | ||||
| #include <filesystem> | ||||
|  | ||||
| ConfigManager::ConfigManager() { | ||||
|  | ||||
| } | ||||
|  | ||||
| ConfigManager::~ConfigManager() { | ||||
|     disableAutoSave(); | ||||
| } | ||||
|  | ||||
| void ConfigManager::setPath(std::string file) { | ||||
|     path = file; | ||||
| } | ||||
|  | ||||
| void ConfigManager::load(json def, bool lock) { | ||||
|     if (lock) { mtx.lock(); } | ||||
|     if (path == "") { | ||||
|         spdlog::error("Config manager tried to load file with no path specified"); | ||||
|         return; | ||||
|     } | ||||
|     if (!std::filesystem::exists(path)) { | ||||
|         spdlog::warn("Config file '{0}' does not exist, creating it", path); | ||||
|         conf = def; | ||||
|         save(false); | ||||
|     } | ||||
|     if (!std::filesystem::is_regular_file(path)) { | ||||
|         spdlog::error("Config file '{0}' isn't a file", path); | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     std::ifstream file(path.c_str()); | ||||
|     file >> conf; | ||||
|     file.close(); | ||||
|     if (lock) { mtx.unlock(); } | ||||
| } | ||||
|  | ||||
| void ConfigManager::save(bool lock) { | ||||
|     if (lock) { mtx.lock(); } | ||||
|     std::ofstream file(path.c_str()); | ||||
|     file << conf.dump(4); | ||||
|     file.close(); | ||||
|     if (lock) { mtx.unlock(); } | ||||
| } | ||||
|  | ||||
| void ConfigManager::enableAutoSave() { | ||||
|     if (!autoSaveEnabled) { | ||||
|         autoSaveEnabled = true; | ||||
|         autoSaveThread = std::thread(autoSaveWorker, this); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ConfigManager::disableAutoSave() { | ||||
|     if (autoSaveEnabled) { | ||||
|         autoSaveEnabled = false; | ||||
|         autoSaveThread.join(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ConfigManager::aquire() { | ||||
|     mtx.lock(); | ||||
| } | ||||
|  | ||||
| void ConfigManager::release(bool changed) { | ||||
|     this->changed |= changed; | ||||
|     mtx.unlock(); | ||||
| } | ||||
|  | ||||
| void ConfigManager::autoSaveWorker(ConfigManager* _this) { | ||||
|     while (_this->autoSaveEnabled) { | ||||
|         if (!_this->mtx.try_lock()) { | ||||
|             spdlog::warn("ConfigManager locked, waiting..."); | ||||
|             std::this_thread::sleep_for(std::chrono::milliseconds(1000)); | ||||
|             continue; | ||||
|         } | ||||
|         if (_this->changed) { | ||||
|             _this->changed = false; | ||||
|             _this->save(false); | ||||
|         } | ||||
|         _this->mtx.unlock(); | ||||
|         std::this_thread::sleep_for(std::chrono::milliseconds(1000)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // void ConfigManager::setResourceDir(std::string path) { | ||||
| //     if (!std::filesystem::exists(path)) { | ||||
| //         spdlog::error("Resource directory '{0}' does not exist", path); | ||||
| //         return; | ||||
| //     } | ||||
| //     if (!std::filesystem::is_regular_file(path)) { | ||||
| //         spdlog::error("Resource directory '{0}' is not a directory", path); | ||||
| //         return; | ||||
| //     } | ||||
| //     resDir = path; | ||||
| // } | ||||
|  | ||||
| // std::string ConfigManager::getResourceDir() { | ||||
| //     return resDir; | ||||
| // } | ||||
|  | ||||
| // void ConfigManager::setConfigDir(std::string path) { | ||||
| //     if (!std::filesystem::exists(path)) { | ||||
| //         spdlog::error("Resource directory '{0}' does not exist", path); | ||||
| //         return; | ||||
| //     } | ||||
| //     if (!std::filesystem::is_regular_file(path)) { | ||||
| //         spdlog::error("Resource directory '{0}' is not a directory", path); | ||||
| //         return; | ||||
| //     } | ||||
| //     resDir = path; | ||||
| // } | ||||
|  | ||||
| // std::string ConfigManager::getConfigDir() { | ||||
| //     return configDir; | ||||
| // } | ||||
|  | ||||
							
								
								
									
										32
									
								
								core/src/config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								core/src/config.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| #pragma once | ||||
| #include <json.hpp> | ||||
| #include <thread> | ||||
| #include <string> | ||||
| #include <mutex> | ||||
|  | ||||
| using nlohmann::json; | ||||
|  | ||||
| class ConfigManager { | ||||
| public: | ||||
|     ConfigManager(); | ||||
|     ~ConfigManager(); | ||||
|     void setPath(std::string file); | ||||
|     void load(json def, bool lock = true); | ||||
|     void save(bool lock = true); | ||||
|     void enableAutoSave(); | ||||
|     void disableAutoSave(); | ||||
|     void aquire(); | ||||
|     void release(bool changed = false); | ||||
|  | ||||
|     json conf; | ||||
|      | ||||
| private: | ||||
|     static void autoSaveWorker(ConfigManager* _this); | ||||
|  | ||||
|     std::string path = ""; | ||||
|     bool changed = false; | ||||
|     bool autoSaveEnabled = false; | ||||
|     std::thread autoSaveThread; | ||||
|     std::mutex mtx; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										358
									
								
								core/src/core.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								core/src/core.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,358 @@ | ||||
| #include "imgui.h" | ||||
| #include "imgui_impl_glfw.h" | ||||
| #include "imgui_impl_opengl3.h" | ||||
| #include <stdio.h> | ||||
| #include <GL/glew.h> | ||||
| #include <GLFW/glfw3.h> | ||||
| #include <gui/main_window.h> | ||||
| #include <gui/style.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/icons.h> | ||||
| #include <version.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <gui/widgets/bandplan.h> | ||||
| #include <stb_image.h> | ||||
| #include <config.h> | ||||
| #include <core.h> | ||||
| #include <options.h> | ||||
| #include <duktape/duktape.h> | ||||
| #include <duktape/duk_console.h> | ||||
| #include <filesystem> | ||||
|  | ||||
| #define STB_IMAGE_RESIZE_IMPLEMENTATION | ||||
| #include <stb_image_resize.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <Windows.h> | ||||
| #endif | ||||
|  | ||||
| namespace core { | ||||
|     ConfigManager configManager; | ||||
|     ScriptManager scriptManager; | ||||
|     ModuleManager moduleManager; | ||||
|  | ||||
|     void setInputSampleRate(double samplerate) { | ||||
|         // NOTE: Zoom controls won't work | ||||
|         gui::waterfall.setBandwidth(samplerate); | ||||
|         gui::waterfall.setViewOffset(0); | ||||
|         gui::waterfall.setViewBandwidth(samplerate); | ||||
|         sigpath::signalPath.setSampleRate(samplerate); | ||||
|         setViewBandwidthSlider(samplerate); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| bool maximized = false; | ||||
| bool fullScreen = false; | ||||
|  | ||||
| static void glfw_error_callback(int error, const char* description) { | ||||
|     spdlog::error("Glfw Error {0}: {1}", error, description); | ||||
| } | ||||
|  | ||||
| static void maximized_callback(GLFWwindow* window, int n) { | ||||
|     if (n == GLFW_TRUE) { | ||||
|         maximized = true; | ||||
|     } | ||||
|     else { | ||||
|         maximized = false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| duk_ret_t test_func(duk_context *ctx) { | ||||
|     printf("Hello from C++\n"); | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| // main | ||||
| int sdrpp_main(int argc, char *argv[]) { | ||||
| #ifdef _WIN32 | ||||
|     //FreeConsole(); | ||||
|     // ConfigManager::setResourceDir("./res"); | ||||
|     // ConfigManager::setConfigDir("."); | ||||
| #endif | ||||
|  | ||||
|     spdlog::info("SDR++ v" VERSION_STR); | ||||
|  | ||||
|     // Load default options and parse command line | ||||
|     options::loadDefaults(); | ||||
|     if (!options::parse(argc, argv)) { return -1; } | ||||
|  | ||||
|     // Check root directory | ||||
|     if (!std::filesystem::exists(options::opts.root)) { | ||||
|         spdlog::warn("Root directory {0} does not exist, creating it", options::opts.root); | ||||
|         if (!std::filesystem::create_directory(options::opts.root)) { | ||||
|             spdlog::error("Could not create root directory {0}", options::opts.root); | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!std::filesystem::is_directory(options::opts.root)) { | ||||
|         spdlog::error("{0} is not a directory", options::opts.root); | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // ======== DEFAULT CONFIG ======== | ||||
|     json defConfig; | ||||
|     defConfig["bandColors"]["amateur"] = "#FF0000FF"; | ||||
|     defConfig["bandColors"]["aviation"] = "#00FF00FF"; | ||||
|     defConfig["bandColors"]["broadcast"] = "#0000FFFF"; | ||||
|     defConfig["bandColors"]["marine"] = "#00FFFFFF"; | ||||
|     defConfig["bandColors"]["military"] = "#FFFF00FF"; | ||||
|     defConfig["bandPlan"] = "General"; | ||||
|     defConfig["bandPlanEnabled"] = true; | ||||
|     defConfig["centerTuning"] = false; | ||||
|     defConfig["colorMap"] = "Classic"; | ||||
|     defConfig["fftHeight"] = 300; | ||||
|     defConfig["frequency"] = 100000000.0; | ||||
|     defConfig["max"] = 0.0; | ||||
|     defConfig["maximized"] = false; | ||||
|     defConfig["menuOrder"] = { | ||||
|         "Source", | ||||
|         "Radio", | ||||
|         "Recorder", | ||||
|         "Sinks", | ||||
|         "Audio", | ||||
|         "Scripting", | ||||
|         "Band Plan", | ||||
|         "Display" | ||||
|     }; | ||||
|     defConfig["menuWidth"] = 300; | ||||
|     defConfig["min"] = -70.0; | ||||
|  | ||||
|     defConfig["moduleInstances"]["Radio"] = "radio"; | ||||
|     defConfig["moduleInstances"]["Recorder"] = "recorder"; | ||||
|     defConfig["moduleInstances"]["SoapySDR Source"] = "soapy_source"; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"] = "plutosdr_source"; | ||||
|     defConfig["moduleInstances"]["RTL-TCP Source"] = "rtl_tcp_source"; | ||||
|     defConfig["moduleInstances"]["AirspyHF+ Source"] = "airspyhf_source"; | ||||
|     defConfig["moduleInstances"]["Airspy Source"] = "airspy_source"; | ||||
|     defConfig["moduleInstances"]["HackRF Source"] = "hackrf_source"; | ||||
|     defConfig["moduleInstances"]["Audio Sink"] = "audio_sink"; | ||||
|  | ||||
|     defConfig["modules"] = json::array(); | ||||
|     defConfig["offset"] = 0.0; | ||||
|     defConfig["showWaterfall"] = true; | ||||
|     defConfig["source"] = ""; | ||||
|     defConfig["streams"] = json::object(); | ||||
|     defConfig["windowSize"]["h"] = 720; | ||||
|     defConfig["windowSize"]["w"] = 1280; | ||||
|      | ||||
|     defConfig["bandColors"]["broadcast"] = "#0000FFFF"; | ||||
|     defConfig["bandColors"]["amateur"] = "#FF0000FF"; | ||||
|     defConfig["bandColors"]["aviation"] = "#00FF00FF"; | ||||
|     defConfig["bandColors"]["marine"] = "#00FFFFFF"; | ||||
|     defConfig["bandColors"]["military"] = "#FFFF00FF"; | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|     defConfig["modulesDirectory"] = "./modules"; | ||||
|     defConfig["resourcesDirectory"] = "./res"; | ||||
| #else | ||||
|     defConfig["modulesDirectory"] = "/usr/lib/sdrpp/plugins"; | ||||
|     defConfig["resourcesDirectory"] = "/usr/share/sdrpp"; | ||||
| #endif | ||||
|  | ||||
|     // Load config | ||||
|     spdlog::info("Loading config"); | ||||
|     core::configManager.setPath(options::opts.root + "/config.json"); | ||||
|     core::configManager.load(defConfig); | ||||
|     core::configManager.enableAutoSave(); | ||||
|  | ||||
|     // Fix config | ||||
|     core::configManager.aquire(); | ||||
|     for (auto const& item : defConfig.items()) { | ||||
|         if (!core::configManager.conf.contains(item.key())) { | ||||
|             spdlog::warn("Missing key in config {0}, repairing", item.key()); | ||||
|             core::configManager.conf[item.key()] = defConfig[item.key()]; | ||||
|         } | ||||
|     } | ||||
|     core::configManager.release(true); | ||||
|  | ||||
|     // Setup window | ||||
|     glfwSetErrorCallback(glfw_error_callback); | ||||
|     if (!glfwInit()) { | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
| #ifdef __APPLE__ | ||||
|     // GL 3.2 + GLSL 150 | ||||
|     const char* glsl_version = "#version 150"; | ||||
|     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | ||||
|     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); | ||||
|     glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only | ||||
|     glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // Required on Mac | ||||
| #else | ||||
|     // GL 3.0 + GLSL 120 | ||||
|     const char* glsl_version = "#version 120"; | ||||
|     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | ||||
|     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | ||||
| #endif | ||||
|      | ||||
|     core::configManager.aquire(); | ||||
|     int winWidth = core::configManager.conf["windowSize"]["w"]; | ||||
|     int winHeight = core::configManager.conf["windowSize"]["h"]; | ||||
|     maximized = core::configManager.conf["maximized"]; | ||||
|     std::string resDir = core::configManager.conf["resourcesDirectory"]; | ||||
|     json bandColors = core::configManager.conf["bandColors"]; | ||||
|     core::configManager.release(); | ||||
|  | ||||
|     // Create window with graphics context | ||||
|     GLFWmonitor* monitor = glfwGetPrimaryMonitor(); | ||||
|     GLFWwindow* window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL); | ||||
|     if (window == NULL) | ||||
|         return 1; | ||||
|     glfwMakeContextCurrent(window); | ||||
|  | ||||
| #if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3) | ||||
|     if (maximized) { | ||||
|         glfwMaximizeWindow(window); | ||||
|     } | ||||
|  | ||||
|     glfwSetWindowMaximizeCallback(window, maximized_callback); | ||||
| #endif | ||||
|  | ||||
|     // Load app icon | ||||
|     GLFWimage icons[10]; | ||||
|     icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4); | ||||
|     icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16; | ||||
|     icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24; | ||||
|     icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32; | ||||
|     icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48; | ||||
|     icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64; | ||||
|     icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96; | ||||
|     icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128; | ||||
|     icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196; | ||||
|     icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256; | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4); | ||||
|     stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4); | ||||
|     glfwSetWindowIcon(window, 10, icons); | ||||
|     stbi_image_free(icons[0].pixels); | ||||
|     for (int i = 1; i < 10; i++) { | ||||
|         free(icons[i].pixels); | ||||
|     } | ||||
|  | ||||
|     if (glewInit() != GLEW_OK) { | ||||
|         spdlog::error("Failed to initialize OpenGL loader!"); | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // Setup Dear ImGui context | ||||
|     IMGUI_CHECKVERSION(); | ||||
|     ImGui::CreateContext(); | ||||
|     ImGuiIO& io = ImGui::GetIO(); (void)io; | ||||
|     io.IniFilename = NULL; | ||||
|  | ||||
|     // Setup Platform/Renderer bindings | ||||
|     ImGui_ImplGlfw_InitForOpenGL(window, true); | ||||
|     ImGui_ImplOpenGL3_Init(glsl_version); | ||||
|  | ||||
|     if (!style::setDarkStyle(resDir)) { return -1; } | ||||
|  | ||||
|     LoadingScreen::setWindow(window); | ||||
|  | ||||
|     LoadingScreen::show("Loading icons"); | ||||
|     spdlog::info("Loading icons"); | ||||
|     if (!icons::load(resDir)) { return -1; } | ||||
|  | ||||
|     LoadingScreen::show("Loading band plans"); | ||||
|     spdlog::info("Loading band plans"); | ||||
|     bandplan::loadFromDir(resDir + "/bandplans"); | ||||
|  | ||||
|     LoadingScreen::show("Loading band plan colors"); | ||||
|     spdlog::info("Loading band plans color table"); | ||||
|     bandplan::loadColorTable(bandColors); | ||||
|  | ||||
|     windowInit(); | ||||
|  | ||||
|     spdlog::info("Ready."); | ||||
|  | ||||
|     bool _maximized = maximized; | ||||
|     int fsWidth, fsHeight, fsPosX, fsPosY; | ||||
|  | ||||
|     // Main loop | ||||
|     while (!glfwWindowShouldClose(window)) { | ||||
|         glfwPollEvents(); | ||||
|  | ||||
|         // Start the Dear ImGui frame | ||||
|         ImGui_ImplOpenGL3_NewFrame(); | ||||
|         ImGui_ImplGlfw_NewFrame(); | ||||
|         ImGui::NewFrame(); | ||||
|  | ||||
|         //ImGui::ShowDemoWindow(); | ||||
|  | ||||
|         if (_maximized != maximized) { | ||||
|             _maximized = maximized; | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["maximized"]= _maximized; | ||||
|             if (!maximized) { | ||||
|                 glfwSetWindowSize(window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]); | ||||
|             } | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         int _winWidth, _winHeight; | ||||
|         glfwGetWindowSize(window, &_winWidth, &_winHeight); | ||||
|  | ||||
|         if (ImGui::IsKeyPressed(GLFW_KEY_F11)) { | ||||
|             fullScreen = !fullScreen; | ||||
|             if (fullScreen) { | ||||
|                 spdlog::info("Fullscreen: ON"); | ||||
|                 fsWidth = _winWidth; | ||||
|                 fsHeight = _winHeight; | ||||
|                 glfwGetWindowPos(window, &fsPosX, &fsPosY); | ||||
|                 const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); | ||||
|                 glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0); | ||||
|             } | ||||
|             else { | ||||
|                 spdlog::info("Fullscreen: OFF"); | ||||
|                 glfwSetWindowMonitor(window, nullptr,  fsPosX, fsPosY, fsWidth, fsHeight, 0); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) { | ||||
|             winWidth = _winWidth; | ||||
|             winHeight = _winHeight; | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["windowSize"]["w"] = winWidth; | ||||
|             core::configManager.conf["windowSize"]["h"] = winHeight; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         if (winWidth > 0 && winHeight > 0) { | ||||
|             ImGui::SetNextWindowPos(ImVec2(0, 0)); | ||||
|             ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight)); | ||||
|             drawWindow(); | ||||
|         } | ||||
|  | ||||
|         // Rendering | ||||
|         ImGui::Render(); | ||||
|         int display_w, display_h; | ||||
|         glfwGetFramebufferSize(window, &display_w, &display_h); | ||||
|         glViewport(0, 0, display_w, display_h); | ||||
|         glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f); | ||||
|         //glClearColor(0.9f, 0.9f, 0.9f, 1.0f); | ||||
|         glClear(GL_COLOR_BUFFER_BIT); | ||||
|         ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); | ||||
|  | ||||
|         glfwSwapInterval(1); // Enable vsync | ||||
|         glfwSwapBuffers(window); | ||||
|     } | ||||
|  | ||||
|     // Cleanup | ||||
|     ImGui_ImplOpenGL3_Shutdown(); | ||||
|     ImGui_ImplGlfw_Shutdown(); | ||||
|     ImGui::DestroyContext(); | ||||
|  | ||||
|     glfwDestroyWindow(window); | ||||
|     glfwTerminate(); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										15
									
								
								core/src/core.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								core/src/core.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| #pragma once | ||||
| #include <config.h> | ||||
| #include <module.h> | ||||
| #include <scripting.h> | ||||
| #include <module.h> | ||||
|  | ||||
| namespace core { | ||||
|     SDRPP_EXPORT ConfigManager configManager; | ||||
|     SDRPP_EXPORT ScriptManager scriptManager; | ||||
|     SDRPP_EXPORT ModuleManager moduleManager; | ||||
|  | ||||
|     void setInputSampleRate(double samplerate); | ||||
| }; | ||||
|  | ||||
| int sdrpp_main(int argc, char *argv[]); | ||||
							
								
								
									
										33
									
								
								core/src/credits.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								core/src/credits.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| #include <credits.h> | ||||
|  | ||||
| namespace sdrpp_credits { | ||||
|     const char* contributors[] = { | ||||
|         "Ryzerth (Author)", | ||||
|         "aosync", | ||||
|         "Alexsey Shestacov", | ||||
|         "Benjamin Kyd", | ||||
|         "Cropinghigh", | ||||
|         "Howard0su", | ||||
|         "Martin Hauke", | ||||
|         "Raov", | ||||
|         "Szymon Zakrent", | ||||
|         "Tobias Mädel" | ||||
|     }; | ||||
|  | ||||
|     const char* libraries[] = { | ||||
|         "Dear ImGui (ocornut)", | ||||
|         "json (nlohmann)", | ||||
|         "portaudio (P.A. comm.)", | ||||
|         "SoapySDR (PothosWare)", | ||||
|         "spdlog (gabime)", | ||||
|     }; | ||||
|  | ||||
|     const char* patrons[] = { | ||||
|         "SignalsEverywhere", | ||||
|         "Lee Donaghy" | ||||
|     }; | ||||
|  | ||||
|     const int contributorCount = sizeof(contributors) / sizeof(char*); | ||||
|     const int libraryCount = sizeof(libraries) / sizeof(char*); | ||||
|     const int patronCount = sizeof(patrons) / sizeof(char*); | ||||
| } | ||||
							
								
								
									
										11
									
								
								core/src/credits.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								core/src/credits.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| #pragma once | ||||
| #include <module.h> | ||||
|  | ||||
| namespace sdrpp_credits { | ||||
|     SDRPP_EXPORT const char* contributors[]; | ||||
|     SDRPP_EXPORT const char* libraries[]; | ||||
|     SDRPP_EXPORT const char* patrons[]; | ||||
|     SDRPP_EXPORT const int contributorCount; | ||||
|     SDRPP_EXPORT const int libraryCount; | ||||
|     SDRPP_EXPORT const int patronCount; | ||||
| } | ||||
							
								
								
									
										93
									
								
								core/src/dsp/audio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								core/src/dsp/audio.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     class MonoToStereo : public generic_block<MonoToStereo> { | ||||
|     public: | ||||
|         MonoToStereo() {} | ||||
|  | ||||
|         MonoToStereo(stream<float>* in) { init(in); } | ||||
|  | ||||
|         ~MonoToStereo() { generic_block<MonoToStereo>::stop(); } | ||||
|  | ||||
|         void init(stream<float>* in) { | ||||
|             _in = in; | ||||
|             generic_block<MonoToStereo>::registerInput(_in); | ||||
|             generic_block<MonoToStereo>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<float>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<MonoToStereo>::ctrlMtx); | ||||
|             generic_block<MonoToStereo>::tempStop(); | ||||
|             generic_block<MonoToStereo>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<MonoToStereo>::registerInput(_in); | ||||
|             generic_block<MonoToStereo>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out.writeBuf[i].l = _in->readBuf[i]; | ||||
|                 out.writeBuf[i].r = _in->readBuf[i]; | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<stereo_t> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         stream<float>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class StereoToMono : public generic_block<StereoToMono> { | ||||
|     public: | ||||
|         StereoToMono() {} | ||||
|  | ||||
|         StereoToMono(stream<stereo_t>* in) { init(in); } | ||||
|  | ||||
|         ~StereoToMono() { generic_block<StereoToMono>::stop(); } | ||||
|  | ||||
|         void init(stream<stereo_t>* in) { | ||||
|             _in = in; | ||||
|             generic_block<StereoToMono>::registerInput(_in); | ||||
|             generic_block<StereoToMono>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<stereo_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<StereoToMono>::ctrlMtx); | ||||
|             generic_block<StereoToMono>::tempStop(); | ||||
|             generic_block<StereoToMono>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<StereoToMono>::registerInput(_in); | ||||
|             generic_block<StereoToMono>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out.writeBuf[i] = (_in->readBuf[i].l + _in->readBuf[i].r) / 2.0f; | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         stream<stereo_t>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										128
									
								
								core/src/dsp/block.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								core/src/dsp/block.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| #pragma once | ||||
| #include <stdio.h> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/types.h> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
| #include <algorithm> | ||||
|  | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| #define FL_M_PI                3.1415926535f | ||||
|  | ||||
| namespace dsp { | ||||
|      | ||||
|     template <class BLOCK> | ||||
|     class generic_block { | ||||
|     public: | ||||
|         virtual void init() {} | ||||
|  | ||||
|         virtual ~generic_block() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         virtual void start() { | ||||
|             std::lock_guard<std::mutex> lck(ctrlMtx); | ||||
|             if (running) { | ||||
|                 return; | ||||
|             } | ||||
|             running = true; | ||||
|             doStart(); | ||||
|         } | ||||
|  | ||||
|         virtual void stop() { | ||||
|             std::lock_guard<std::mutex> lck(ctrlMtx); | ||||
|             if (!running) { | ||||
|                 return; | ||||
|             } | ||||
|             doStop(); | ||||
|             running = false; | ||||
|         } | ||||
|  | ||||
|         virtual int calcOutSize(int inSize) { return inSize; } | ||||
|  | ||||
|         virtual int run() = 0; | ||||
|          | ||||
|         friend BLOCK; | ||||
|  | ||||
|     private: | ||||
|         void workerLoop() {  | ||||
|             while (run() >= 0); | ||||
|         } | ||||
|  | ||||
|         void aquire() { | ||||
|             ctrlMtx.lock(); | ||||
|         } | ||||
|  | ||||
|         void release() { | ||||
|             ctrlMtx.unlock(); | ||||
|         } | ||||
|  | ||||
|         void registerInput(untyped_steam* inStream) { | ||||
|             inputs.push_back(inStream); | ||||
|         } | ||||
|  | ||||
|         void unregisterInput(untyped_steam* inStream) { | ||||
|             inputs.erase(std::remove(inputs.begin(), inputs.end(), inStream), inputs.end()); | ||||
|         } | ||||
|  | ||||
|         void registerOutput(untyped_steam* outStream) { | ||||
|             outputs.push_back(outStream); | ||||
|         } | ||||
|  | ||||
|         void unregisterOutput(untyped_steam* outStream) { | ||||
|             outputs.erase(std::remove(outputs.begin(), outputs.end(), outStream), outputs.end()); | ||||
|         } | ||||
|  | ||||
|         virtual void doStart() { | ||||
|             workerThread = std::thread(&generic_block<BLOCK>::workerLoop, this); | ||||
|         } | ||||
|  | ||||
|         virtual void doStop() { | ||||
|             for (auto& in : inputs) { | ||||
|                 in->stopReader(); | ||||
|             } | ||||
|             for (auto& out : outputs) { | ||||
|                 out->stopWriter(); | ||||
|             } | ||||
|  | ||||
|             // TODO: Make sure this isn't needed, I don't know why it stops | ||||
|             if (workerThread.joinable()) { | ||||
|                 workerThread.join(); | ||||
|             } | ||||
|  | ||||
|             for (auto& in : inputs) { | ||||
|                 in->clearReadStop(); | ||||
|             } | ||||
|             for (auto& out : outputs) { | ||||
|                 out->clearWriteStop(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         void tempStart() { | ||||
|             if (tempStopped) { | ||||
|                 doStart(); | ||||
|                 tempStopped = false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         void tempStop() { | ||||
|             if (running && !tempStopped) { | ||||
|                 doStop(); | ||||
|                 tempStopped = true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         std::vector<untyped_steam*> inputs; | ||||
|         std::vector<untyped_steam*> outputs; | ||||
|  | ||||
|         bool running = false; | ||||
|         bool tempStopped = false; | ||||
|  | ||||
|         std::thread workerThread; | ||||
|  | ||||
|     protected: | ||||
|         std::mutex ctrlMtx; | ||||
|  | ||||
|     }; | ||||
| } | ||||
| @@ -1,33 +1,23 @@ | ||||
| #pragma once | ||||
| #include <condition_variable> | ||||
| #include <algorithm> | ||||
| #include <math.h> | ||||
| #include <dsp/block.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #define STREAM_BUF_SZ   1000000 | ||||
| #define RING_BUF_SZ 1000000 | ||||
| 
 | ||||
| namespace dsp { | ||||
|     template <class T> | ||||
|     class stream { | ||||
|     class RingBuffer { | ||||
|     public: | ||||
|         stream() { | ||||
|         RingBuffer() { | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         stream(int maxLatency) { | ||||
|             size = STREAM_BUF_SZ; | ||||
|             _buffer = new T[size]; | ||||
|             _stopReader = false; | ||||
|             _stopWriter = false; | ||||
|             this->maxLatency = maxLatency; | ||||
|             writec = 0; | ||||
|             readc = 0; | ||||
|             readable = 0; | ||||
|             writable = size; | ||||
|             memset(_buffer, 0, size * sizeof(T)); | ||||
|         } | ||||
|         RingBuffer(int maxLatency) { init(maxLatency); } | ||||
| 
 | ||||
|         ~RingBuffer() { delete _buffer; } | ||||
| 
 | ||||
|         void init(int maxLatency) { | ||||
|             size = STREAM_BUF_SZ; | ||||
|             size = RING_BUF_SZ; | ||||
|             _buffer = new T[size]; | ||||
|             _stopReader = false; | ||||
|             _stopWriter = false; | ||||
							
								
								
									
										185
									
								
								core/src/dsp/convertion.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								core/src/dsp/convertion.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     class ComplexToStereo : public generic_block<ComplexToStereo> { | ||||
|     public: | ||||
|         ComplexToStereo() {} | ||||
|  | ||||
|         ComplexToStereo(stream<complex_t>* in) { init(in); } | ||||
|  | ||||
|         ~ComplexToStereo() { generic_block<ComplexToStereo>::stop(); } | ||||
|  | ||||
|         static_assert(sizeof(complex_t) == sizeof(stereo_t)); | ||||
|  | ||||
|         void init(stream<complex_t>* in) { | ||||
|             _in = in; | ||||
|             generic_block<ComplexToStereo>::registerInput(_in); | ||||
|             generic_block<ComplexToStereo>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<ComplexToStereo>::ctrlMtx); | ||||
|             generic_block<ComplexToStereo>::tempStop(); | ||||
|             generic_block<ComplexToStereo>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<ComplexToStereo>::registerInput(_in); | ||||
|             generic_block<ComplexToStereo>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t)); | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<stereo_t> out; | ||||
|  | ||||
|     private: | ||||
|         float avg; | ||||
|         int count; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class ComplexToReal : public generic_block<ComplexToReal> { | ||||
|     public: | ||||
|         ComplexToReal() {} | ||||
|  | ||||
|         ComplexToReal(stream<complex_t>* in) { init(in); } | ||||
|  | ||||
|         ~ComplexToReal() { generic_block<ComplexToReal>::stop(); } | ||||
|  | ||||
|         void init(stream<complex_t>* in) { | ||||
|             _in = in; | ||||
|             generic_block<ComplexToReal>::registerInput(_in); | ||||
|             generic_block<ComplexToReal>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<ComplexToReal>::ctrlMtx); | ||||
|             generic_block<ComplexToReal>::tempStop(); | ||||
|             generic_block<ComplexToReal>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<ComplexToReal>::registerInput(_in); | ||||
|             generic_block<ComplexToReal>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             volk_32fc_deinterleave_real_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         float avg; | ||||
|         int count; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class ComplexToImag : public generic_block<ComplexToImag> { | ||||
|     public: | ||||
|         ComplexToImag() {} | ||||
|  | ||||
|         ComplexToImag(stream<complex_t>* in) { init(in); } | ||||
|  | ||||
|         ~ComplexToImag() { generic_block<ComplexToImag>::stop(); } | ||||
|  | ||||
|         void init(stream<complex_t>* in) { | ||||
|             _in = in; | ||||
|             generic_block<ComplexToImag>::registerInput(_in); | ||||
|             generic_block<ComplexToImag>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<ComplexToImag>::ctrlMtx); | ||||
|             generic_block<ComplexToImag>::tempStop(); | ||||
|             generic_block<ComplexToImag>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<ComplexToImag>::registerInput(_in); | ||||
|             generic_block<ComplexToImag>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             volk_32fc_deinterleave_imag_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             if(!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         float avg; | ||||
|         int count; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     class RealToComplex : public generic_block<RealToComplex> { | ||||
|     public: | ||||
|         RealToComplex() {} | ||||
|  | ||||
|         RealToComplex(stream<float>* in) { init(in); } | ||||
|  | ||||
|         ~RealToComplex() { | ||||
|             delete[] nullBuffer; | ||||
|             generic_block<RealToComplex>::stop(); | ||||
|         } | ||||
|  | ||||
|         void init(stream<float>* in) { | ||||
|             _in = in; | ||||
|             nullBuffer = new float[STREAM_BUFFER_SIZE]; | ||||
|             memset(nullBuffer, 0, STREAM_BUFFER_SIZE * sizeof(float)); | ||||
|             generic_block<RealToComplex>::registerInput(_in); | ||||
|             generic_block<RealToComplex>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<float>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<RealToComplex>::ctrlMtx); | ||||
|             generic_block<RealToComplex>::tempStop(); | ||||
|             generic_block<RealToComplex>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<RealToComplex>::registerInput(_in); | ||||
|             generic_block<RealToComplex>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, nullBuffer, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<complex_t> out; | ||||
|  | ||||
|     private: | ||||
|         float avg; | ||||
|         int count; | ||||
|         float* nullBuffer; | ||||
|         stream<float>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										279
									
								
								core/src/dsp/demodulator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								core/src/dsp/demodulator.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <volk/volk.h> | ||||
|  | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| #define FAST_ATAN2_COEF1    FL_M_PI / 4.0f | ||||
| #define FAST_ATAN2_COEF2    3.0f * FAST_ATAN2_COEF1 | ||||
|  | ||||
| inline float fast_arctan2(float y, float x) { | ||||
|     float abs_y = fabsf(y); | ||||
|     float r, angle; | ||||
|     if (x == 0.0f && y == 0.0f) { return 0.0f; } | ||||
|     if (x>=0.0f) { | ||||
|         r = (x - abs_y) / (x + abs_y); | ||||
|         angle = FAST_ATAN2_COEF1 - FAST_ATAN2_COEF1 * r; | ||||
|     } | ||||
|     else { | ||||
|         r = (x + abs_y) / (abs_y - x); | ||||
|         angle = FAST_ATAN2_COEF2 - FAST_ATAN2_COEF1 * r; | ||||
|     } | ||||
|     if (y < 0.0f) { | ||||
|         return -angle; | ||||
|     } | ||||
|    return angle; | ||||
| } | ||||
|  | ||||
| namespace dsp { | ||||
|     class FMDemod : public generic_block<FMDemod> { | ||||
|     public: | ||||
|         FMDemod() {} | ||||
|  | ||||
|         FMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); } | ||||
|  | ||||
|         ~FMDemod() { generic_block<FMDemod>::stop(); } | ||||
|  | ||||
|         void init(stream<complex_t>* in, float sampleRate, float deviation) { | ||||
|             _in = in; | ||||
|             _sampleRate = sampleRate; | ||||
|             _deviation = deviation; | ||||
|             phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation); | ||||
|             generic_block<FMDemod>::registerInput(_in); | ||||
|             generic_block<FMDemod>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx); | ||||
|             generic_block<FMDemod>::tempStop(); | ||||
|             generic_block<FMDemod>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<FMDemod>::registerInput(_in); | ||||
|             generic_block<FMDemod>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setSampleRate(float sampleRate) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx); | ||||
|             generic_block<FMDemod>::tempStop(); | ||||
|             _sampleRate = sampleRate; | ||||
|             phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation); | ||||
|             generic_block<FMDemod>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         float getSampleRate() { | ||||
|             return _sampleRate; | ||||
|         } | ||||
|  | ||||
|         void setDeviation(float deviation) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx); | ||||
|             generic_block<FMDemod>::tempStop(); | ||||
|             _deviation = deviation; | ||||
|             phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation); | ||||
|             generic_block<FMDemod>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         float getDeviation() { | ||||
|             return _deviation; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             // This is somehow faster than volk... | ||||
|  | ||||
|             float diff, currentPhase; | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 currentPhase = fast_arctan2(_in->readBuf[i].i, _in->readBuf[i].q); | ||||
|                 diff = currentPhase - phase; | ||||
|                 if (diff > 3.1415926535f)        { diff -= 2 * 3.1415926535f; } | ||||
|                 else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; } | ||||
|                 out.writeBuf[i] = diff / phasorSpeed; | ||||
|                 phase = currentPhase; | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float phase, phasorSpeed, _sampleRate, _deviation; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class AMDemod : public generic_block<AMDemod> { | ||||
|     public: | ||||
|         AMDemod() {} | ||||
|  | ||||
|         AMDemod(stream<complex_t>* in) { init(in); } | ||||
|  | ||||
|         ~AMDemod() { generic_block<AMDemod>::stop(); } | ||||
|  | ||||
|         void init(stream<complex_t>* in) { | ||||
|             _in = in; | ||||
|             generic_block<AMDemod>::registerInput(_in); | ||||
|             generic_block<AMDemod>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<AMDemod>::ctrlMtx); | ||||
|             generic_block<AMDemod>::tempStop(); | ||||
|             generic_block<AMDemod>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<AMDemod>::registerInput(_in); | ||||
|             generic_block<AMDemod>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             volk_32fc_magnitude_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|  | ||||
|             volk_32f_accumulator_s32f(&avg, out.writeBuf, count); | ||||
|             avg /= (float)count; | ||||
|  | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out.writeBuf[i] -= avg; | ||||
|             } | ||||
|  | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         float avg; | ||||
|         int count; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class SSBDemod : public generic_block<SSBDemod> { | ||||
|     public: | ||||
|         SSBDemod() {} | ||||
|  | ||||
|         SSBDemod(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) { init(in, sampleRate, bandWidth, mode); } | ||||
|  | ||||
|         ~SSBDemod() { | ||||
|             generic_block<SSBDemod>::stop(); | ||||
|             delete[] buffer; | ||||
|         } | ||||
|  | ||||
|         enum { | ||||
|             MODE_USB, | ||||
|             MODE_LSB, | ||||
|             MODE_DSB | ||||
|         }; | ||||
|  | ||||
|         void init(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) { | ||||
|             _in = in; | ||||
|             _sampleRate = sampleRate; | ||||
|             _bandWidth = bandWidth; | ||||
|             _mode = mode; | ||||
|             phase = lv_cmake(1.0f, 0.0f); | ||||
|             switch (_mode) { | ||||
|             case MODE_USB: | ||||
|                 phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_LSB: | ||||
|                 phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_DSB: | ||||
|                 phaseDelta = lv_cmake(1.0f, 0.0f); | ||||
|                 break; | ||||
|             } | ||||
|             buffer = new lv_32fc_t[STREAM_BUFFER_SIZE]; | ||||
|             generic_block<SSBDemod>::registerInput(_in); | ||||
|             generic_block<SSBDemod>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<SSBDemod>::ctrlMtx); | ||||
|             generic_block<SSBDemod>::tempStop(); | ||||
|             generic_block<SSBDemod>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<SSBDemod>::registerInput(_in); | ||||
|             generic_block<SSBDemod>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setSampleRate(float sampleRate) { | ||||
|             // No need to restart | ||||
|             _sampleRate = sampleRate; | ||||
|             switch (_mode) { | ||||
|             case MODE_USB: | ||||
|                 phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_LSB: | ||||
|                 phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_DSB: | ||||
|                 phaseDelta = lv_cmake(1.0f, 0.0f); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         void setBandWidth(float bandWidth) { | ||||
|             // No need to restart | ||||
|             _bandWidth = bandWidth; | ||||
|             switch (_mode) { | ||||
|             case MODE_USB: | ||||
|                 phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_LSB: | ||||
|                 phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_DSB: | ||||
|                 phaseDelta = lv_cmake(1.0f, 0.0f); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         void setMode(int mode) { | ||||
|             _mode = mode; | ||||
|             switch (_mode) { | ||||
|             case MODE_USB: | ||||
|                 phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_LSB: | ||||
|                 phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI)); | ||||
|                 break; | ||||
|             case MODE_DSB: | ||||
|                 phaseDelta = lv_cmake(1.0f, 0.0f); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count); | ||||
|             volk_32fc_deinterleave_real_32f(out.writeBuf, buffer, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         int _mode; | ||||
|         float _sampleRate, _bandWidth; | ||||
|         stream<complex_t>* _in; | ||||
|         lv_32fc_t* buffer; | ||||
|         lv_32fc_t phase; | ||||
|         lv_32fc_t phaseDelta; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										168
									
								
								core/src/dsp/filter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								core/src/dsp/filter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <dsp/window.h> | ||||
| #include <string.h> | ||||
|  | ||||
| namespace dsp { | ||||
|  | ||||
|     template <class T> | ||||
|     class FIR : public generic_block<FIR<T>> { | ||||
|     public: | ||||
|         FIR() {} | ||||
|  | ||||
|         FIR(stream<T>* in, dsp::filter_window::generic_window* window) { init(in, window); } | ||||
|  | ||||
|         ~FIR() { | ||||
|             generic_block<FIR<T>>::stop(); | ||||
|             volk_free(buffer); | ||||
|             volk_free(taps); | ||||
|         } | ||||
|  | ||||
|         void init(stream<T>* in, dsp::filter_window::generic_window* window) { | ||||
|             _in = in; | ||||
|  | ||||
|             tapCount = window->getTapCount(); | ||||
|             taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment()); | ||||
|             window->createTaps(taps, tapCount); | ||||
|  | ||||
|             buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment()); | ||||
|             bufStart = &buffer[tapCount]; | ||||
|             generic_block<FIR<T>>::registerInput(_in); | ||||
|             generic_block<FIR<T>>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<FIR<T>>::ctrlMtx); | ||||
|             generic_block<FIR<T>>::tempStop(); | ||||
|             generic_block<FIR<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<FIR<T>>::registerInput(_in); | ||||
|             generic_block<FIR<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void updateWindow(dsp::filter_window::generic_window* window) { | ||||
|             _window = window; | ||||
|             volk_free(taps); | ||||
|             tapCount = window->getTapCount(); | ||||
|             taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment()); | ||||
|             window->createTaps(taps, tapCount); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             memcpy(bufStart, _in->readBuf, count * sizeof(T)); | ||||
|             _in->flush(); | ||||
|  | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 for (int i = 0; i < count; i++) { | ||||
|                     volk_32f_x2_dot_prod_32f((float*)&out.writeBuf[i], (float*)&buffer[i+1], taps, tapCount); | ||||
|                 } | ||||
|             } | ||||
|             if constexpr (std::is_same_v<T, complex_t>) { | ||||
|                 for (int i = 0; i < count; i++) { | ||||
|                     volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[i], (lv_32fc_t*)&buffer[i+1], taps, tapCount); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|  | ||||
|             memmove(buffer, &buffer[count], tapCount * sizeof(T)); | ||||
|  | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<T> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         stream<T>* _in; | ||||
|  | ||||
|         dsp::filter_window::generic_window* _window; | ||||
|  | ||||
|         T* bufStart; | ||||
|         T* buffer; | ||||
|         int tapCount; | ||||
|         float* taps; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class BFMDeemp : public generic_block<BFMDeemp> { | ||||
|     public: | ||||
|         BFMDeemp() {} | ||||
|  | ||||
|         BFMDeemp(stream<float>* in, float sampleRate, float tau) { init(in, sampleRate, tau); } | ||||
|  | ||||
|         ~BFMDeemp() { generic_block<BFMDeemp>::stop(); } | ||||
|  | ||||
|         void init(stream<float>* in, float sampleRate, float tau) { | ||||
|             _in = in; | ||||
|             _sampleRate = sampleRate; | ||||
|             _tau = tau; | ||||
|             float dt = 1.0f / _sampleRate; | ||||
|             alpha = dt / (_tau + dt); | ||||
|             generic_block<BFMDeemp>::registerInput(_in); | ||||
|             generic_block<BFMDeemp>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<float>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<BFMDeemp>::ctrlMtx); | ||||
|             generic_block<BFMDeemp>::tempStop(); | ||||
|             generic_block<BFMDeemp>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<BFMDeemp>::registerInput(_in); | ||||
|             generic_block<BFMDeemp>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setSampleRate(float sampleRate) { | ||||
|             _sampleRate = sampleRate; | ||||
|             float dt = 1.0f / _sampleRate; | ||||
|             alpha = dt / (_tau + dt); | ||||
|         } | ||||
|  | ||||
|         void setTau(float tau) { | ||||
|             _tau = tau; | ||||
|             float dt = 1.0f / _sampleRate; | ||||
|             alpha = dt / (_tau + dt); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             if (bypass) { | ||||
|                 memcpy(out.writeBuf, _in->readBuf, count * sizeof(float)); | ||||
|                 _in->flush(); | ||||
|                 if (!out.swap(count)) { return -1; } | ||||
|                 return count; | ||||
|             } | ||||
|  | ||||
|             if (isnan(lastOut)) { | ||||
|                 lastOut = 0.0f; | ||||
|             } | ||||
|             out.writeBuf[0] = (alpha * _in->readBuf[0]) + ((1-alpha) * lastOut); | ||||
|             for (int i = 1; i < count; i++) { | ||||
|                 out.writeBuf[i] = (alpha * _in->readBuf[i]) + ((1 - alpha) * out.writeBuf[i - 1]); | ||||
|             } | ||||
|             lastOut = out.writeBuf[count - 1]; | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         bool bypass = false; | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float lastOut = 0.0f; | ||||
|         float alpha; | ||||
|         float _tau; | ||||
|         float _sampleRate; | ||||
|         stream<float>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										105
									
								
								core/src/dsp/math.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								core/src/dsp/math.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <volk/volk.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     template <class T> | ||||
|     class Add : public generic_block<Add<T>> { | ||||
|     public: | ||||
|         Add() {} | ||||
|  | ||||
|         Add(stream<T>* a, stream<T>* b) { init(a, b); } | ||||
|  | ||||
|         ~Add() { generic_block<Add>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* a, stream<T>* b) { | ||||
|             _a = a; | ||||
|             _b = b; | ||||
|             generic_block<Add>::registerInput(a); | ||||
|             generic_block<Add>::registerInput(b); | ||||
|             generic_block<Add>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             a_count = _a->read(); | ||||
|             if (a_count < 0) { return -1; } | ||||
|             b_count = _b->read(); | ||||
|             if (b_count < 0) { return -1; } | ||||
|             if (a_count != b_count) { | ||||
|                 _a->flush(); | ||||
|                 _b->flush(); | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) { | ||||
|                 volk_32fc_x2_add_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count); | ||||
|             } | ||||
|             else { | ||||
|                 volk_32f_x2_add_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count); | ||||
|             } | ||||
|  | ||||
|             _a->flush(); | ||||
|             _b->flush(); | ||||
|             if (!out.swap(a_count)) { return -1; } | ||||
|             return a_count; | ||||
|         } | ||||
|  | ||||
|         stream<T> out; | ||||
|  | ||||
|     private: | ||||
|         int a_count, b_count; | ||||
|         stream<T>* _a; | ||||
|         stream<T>* _b; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     template <class T> | ||||
|     class Multiply : public generic_block<Multiply<T>> { | ||||
|     public: | ||||
|         Multiply() {} | ||||
|  | ||||
|         Multiply(stream<T>* a, stream<T>* b) { init(a, b); } | ||||
|  | ||||
|         ~Multiply() { generic_block<Multiply>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* a, stream<T>* b) { | ||||
|             _a = a; | ||||
|             _b = b; | ||||
|             generic_block<Multiply>::registerInput(a); | ||||
|             generic_block<Multiply>::registerInput(b); | ||||
|             generic_block<Multiply>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             a_count = _a->read(); | ||||
|             if (a_count < 0) { return -1; } | ||||
|             b_count = _b->read(); | ||||
|             if (b_count < 0) { return -1; } | ||||
|             if (a_count != b_count) { | ||||
|                 _a->flush(); | ||||
|                 _b->flush(); | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             if constexpr (std::is_same_v<T, complex_t>) { | ||||
|                 volk_32fc_x2_multiply_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count); | ||||
|             } | ||||
|             else { | ||||
|                 volk_32f_x2_multiply_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count); | ||||
|             } | ||||
|  | ||||
|             _a->flush(); | ||||
|             _b->flush(); | ||||
|             if (!out.swap(a_count)) { return -1; } | ||||
|             return a_count; | ||||
|         } | ||||
|  | ||||
|         stream<T> out; | ||||
|  | ||||
|     private: | ||||
|         int a_count, b_count; | ||||
|         stream<T>* _a; | ||||
|         stream<T>* _b; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										99
									
								
								core/src/dsp/measure.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								core/src/dsp/measure.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <fftw3.h> | ||||
| #include <volk/volk.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <dsp/types.h> | ||||
| #include <string.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     class VolumeMeasure : public generic_block<VolumeMeasure> { | ||||
|     public: | ||||
|         VolumeMeasure() {} | ||||
|  | ||||
|         VolumeMeasure(stream<stereo_t>* in) { init(in); } | ||||
|  | ||||
|         ~VolumeMeasure() { | ||||
|             generic_block<VolumeMeasure>::stop(); | ||||
|             delete[] leftBuf; | ||||
|             delete[] rightBuf; | ||||
|         } | ||||
|  | ||||
|         void init(stream<stereo_t>* in) { | ||||
|             _in = in; | ||||
|             leftBuf = new float[STREAM_BUFFER_SIZE]; | ||||
|             rightBuf = new float[STREAM_BUFFER_SIZE]; | ||||
|             generic_block<VolumeMeasure>::registerInput(_in); | ||||
|             generic_block<VolumeMeasure>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<stereo_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<VolumeMeasure>::ctrlMtx); | ||||
|             generic_block<VolumeMeasure>::tempStop(); | ||||
|             generic_block<VolumeMeasure>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<VolumeMeasure>::registerInput(_in); | ||||
|             generic_block<VolumeMeasure>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             memcpy(out.writeBuf, _in->readBuf, count * sizeof(stereo_t)); | ||||
|             volk_32fc_deinterleave_32f_x2(leftBuf, rightBuf, (lv_32fc_t*)_in->readBuf, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|  | ||||
|             // Get peak from last value | ||||
|             float time = (float)count / sampleRate; | ||||
|             peak.l -= peakFall * time; | ||||
|             peak.r -= peakFall * time; | ||||
|             stereo_t _peak; | ||||
|             _peak.l = powf(10, peak.l / 10.0f); | ||||
|             _peak.r = powf(10, peak.r / 10.0f); | ||||
|  | ||||
|             stereo_t _average; | ||||
|  | ||||
|             // Calculate average | ||||
|             volk_32f_s32f_power_32f(leftBuf, leftBuf, 2, count); | ||||
|             volk_32f_s32f_power_32f(rightBuf, rightBuf, 2, count); | ||||
|             volk_32f_sqrt_32f(leftBuf, leftBuf, count); | ||||
|             volk_32f_sqrt_32f(rightBuf, rightBuf, count); | ||||
|             volk_32f_accumulator_s32f(&_average.l, leftBuf, count); | ||||
|             volk_32f_accumulator_s32f(&_average.r, rightBuf, count); | ||||
|             _average.l /= (float)count; | ||||
|             _average.r /= (float)count; | ||||
|  | ||||
|             // Calculate peak | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 if (leftBuf[i] > _peak.l) { _peak.l = leftBuf[i]; } | ||||
|                 if (rightBuf[i] > _peak.r) { _peak.r = rightBuf[i]; } | ||||
|             } | ||||
|  | ||||
|             // Assign | ||||
|             peak.l = 10.0f * log10f(_peak.l); | ||||
|             peak.r = 10.0f * log10f(_peak.r); | ||||
|             average.l = (average.l * (1.0f - avgFilt)) + (10.0f * log10f(_average.l) * avgFilt); | ||||
|             average.r = (average.r * (1.0f - avgFilt)) + (10.0f * log10f(_average.r) * avgFilt); | ||||
|  | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<stereo_t> out; | ||||
|  | ||||
|         stereo_t peak = {0, 0}; | ||||
|         stereo_t average = {0, 0}; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float peakFall = 10.0f; // dB/S | ||||
|         float avgFilt  = 0.2f; // IIR filter coef | ||||
|         float sampleRate = 48000; | ||||
|         stream<stereo_t>* _in; | ||||
|  | ||||
|         float* leftBuf; | ||||
|         float* rightBuf; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										300
									
								
								core/src/dsp/processing.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								core/src/dsp/processing.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,300 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <volk/volk.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <string.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     template <class T> | ||||
|     class FrequencyXlator : public generic_block<FrequencyXlator<T>> { | ||||
|     public: | ||||
|         FrequencyXlator() {} | ||||
|  | ||||
|         FrequencyXlator(stream<complex_t>* in, float sampleRate, float freq) { init(in, sampleRate, freq); } | ||||
|  | ||||
|         ~FrequencyXlator() { | ||||
|             generic_block<FrequencyXlator<T>>::stop(); | ||||
|         } | ||||
|  | ||||
|         void init(stream<complex_t>* in, float sampleRate, float freq) { | ||||
|             _in = in; | ||||
|             _sampleRate = sampleRate; | ||||
|             _freq = freq; | ||||
|             phase = lv_cmake(1.0f, 0.0f); | ||||
|             phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI)); | ||||
|             generic_block<FrequencyXlator<T>>::registerInput(_in); | ||||
|             generic_block<FrequencyXlator<T>>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInputSize(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<FrequencyXlator<T>>::ctrlMtx); | ||||
|             generic_block<FrequencyXlator<T>>::tempStop(); | ||||
|             generic_block<FrequencyXlator<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<FrequencyXlator<T>>::registerInput(_in); | ||||
|             generic_block<FrequencyXlator<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setSampleRate(float sampleRate) { | ||||
|             // No need to restart | ||||
|             _sampleRate = sampleRate; | ||||
|             phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI)); | ||||
|         } | ||||
|  | ||||
|         float getSampleRate() { | ||||
|             return _sampleRate; | ||||
|         } | ||||
|  | ||||
|         void setFrequency(float freq) { | ||||
|             // No need to restart | ||||
|             _freq = freq; | ||||
|             phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI)); | ||||
|         } | ||||
|  | ||||
|         float getFrequency() { | ||||
|             return _freq; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             // TODO: Do float xlation | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 spdlog::error("XLATOR NOT IMPLEMENTED FOR FLOAT"); | ||||
|             } | ||||
|             if constexpr (std::is_same_v<T, complex_t>) { | ||||
|                 volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count); | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<complex_t> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float _sampleRate; | ||||
|         float _freq; | ||||
|         lv_32fc_t phaseDelta; | ||||
|         lv_32fc_t phase; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class AGC : public generic_block<AGC> { | ||||
|     public: | ||||
|         AGC() {} | ||||
|  | ||||
|         AGC(stream<float>* in, float fallRate, float sampleRate) { init(in, fallRate, sampleRate); } | ||||
|  | ||||
|         ~AGC() { generic_block<AGC>::stop(); } | ||||
|  | ||||
|         void init(stream<float>* in, float fallRate, float sampleRate) { | ||||
|             _in = in; | ||||
|             _sampleRate = sampleRate; | ||||
|             _fallRate = fallRate; | ||||
|             _CorrectedFallRate = _fallRate / _sampleRate; | ||||
|             generic_block<AGC>::registerInput(_in); | ||||
|             generic_block<AGC>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<float>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx); | ||||
|             generic_block<AGC>::tempStop(); | ||||
|             generic_block<AGC>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<AGC>::registerInput(_in); | ||||
|             generic_block<AGC>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setSampleRate(float sampleRate) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx); | ||||
|             _sampleRate = sampleRate; | ||||
|             _CorrectedFallRate = _fallRate / _sampleRate; | ||||
|         } | ||||
|  | ||||
|         void setFallRate(float fallRate) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx); | ||||
|             _fallRate = fallRate; | ||||
|             _CorrectedFallRate = _fallRate / _sampleRate; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             level = pow(10, ((10.0f * log10f(level)) - (_CorrectedFallRate * count)) / 10.0f); | ||||
|  | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 if (_in->readBuf[i] > level) { level = _in->readBuf[i]; } | ||||
|             } | ||||
|  | ||||
|             volk_32f_s32f_multiply_32f(out.writeBuf, _in->readBuf, 1.0f / level, count); | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<float> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float level = 0.0f; | ||||
|         float _fallRate; | ||||
|         float _CorrectedFallRate; | ||||
|         float _sampleRate; | ||||
|         stream<float>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     template <class T> | ||||
|     class Volume : public generic_block<Volume<T>> { | ||||
|     public: | ||||
|         Volume() {} | ||||
|  | ||||
|         Volume(stream<T>* in, float volume) { init(in, volume); } | ||||
|  | ||||
|         ~Volume() { generic_block<Volume<T>>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* in, float volume) { | ||||
|             _in = in; | ||||
|             _volume = volume; | ||||
|             generic_block<Volume<T>>::registerInput(_in); | ||||
|             generic_block<Volume<T>>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInputSize(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Volume<T>>::ctrlMtx); | ||||
|             generic_block<Volume<T>>::tempStop(); | ||||
|             generic_block<Volume<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<Volume<T>>::registerInput(_in); | ||||
|             generic_block<Volume<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setVolume(float volume) { | ||||
|             _volume = volume; | ||||
|             level = powf(_volume, 2); | ||||
|         } | ||||
|  | ||||
|         float getVolume() { | ||||
|             return _volume; | ||||
|         } | ||||
|  | ||||
|         void setMuted(bool muted) { | ||||
|             _muted = muted; | ||||
|         } | ||||
|  | ||||
|         bool getMuted() { | ||||
|             return _muted; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             if (_muted) { | ||||
|                 if constexpr (std::is_same_v<T, stereo_t>) { | ||||
|                     memset(out.writeBuf, 0, sizeof(stereo_t) * count); | ||||
|                 } | ||||
|                 else { | ||||
|                     memset(out.writeBuf, 0, sizeof(float) * count); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 if constexpr (std::is_same_v<T, stereo_t>) { | ||||
|                     volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count * 2); | ||||
|                 } | ||||
|                 else { | ||||
|                     volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<T> out; | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float level = 1.0f; | ||||
|         float _volume = 1.0f; | ||||
|         bool _muted = false; | ||||
|         stream<T>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class Squelch : public generic_block<Squelch> { | ||||
|     public: | ||||
|         Squelch() {} | ||||
|  | ||||
|         Squelch(stream<complex_t>* in, float level) { init(in, level); } | ||||
|  | ||||
|         ~Squelch() { | ||||
|             generic_block<Squelch>::stop(); | ||||
|             delete[] normBuffer; | ||||
|         } | ||||
|  | ||||
|         void init(stream<complex_t>* in, float level) { | ||||
|             _in = in; | ||||
|             _level = level; | ||||
|             normBuffer = new float[STREAM_BUFFER_SIZE]; | ||||
|             generic_block<Squelch>::registerInput(_in); | ||||
|             generic_block<Squelch>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Squelch>::ctrlMtx); | ||||
|             generic_block<Squelch>::tempStop(); | ||||
|             generic_block<Squelch>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<Squelch>::registerInput(_in); | ||||
|             generic_block<Squelch>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setLevel(float level) { | ||||
|             _level = level; | ||||
|         } | ||||
|  | ||||
|         float getLevel() { | ||||
|             return _level; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             float sum = 0.0f; | ||||
|             volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->readBuf, count); | ||||
|             volk_32f_accumulator_s32f(&sum, normBuffer, count); | ||||
|             sum /= (float)count; | ||||
|  | ||||
|             if (10.0f * log10f(sum) >= _level) { | ||||
|                 memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t)); | ||||
|             } | ||||
|             else { | ||||
|                 memset(out.writeBuf, 0, count * sizeof(complex_t)); | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count;  | ||||
|         } | ||||
|  | ||||
|         stream<complex_t> out; | ||||
|  | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         float* normBuffer; | ||||
|         float _level = -50.0f; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										267
									
								
								core/src/dsp/resampling.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								core/src/dsp/resampling.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,267 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <dsp/window.h> | ||||
| #include <numeric> | ||||
| #include <string.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     template <class T> | ||||
|     class PolyphaseResampler : public generic_block<PolyphaseResampler<T>> { | ||||
|     public: | ||||
|         PolyphaseResampler() {} | ||||
|  | ||||
|         PolyphaseResampler(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) { init(in, window, inSampleRate, outSampleRate); } | ||||
|  | ||||
|         ~PolyphaseResampler() { | ||||
|             generic_block<PolyphaseResampler<T>>::stop(); | ||||
|             volk_free(buffer); | ||||
|             volk_free(taps); | ||||
|             freeTapPhases(); | ||||
|         } | ||||
|  | ||||
|         void init(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) { | ||||
|             _in = in; | ||||
|             _window = window; | ||||
|             _inSampleRate = inSampleRate; | ||||
|             _outSampleRate = outSampleRate; | ||||
|  | ||||
|             int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate); | ||||
|             _interp = _outSampleRate / _gcd; | ||||
|             _decim = _inSampleRate / _gcd; | ||||
|  | ||||
|             tapCount = _window->getTapCount(); | ||||
|             taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment()); | ||||
|             _window->createTaps(taps, tapCount, _interp); | ||||
|  | ||||
|             buildTapPhases(); | ||||
|  | ||||
|             buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment()); | ||||
|             memset(buffer, 0, STREAM_BUFFER_SIZE * sizeof(T) * 2); | ||||
|             generic_block<PolyphaseResampler<T>>::registerInput(_in); | ||||
|             generic_block<PolyphaseResampler<T>>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStop(); | ||||
|             generic_block<PolyphaseResampler<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<PolyphaseResampler<T>>::registerInput(_in); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setInSampleRate(float inSampleRate) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStop(); | ||||
|             _inSampleRate = inSampleRate; | ||||
|             int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate); | ||||
|             _interp = _outSampleRate / _gcd; | ||||
|             _decim = _inSampleRate / _gcd; | ||||
|             buildTapPhases(); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setOutSampleRate(float outSampleRate) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStop(); | ||||
|             _outSampleRate = outSampleRate; | ||||
|             int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate); | ||||
|             _interp = _outSampleRate / _gcd; | ||||
|             _decim = _inSampleRate / _gcd; | ||||
|             buildTapPhases(); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int getInterpolation() { | ||||
|             return _interp; | ||||
|         } | ||||
|  | ||||
|         int getDecimation() { | ||||
|             return _decim; | ||||
|         } | ||||
|  | ||||
|         void updateWindow(dsp::filter_window::generic_window* window) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStop(); | ||||
|             _window = window; | ||||
|             volk_free(taps); | ||||
|             tapCount = window->getTapCount(); | ||||
|             taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment()); | ||||
|             window->createTaps(taps, tapCount, _interp); | ||||
|             buildTapPhases(); | ||||
|             generic_block<PolyphaseResampler<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int calcOutSize(int in) { | ||||
|             return (in * _interp) / _decim; | ||||
|         } | ||||
|  | ||||
|         virtual int run() override { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { | ||||
|                 return -1; | ||||
|             } | ||||
|  | ||||
|             int outCount = calcOutSize(count); | ||||
|  | ||||
|             memcpy(&buffer[tapsPerPhase], _in->readBuf, count * sizeof(T)); | ||||
|             _in->flush(); | ||||
|  | ||||
|             // Write to output | ||||
|             int outIndex = 0; | ||||
|             int _interp_m_1 = _interp - 1; | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 for (int i = 0; outIndex < outCount; i += _decim) { | ||||
|                     int phase = i % _interp; | ||||
|                     volk_32f_x2_dot_prod_32f(&out.writeBuf[outIndex], &buffer[i / _interp], tapPhases[phase], tapsPerPhase); | ||||
|                     outIndex++; | ||||
|                 } | ||||
|             } | ||||
|             if constexpr (std::is_same_v<T, complex_t>) { | ||||
|                 for (int i = 0; outIndex < outCount; i += _decim) { | ||||
|                     int phase = i % _interp; | ||||
|                     volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex], (lv_32fc_t*)&buffer[(i / _interp)], tapPhases[phase], tapsPerPhase); | ||||
|                     outIndex++; | ||||
|                 } | ||||
|             } | ||||
|             if (!out.swap(outCount)) { return -1; } | ||||
|  | ||||
|             memmove(buffer, &buffer[count], tapsPerPhase * sizeof(T)); | ||||
|  | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<T> out; | ||||
|  | ||||
|     private: | ||||
|         void buildTapPhases(){ | ||||
|             if(!taps){ | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if(!tapPhases.empty()){ | ||||
|                 freeTapPhases(); | ||||
|             } | ||||
|  | ||||
|             int phases = _interp; | ||||
|             tapsPerPhase = (tapCount+phases-1)/phases; //Integer division ceiling | ||||
|  | ||||
|             bufStart = &buffer[tapsPerPhase]; | ||||
|  | ||||
|             for(int i = 0; i < phases; i++){ | ||||
|                 tapPhases.push_back((float*)volk_malloc(tapsPerPhase * sizeof(float), volk_get_alignment())); | ||||
|             } | ||||
|  | ||||
|             int currentTap = 0; | ||||
|             for(int tap = 0; tap < tapsPerPhase; tap++) { | ||||
|                 for (int phase = 0; phase < phases; phase++) { | ||||
|                     if(currentTap < tapCount) { | ||||
|                         tapPhases[(_interp - 1) - phase][tap] = taps[currentTap++]; | ||||
|                     } | ||||
|                     else{ | ||||
|                         tapPhases[(_interp - 1) - phase][tap] = 0; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         void freeTapPhases(){ | ||||
|             for(auto & tapPhase : tapPhases){ | ||||
|                 volk_free(tapPhase); | ||||
|             } | ||||
|             tapPhases.clear(); | ||||
|         } | ||||
|  | ||||
|         int count; | ||||
|         stream<T>* _in; | ||||
|  | ||||
|         dsp::filter_window::generic_window* _window; | ||||
|  | ||||
|         T* bufStart; | ||||
|         T* buffer; | ||||
|         int tapCount; | ||||
|         int _interp, _decim; | ||||
|         float _inSampleRate, _outSampleRate; | ||||
|         float* taps; | ||||
|  | ||||
|         int tapsPerPhase; | ||||
|         std::vector<float*> tapPhases; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     class PowerDecimator : public generic_block<PowerDecimator> { | ||||
|     public: | ||||
|         PowerDecimator() {} | ||||
|  | ||||
|         PowerDecimator(stream<complex_t>* in, unsigned int power) { init(in, power); } | ||||
|  | ||||
|         ~PowerDecimator() { | ||||
|             generic_block<PowerDecimator>::stop(); | ||||
|         } | ||||
|  | ||||
|         void init(stream<complex_t>* in, unsigned int power) { | ||||
|             _in = in; | ||||
|             _power = power; | ||||
|             generic_block<PowerDecimator>::registerInput(_in); | ||||
|             generic_block<PowerDecimator>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<complex_t>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<PowerDecimator>::ctrlMtx); | ||||
|             generic_block<PowerDecimator>::tempStop(); | ||||
|             generic_block<PowerDecimator>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<PowerDecimator>::registerInput(_in); | ||||
|             generic_block<PowerDecimator>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setPower(unsigned int power) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<PowerDecimator>::ctrlMtx); | ||||
|             generic_block<PowerDecimator>::tempStop(); | ||||
|             generic_block<PowerDecimator>::unregisterInput(_in); | ||||
|             _power = power; | ||||
|             generic_block<PowerDecimator>::registerInput(_in); | ||||
|             generic_block<PowerDecimator>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             if (_power == 0) { | ||||
|                 memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t)); | ||||
|             } | ||||
|             else if (_power == 1) { | ||||
|                 for (int j = 0; j < count; j += 2) { | ||||
|                     out.writeBuf[j / 2].i = (_in->readBuf[j].i + _in->readBuf[j + 1].i) * 0.5f; | ||||
|                     out.writeBuf[j / 2].q = (_in->readBuf[j].q + _in->readBuf[j + 1].q) * 0.5f; | ||||
|                 } | ||||
|                 count /= 2; | ||||
|             } | ||||
|  | ||||
|             _in->flush(); | ||||
|  | ||||
|             if (_power > 1) { | ||||
|                 for (int i = 1; i < _power; i++) { | ||||
|                     for (int j = 0; j < count; j += 2) { | ||||
|                         out.writeBuf[j / 2].i = (_in->readBuf[j].i + _in->readBuf[j + 1].i) * 0.5f; | ||||
|                         out.writeBuf[j / 2].q = (_in->readBuf[j].q + _in->readBuf[j + 1].q) * 0.5f; | ||||
|                     } | ||||
|                     count /= 2; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if (!out.swap(count)) { return -1; } | ||||
|             return count;  | ||||
|         } | ||||
|  | ||||
|         stream<complex_t> out; | ||||
|  | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         unsigned int _power = 0; | ||||
|         stream<complex_t>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										189
									
								
								core/src/dsp/routing.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								core/src/dsp/routing.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <dsp/buffer.h> | ||||
| #include <string.h> | ||||
| #include <numeric> | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     template <class T> | ||||
|     class Splitter : public generic_block<Splitter<T>> { | ||||
|     public: | ||||
|         Splitter() {} | ||||
|  | ||||
|         Splitter(stream<T>* in) { init(in); } | ||||
|  | ||||
|         ~Splitter() { generic_block<Splitter>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* in) { | ||||
|             _in = in; | ||||
|             generic_block<Splitter>::registerInput(_in); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx); | ||||
|             generic_block<Splitter>::tempStop(); | ||||
|             generic_block<Splitter>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<Splitter>::registerInput(_in); | ||||
|             generic_block<Splitter>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void bindStream(stream<T>* stream) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx); | ||||
|             generic_block<Splitter>::tempStop(); | ||||
|             out.push_back(stream); | ||||
|             generic_block<Splitter>::registerOutput(stream); | ||||
|             generic_block<Splitter>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void unbindStream(stream<T>* stream) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx); | ||||
|             generic_block<Splitter>::tempStop(); | ||||
|             generic_block<Splitter>::unregisterOutput(stream); | ||||
|             out.erase(std::remove(out.begin(), out.end(), stream), out.end()); | ||||
|             generic_block<Splitter>::tempStart(); | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         int run() { | ||||
|             // TODO: If too slow, buffering might be necessary | ||||
|             int count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|             for (const auto& stream : out) { | ||||
|                 memcpy(stream->writeBuf, _in->readBuf, count * sizeof(T)); | ||||
|                 if (!stream->swap(count)) { return -1; } | ||||
|             } | ||||
|             _in->flush(); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<T>* _in; | ||||
|         std::vector<stream<T>*> out; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     // NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works... | ||||
|     template <class T> | ||||
|     class Reshaper : public generic_block<Reshaper<T>> { | ||||
|     public: | ||||
|         Reshaper() {} | ||||
|  | ||||
|         Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); } | ||||
|  | ||||
|         ~Reshaper() { generic_block<Reshaper<T>>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* in, int keep, int skip) { | ||||
|             _in = in; | ||||
|             _keep = keep; | ||||
|             _skip = skip; | ||||
|             ringBuf.init(keep * 2); | ||||
|             generic_block<Reshaper<T>>::registerInput(_in); | ||||
|             generic_block<Reshaper<T>>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx); | ||||
|             generic_block<Reshaper<T>>::tempStop(); | ||||
|             generic_block<Reshaper<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<Reshaper<T>>::registerInput(_in); | ||||
|             generic_block<Reshaper<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setKeep(int keep) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx); | ||||
|             generic_block<Reshaper<T>>::tempStop(); | ||||
|             generic_block<Reshaper<T>>::unregisterInput(_in); | ||||
|             _keep = keep; | ||||
|             ringBuf.setMaxLatency(keep * 2); | ||||
|             generic_block<Reshaper<T>>::registerInput(_in); | ||||
|             generic_block<Reshaper<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setSkip(int skip) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx); | ||||
|             generic_block<Reshaper<T>>::tempStop(); | ||||
|             generic_block<Reshaper<T>>::unregisterInput(_in); | ||||
|             _skip = skip; | ||||
|             generic_block<Reshaper<T>>::registerInput(_in); | ||||
|             generic_block<Reshaper<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|             ringBuf.write(_in->readBuf, count); | ||||
|             _in->flush(); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         stream<T> out; | ||||
|  | ||||
|     private: | ||||
|         void doStart() { | ||||
|             workThread = std::thread(&Reshaper<T>::loop, this); | ||||
|             bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this); | ||||
|         } | ||||
|  | ||||
|         void loop() { | ||||
|             while (run() >= 0); | ||||
|         } | ||||
|  | ||||
|         void doStop() { | ||||
|             _in->stopReader(); | ||||
|             ringBuf.stopReader(); | ||||
|             out.stopWriter(); | ||||
|             ringBuf.stopWriter(); | ||||
|  | ||||
|             if (workThread.joinable()) { | ||||
|                 workThread.join(); | ||||
|             } | ||||
|             if (bufferWorkerThread.joinable()) { | ||||
|                 bufferWorkerThread.join(); | ||||
|             } | ||||
|  | ||||
|             _in->clearReadStop(); | ||||
|             ringBuf.clearReadStop(); | ||||
|             out.clearWriteStop(); | ||||
|             ringBuf.clearWriteStop(); | ||||
|         } | ||||
|  | ||||
|         void bufferWorker() { | ||||
|             complex_t* buf = new complex_t[_keep]; | ||||
|             bool delay = _skip < 0; | ||||
|  | ||||
|             int readCount = std::min<int>(_keep + _skip, _keep); | ||||
|             int skip = std::max<int>(_skip, 0); | ||||
|             int delaySize = (-_skip) * sizeof(complex_t); | ||||
|             int delayCount = (-_skip); | ||||
|  | ||||
|             complex_t* start = &buf[std::max<int>(-_skip, 0)]; | ||||
|             complex_t* delayStart = &buf[_keep + _skip]; | ||||
|  | ||||
|             while (true) { | ||||
|                 if (delay) { | ||||
|                     memmove(buf, delayStart, delaySize); | ||||
|                     for (int i = 0; i < delayCount; i++) { | ||||
|                         buf[i].i /= 10.0f; | ||||
|                         buf[i].q /= 10.0f; | ||||
|                     } | ||||
|                 } | ||||
|                 if (ringBuf.readAndSkip(start, readCount, skip) < 0) { break; }; | ||||
|                 memcpy(out.writeBuf, buf, _keep * sizeof(complex_t)); | ||||
|                 if (!out.swap(_keep)) { break; } | ||||
|             } | ||||
|             delete[] buf; | ||||
|         } | ||||
|  | ||||
|         stream<T>* _in; | ||||
|         int _outBlockSize; | ||||
|         RingBuffer<T> ringBuf; | ||||
|         std::thread bufferWorkerThread; | ||||
|         std::thread workThread; | ||||
|         int _keep, _skip; | ||||
|          | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										140
									
								
								core/src/dsp/sink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								core/src/dsp/sink.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <dsp/buffer.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     template <class T> | ||||
|     class HandlerSink : public generic_block<HandlerSink<T>> { | ||||
|     public: | ||||
|         HandlerSink() {} | ||||
|  | ||||
|         HandlerSink(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { init(in, handler, ctx); } | ||||
|  | ||||
|         ~HandlerSink() { generic_block<HandlerSink<T>>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { | ||||
|             _in = in; | ||||
|             _handler = handler; | ||||
|             _ctx = ctx; | ||||
|             generic_block<HandlerSink<T>>::registerInput(_in); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx); | ||||
|             generic_block<HandlerSink<T>>::tempStop(); | ||||
|             generic_block<HandlerSink<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<HandlerSink<T>>::registerInput(_in); | ||||
|             generic_block<HandlerSink<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setHandler(void (*handler)(T* data, int count, void* ctx), void* ctx) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx); | ||||
|             generic_block<HandlerSink<T>>::tempStop(); | ||||
|             _handler = handler; | ||||
|             _ctx = ctx; | ||||
|             generic_block<HandlerSink<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|             _handler(_in->readBuf, count, _ctx); | ||||
|             _in->flush(); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         stream<T>* _in; | ||||
|         void (*_handler)(T* data, int count, void* ctx); | ||||
|         void* _ctx; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     template <class T> | ||||
|     class RingBufferSink : public generic_block<RingBufferSink<T>> { | ||||
|     public: | ||||
|         RingBufferSink() {} | ||||
|  | ||||
|         RingBufferSink(stream<T>* in) { init(in); } | ||||
|  | ||||
|         ~RingBufferSink() { generic_block<RingBufferSink<T>>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* in) { | ||||
|             _in = in; | ||||
|             data.init(480); // TODO: Use an argument | ||||
|             generic_block<RingBufferSink<T>>::registerInput(_in); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<RingBufferSink<T>>::ctrlMtx); | ||||
|             generic_block<RingBufferSink<T>>::tempStop(); | ||||
|             generic_block<RingBufferSink<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<RingBufferSink<T>>::registerInput(_in); | ||||
|             generic_block<RingBufferSink<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|             if (data.write(_in->readBuf, count) < 0) { return -1; } | ||||
|             _in->flush(); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|         RingBuffer<T> data; | ||||
|  | ||||
|     private: | ||||
|         void doStop() { | ||||
|             _in->stopReader(); | ||||
|             data.stopWriter(); | ||||
|             if (generic_block<RingBufferSink<T>>::workerThread.joinable()) { | ||||
|                 generic_block<RingBufferSink<T>>::workerThread.join(); | ||||
|             } | ||||
|             _in->clearReadStop(); | ||||
|             data.clearWriteStop(); | ||||
|         } | ||||
|  | ||||
|         int count; | ||||
|         stream<T>* _in; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     template <class T> | ||||
|     class NullSink : public generic_block<NullSink<T>> { | ||||
|     public: | ||||
|         NullSink() {} | ||||
|  | ||||
|         NullSink(stream<T>* in) { init(in); } | ||||
|  | ||||
|         ~NullSink() { generic_block<NullSink<T>>::stop(); } | ||||
|  | ||||
|         void init(stream<T>* in) { | ||||
|             _in = in; | ||||
|             generic_block<NullSink<T>>::registerInput(_in); | ||||
|         } | ||||
|  | ||||
|         void setInput(stream<T>* in) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<NullSink<T>>::ctrlMtx); | ||||
|             generic_block<NullSink<T>>::tempStop(); | ||||
|             generic_block<NullSink<T>>::unregisterInput(_in); | ||||
|             _in = in; | ||||
|             generic_block<NullSink<T>>::registerInput(_in); | ||||
|             generic_block<NullSink<T>>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             count = _in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|             _in->flush(); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         int count; | ||||
|         stream<T>* _in; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										74
									
								
								core/src/dsp/source.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								core/src/dsp/source.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     class SineSource : public generic_block<SineSource> { | ||||
|     public: | ||||
|         SineSource() {} | ||||
|  | ||||
|         SineSource(int blockSize, float sampleRate, float freq) { init(blockSize, sampleRate, freq); } | ||||
|  | ||||
|         ~SineSource() { generic_block<SineSource>::stop(); } | ||||
|  | ||||
|         void init(int blockSize, float sampleRate, float freq) { | ||||
|             _blockSize = blockSize; | ||||
|             _sampleRate = sampleRate; | ||||
|             _freq = freq; | ||||
|             zeroPhase = (lv_32fc_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(lv_32fc_t), volk_get_alignment()); | ||||
|             for (int i = 0; i < STREAM_BUFFER_SIZE; i++) { | ||||
|                 zeroPhase[i] = lv_cmake(1.0f, 0.0f); | ||||
|             } | ||||
|             phase = lv_cmake(1.0f, 0.0f); | ||||
|             phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI)); | ||||
|             generic_block<SineSource>::registerOutput(&out); | ||||
|         } | ||||
|  | ||||
|         void setBlockSize(int blockSize) { | ||||
|             std::lock_guard<std::mutex> lck(generic_block<SineSource>::ctrlMtx); | ||||
|             generic_block<SineSource>::tempStop(); | ||||
|             _blockSize = blockSize; | ||||
|             generic_block<SineSource>::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int getBlockSize() { | ||||
|             return _blockSize; | ||||
|         } | ||||
|  | ||||
|         void setSampleRate(float sampleRate) { | ||||
|             // No need to restart | ||||
|             _sampleRate = sampleRate; | ||||
|             phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI)); | ||||
|         } | ||||
|  | ||||
|         float getSampleRate() { | ||||
|             return _sampleRate; | ||||
|         } | ||||
|  | ||||
|         void setFrequency(float freq) { | ||||
|             // No need to restart | ||||
|             _freq = freq; | ||||
|             phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI)); | ||||
|         } | ||||
|  | ||||
|         float getFrequency() { | ||||
|             return _freq; | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, zeroPhase, phaseDelta, &phase, _blockSize); | ||||
|             if(!out.swap(_blockSize)) { return -1; } | ||||
|             return _blockSize; | ||||
|         } | ||||
|  | ||||
|         stream<complex_t> out; | ||||
|  | ||||
|     private: | ||||
|         int _blockSize; | ||||
|         float _sampleRate; | ||||
|         float _freq; | ||||
|         lv_32fc_t phaseDelta; | ||||
|         lv_32fc_t phase; | ||||
|         lv_32fc_t* zeroPhase; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										126
									
								
								core/src/dsp/stream.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								core/src/dsp/stream.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| #pragma once | ||||
| #include <mutex> | ||||
| #include <condition_variable> | ||||
| #include <volk/volk.h> | ||||
|  | ||||
| // 1MB buffer | ||||
| #define STREAM_BUFFER_SIZE  1000000 | ||||
|  | ||||
| namespace dsp { | ||||
|     class untyped_steam { | ||||
|     public: | ||||
|         virtual bool swap(int size) { return false; } | ||||
|         virtual int read() { return -1; } | ||||
|         virtual void flush() {} | ||||
|         virtual void stopWriter() {} | ||||
|         virtual void clearWriteStop() {} | ||||
|         virtual void stopReader() {} | ||||
|         virtual void clearReadStop() {} | ||||
|     }; | ||||
|  | ||||
|     template <class T> | ||||
|     class stream : public untyped_steam { | ||||
|     public: | ||||
|         stream() { | ||||
|             writeBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment()); | ||||
|             readBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment()); | ||||
|         } | ||||
|  | ||||
|         ~stream() { | ||||
|             volk_free(writeBuf); | ||||
|             volk_free(readBuf); | ||||
|         } | ||||
|  | ||||
|         bool swap(int size) { | ||||
|             { | ||||
|                 // Wait to either swap or stop | ||||
|                 std::unique_lock<std::mutex> lck(swapMtx); | ||||
|                 swapCV.wait(lck, [this]{ return (canSwap || writerStop); }); | ||||
|  | ||||
|                 // If writer was stopped, abandon operation | ||||
|                 if (writerStop) { return false; } | ||||
|  | ||||
|                 // Swap buffers | ||||
|                 dataSize = size; | ||||
|                 T* temp = writeBuf; | ||||
|                 writeBuf = readBuf; | ||||
|                 readBuf = temp; | ||||
|                 canSwap = false; | ||||
|             } | ||||
|  | ||||
|             // Notify reader that some data is ready | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(rdyMtx); | ||||
|                 dataReady = true; | ||||
|             } | ||||
|             rdyCV.notify_all(); | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         int read() { | ||||
|             // Wait for data to be ready or to be stopped | ||||
|             std::unique_lock<std::mutex> lck(rdyMtx); | ||||
|             rdyCV.wait(lck, [this]{ return (dataReady || readerStop); }); | ||||
|  | ||||
|             return (readerStop ? -1 : dataSize); | ||||
|         } | ||||
|  | ||||
|         void flush() { | ||||
|             // Clear data ready | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(rdyMtx); | ||||
|                 dataReady = false; | ||||
|             } | ||||
|  | ||||
|             // Notify writer that buffers can be swapped | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(swapMtx); | ||||
|                 canSwap = true; | ||||
|             } | ||||
|  | ||||
|             swapCV.notify_all(); | ||||
|         } | ||||
|  | ||||
|         void stopWriter() { | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(swapMtx); | ||||
|                 writerStop = true; | ||||
|             } | ||||
|             swapCV.notify_all(); | ||||
|         } | ||||
|  | ||||
|         void clearWriteStop() { | ||||
|             writerStop = false; | ||||
|         } | ||||
|  | ||||
|         void stopReader() { | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lck(rdyMtx); | ||||
|                 readerStop = true; | ||||
|             } | ||||
|             rdyCV.notify_all(); | ||||
|         } | ||||
|  | ||||
|         void clearReadStop() { | ||||
|             readerStop = false; | ||||
|         } | ||||
|  | ||||
|         T* writeBuf; | ||||
|         T* readBuf; | ||||
|  | ||||
|     private: | ||||
|         std::mutex swapMtx; | ||||
|         std::condition_variable swapCV; | ||||
|         bool canSwap = true; | ||||
|  | ||||
|         std::mutex rdyMtx; | ||||
|         std::condition_variable rdyCV; | ||||
|         bool dataReady = false; | ||||
|  | ||||
|         bool readerStop = false; | ||||
|         bool writerStop = false; | ||||
|  | ||||
|         int dataSize = 0; | ||||
|     }; | ||||
| } | ||||
| @@ -5,4 +5,9 @@ namespace dsp { | ||||
|         float q; | ||||
|         float i; | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
|     struct stereo_t { | ||||
|         float l; | ||||
|         float r; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										112
									
								
								core/src/dsp/vfo.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								core/src/dsp/vfo.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
| #include <dsp/window.h> | ||||
| #include <dsp/resampling.h> | ||||
| #include <dsp/processing.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| namespace dsp { | ||||
|     class VFO { | ||||
|     public: | ||||
|         VFO() {} | ||||
|  | ||||
|         ~VFO() { stop(); } | ||||
|  | ||||
|         VFO(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) { | ||||
|             init(in, offset, inSampleRate, outSampleRate, bandWidth); | ||||
|         }; | ||||
|  | ||||
|         void init(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) { | ||||
|             _in = in; | ||||
|             _offset = offset; | ||||
|             _inSampleRate = inSampleRate; | ||||
|             _outSampleRate = outSampleRate; | ||||
|             _bandWidth = bandWidth; | ||||
|  | ||||
|             float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f; | ||||
|  | ||||
|             xlator.init(_in, _inSampleRate, -_offset); | ||||
|             win.init(realCutoff, realCutoff, inSampleRate); | ||||
|             resamp.init(&xlator.out, &win, _inSampleRate, _outSampleRate); | ||||
|  | ||||
|             win.setSampleRate(_inSampleRate * resamp.getInterpolation()); | ||||
|             resamp.updateWindow(&win); | ||||
|  | ||||
|             out = &resamp.out; | ||||
|         } | ||||
|  | ||||
|         void start() { | ||||
|             if (running) { return; } | ||||
|             xlator.start(); | ||||
|             resamp.start(); | ||||
|         } | ||||
|  | ||||
|         void stop() { | ||||
|             if (!running) { return; } | ||||
|             xlator.stop(); | ||||
|             resamp.stop(); | ||||
|         } | ||||
|  | ||||
|         void setInSampleRate(float inSampleRate) { | ||||
|             _inSampleRate = inSampleRate; | ||||
|             if (running) { xlator.stop(); resamp.stop(); } | ||||
|             xlator.setSampleRate(_inSampleRate); | ||||
|             resamp.setInSampleRate(_inSampleRate); | ||||
|             float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f; | ||||
|             win.setSampleRate(_inSampleRate * resamp.getInterpolation()); | ||||
|             win.setCutoff(realCutoff); | ||||
|             win.setTransWidth(realCutoff); | ||||
|             resamp.updateWindow(&win); | ||||
|             if (running) { xlator.start(); resamp.start(); } | ||||
|         } | ||||
|  | ||||
|         void setOutSampleRate(float outSampleRate) { | ||||
|             _outSampleRate = outSampleRate; | ||||
|             if (running) { resamp.stop(); } | ||||
|             resamp.setOutSampleRate(_outSampleRate); | ||||
|             float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f; | ||||
|             win.setSampleRate(_inSampleRate * resamp.getInterpolation()); | ||||
|             win.setCutoff(realCutoff); | ||||
|             win.setTransWidth(realCutoff); | ||||
|             resamp.updateWindow(&win); | ||||
|             if (running) { resamp.start(); } | ||||
|         } | ||||
|  | ||||
|         void setOutSampleRate(float outSampleRate, float bandWidth) { | ||||
|             _outSampleRate = outSampleRate; | ||||
|             _bandWidth = bandWidth; | ||||
|             if (running) { resamp.stop(); } | ||||
|             resamp.setOutSampleRate(_outSampleRate); | ||||
|             float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f; | ||||
|             win.setSampleRate(_inSampleRate * resamp.getInterpolation()); | ||||
|             win.setCutoff(realCutoff); | ||||
|             win.setTransWidth(realCutoff); | ||||
|             resamp.updateWindow(&win); | ||||
|             if (running) { resamp.start(); } | ||||
|         } | ||||
|  | ||||
|         void setOffset(float offset) { | ||||
|             _offset = offset; | ||||
|             xlator.setFrequency(-_offset); | ||||
|         } | ||||
|  | ||||
|         void setBandwidth(float bandWidth) { | ||||
|             _bandWidth = bandWidth; | ||||
|             float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f; | ||||
|             win.setCutoff(realCutoff); | ||||
|             win.setTransWidth(realCutoff); | ||||
|             resamp.updateWindow(&win); | ||||
|         } | ||||
|  | ||||
|         stream<complex_t>* out; | ||||
|  | ||||
|     private: | ||||
|         bool running = false; | ||||
|         float _offset, _inSampleRate, _outSampleRate, _bandWidth; | ||||
|         filter_window::BlackmanWindow win; | ||||
|         stream<complex_t>* _in; | ||||
|         FrequencyXlator<complex_t> xlator; | ||||
|         PolyphaseResampler<complex_t> resamp; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										76
									
								
								core/src/dsp/window.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								core/src/dsp/window.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| #pragma once | ||||
| #include <dsp/block.h> | ||||
|  | ||||
| namespace dsp { | ||||
|     namespace filter_window { | ||||
|         class generic_window { | ||||
|         public: | ||||
|             virtual int getTapCount() { return -1; } | ||||
|             virtual void createTaps(float* taps, int tapCount, float factor = 1.0f) {} | ||||
|         }; | ||||
|  | ||||
|         class BlackmanWindow : public filter_window::generic_window { | ||||
|         public: | ||||
|             BlackmanWindow() {} | ||||
|             BlackmanWindow(float cutoff, float transWidth, float sampleRate) { init(cutoff, transWidth, sampleRate); } | ||||
|  | ||||
|             void init(float cutoff, float transWidth, float sampleRate) { | ||||
|                 _cutoff = cutoff; | ||||
|                 _transWidth = transWidth; | ||||
|                 _sampleRate = sampleRate; | ||||
|             } | ||||
|  | ||||
|             void setSampleRate(float sampleRate) { | ||||
|                 _sampleRate = sampleRate; | ||||
|             } | ||||
|  | ||||
|             void setCutoff(float cutoff) { | ||||
|                 _cutoff = cutoff; | ||||
|             } | ||||
|              | ||||
|             void setTransWidth(float transWidth) { | ||||
|                 _transWidth = transWidth; | ||||
|             } | ||||
|  | ||||
|             int getTapCount() { | ||||
|                 float fc = _cutoff / _sampleRate; | ||||
|                 if (fc > 1.0f) { | ||||
|                     fc = 1.0f; | ||||
|                 } | ||||
|  | ||||
|                 int _M = 4.0f / (_transWidth / _sampleRate); | ||||
|                 if (_M < 4) { | ||||
|                     _M = 4; | ||||
|                 } | ||||
|  | ||||
|                 if (_M % 2 == 0) { _M++; } | ||||
|  | ||||
|                 return _M; | ||||
|             } | ||||
|  | ||||
|             void createTaps(float* taps, int tapCount, float factor = 1.0f) { | ||||
|                 float fc = _cutoff / _sampleRate; | ||||
|                 if (fc > 1.0f) { | ||||
|                     fc = 1.0f; | ||||
|                 } | ||||
|                 float tc = tapCount; | ||||
|                 float sum = 0.0f; | ||||
|                 float val; | ||||
|                 for (int i = 0; i < tapCount; i++) { | ||||
|                     val = (sin(2.0f * FL_M_PI * fc * ((float)i - (tc / 2))) / ((float)i - (tc / 2))) *  | ||||
|                         (0.42f - (0.5f * cos(2.0f * FL_M_PI / tc)) + (0.8f * cos(4.0f * FL_M_PI / tc))); | ||||
|                     taps[i] = val; // tapCount - i - 1 | ||||
|                     sum += val; | ||||
|                 } | ||||
|                 for (int i = 0; i < tapCount; i++) { | ||||
|                     taps[i] *= factor; | ||||
|                     taps[i] /= sum; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         private: | ||||
|             float _cutoff, _transWidth, _sampleRate; | ||||
|  | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										3783
									
								
								core/src/duktape/duk_config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3783
									
								
								core/src/duktape/duk_config.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										185
									
								
								core/src/duktape/duk_console.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								core/src/duktape/duk_console.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| /* | ||||
|  *  Minimal 'console' binding. | ||||
|  * | ||||
|  *  https://github.com/DeveloperToolsWG/console-object/blob/master/api.md | ||||
|  *  https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference | ||||
|  *  https://developer.mozilla.org/en/docs/Web/API/console | ||||
|  */ | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <stdarg.h> | ||||
| #include "duktape.h" | ||||
| #include "duk_console.h" | ||||
|  | ||||
| /* XXX: Add some form of log level filtering. */ | ||||
|  | ||||
| /* XXX: Should all output be written via e.g. console.write(formattedMsg)? | ||||
|  * This would make it easier for user code to redirect all console output | ||||
|  * to a custom backend. | ||||
|  */ | ||||
|  | ||||
| /* XXX: Init console object using duk_def_prop() when that call is available. */ | ||||
|  | ||||
| static duk_ret_t duk__console_log_helper(duk_context *ctx, const char *error_name) { | ||||
| 	duk_uint_t flags = (duk_uint_t) duk_get_current_magic(ctx); | ||||
| 	FILE *output = (flags & DUK_CONSOLE_STDOUT_ONLY) ? stdout : stderr; | ||||
| 	duk_idx_t n = duk_get_top(ctx); | ||||
| 	duk_idx_t i; | ||||
|  | ||||
| 	duk_get_global_string(ctx, "console"); | ||||
| 	duk_get_prop_string(ctx, -1, "format"); | ||||
|  | ||||
| 	for (i = 0; i < n; i++) { | ||||
| 		if (duk_check_type_mask(ctx, i, DUK_TYPE_MASK_OBJECT)) { | ||||
| 			/* Slow path formatting. */ | ||||
| 			duk_dup(ctx, -1);  /* console.format */ | ||||
| 			duk_dup(ctx, i); | ||||
| 			duk_call(ctx, 1); | ||||
| 			duk_replace(ctx, i);  /* arg[i] = console.format(arg[i]); */ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	duk_pop_2(ctx); | ||||
|  | ||||
| 	duk_push_string(ctx, " "); | ||||
| 	duk_insert(ctx, 0); | ||||
| 	duk_join(ctx, n); | ||||
|  | ||||
| 	if (error_name) { | ||||
| 		duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_require_string(ctx, -1)); | ||||
| 		duk_push_string(ctx, "name"); | ||||
| 		duk_push_string(ctx, error_name); | ||||
| 		duk_def_prop(ctx, -3, DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE);  /* to get e.g. 'Trace: 1 2 3' */ | ||||
| 		duk_get_prop_string(ctx, -1, "stack"); | ||||
| 	} | ||||
|  | ||||
| 	fprintf(output, "%s\n", duk_to_string(ctx, -1)); | ||||
| 	if (flags & DUK_CONSOLE_FLUSH) { | ||||
| 		fflush(output); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static duk_ret_t duk__console_assert(duk_context *ctx) { | ||||
| 	if (duk_to_boolean(ctx, 0)) { | ||||
| 		return 0; | ||||
| 	} | ||||
| 	duk_remove(ctx, 0); | ||||
|  | ||||
| 	return duk__console_log_helper(ctx, "AssertionError"); | ||||
| } | ||||
|  | ||||
| static duk_ret_t duk__console_log(duk_context *ctx) { | ||||
| 	return duk__console_log_helper(ctx, NULL); | ||||
| } | ||||
|  | ||||
| static duk_ret_t duk__console_trace(duk_context *ctx) { | ||||
| 	return duk__console_log_helper(ctx, "Trace"); | ||||
| } | ||||
|  | ||||
| static duk_ret_t duk__console_info(duk_context *ctx) { | ||||
| 	return duk__console_log_helper(ctx, NULL); | ||||
| } | ||||
|  | ||||
| static duk_ret_t duk__console_warn(duk_context *ctx) { | ||||
| 	return duk__console_log_helper(ctx, NULL); | ||||
| } | ||||
|  | ||||
| static duk_ret_t duk__console_error(duk_context *ctx) { | ||||
| 	return duk__console_log_helper(ctx, "Error"); | ||||
| } | ||||
|  | ||||
| static duk_ret_t duk__console_dir(duk_context *ctx) { | ||||
| 	/* For now, just share the formatting of .log() */ | ||||
| 	return duk__console_log_helper(ctx, 0); | ||||
| } | ||||
|  | ||||
| static void duk__console_reg_vararg_func(duk_context *ctx, duk_c_function func, const char *name, duk_uint_t flags) { | ||||
| 	duk_push_c_function(ctx, func, DUK_VARARGS); | ||||
| 	duk_push_string(ctx, "name"); | ||||
| 	duk_push_string(ctx, name); | ||||
| 	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);  /* Improve stacktraces by displaying function name */ | ||||
| 	duk_set_magic(ctx, -1, (duk_int_t) flags); | ||||
| 	duk_put_prop_string(ctx, -2, name); | ||||
| } | ||||
|  | ||||
| void duk_console_init(duk_context *ctx, duk_uint_t flags) { | ||||
| 	duk_uint_t flags_orig; | ||||
|  | ||||
| 	/* If both DUK_CONSOLE_STDOUT_ONLY and DUK_CONSOLE_STDERR_ONLY where specified, | ||||
| 	 * just turn off DUK_CONSOLE_STDOUT_ONLY and keep DUK_CONSOLE_STDERR_ONLY. | ||||
| 	 */ | ||||
| 	if ((flags & DUK_CONSOLE_STDOUT_ONLY) && (flags & DUK_CONSOLE_STDERR_ONLY)) { | ||||
| 	    flags &= ~DUK_CONSOLE_STDOUT_ONLY; | ||||
| 	} | ||||
| 	/* Remember the (possibly corrected) flags we received. */ | ||||
| 	flags_orig = flags; | ||||
|  | ||||
| 	duk_push_object(ctx); | ||||
|  | ||||
| 	/* Custom function to format objects; user can replace. | ||||
| 	 * For now, try JX-formatting and if that fails, fall back | ||||
| 	 * to ToString(v). | ||||
| 	 */ | ||||
| 	duk_eval_string(ctx, | ||||
| 		"(function (E) {" | ||||
| 		    "return function format(v){" | ||||
| 		        "try{" | ||||
| 		            "return E('jx',v);" | ||||
| 		        "}catch(e){" | ||||
| 		            "return String(v);"  /* String() allows symbols, ToString() internal algorithm doesn't. */ | ||||
| 		        "}" | ||||
| 		    "};" | ||||
| 		"})(Duktape.enc)"); | ||||
| 	duk_put_prop_string(ctx, -2, "format"); | ||||
|  | ||||
| 	flags = flags_orig; | ||||
| 	if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) { | ||||
| 	    /* No output indicators were specified; these levels go to stdout. */ | ||||
| 	    flags |= DUK_CONSOLE_STDOUT_ONLY; | ||||
| 	} | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_assert, "assert", flags); | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_log, "log", flags); | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_log, "debug", flags);  /* alias to console.log */ | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_trace, "trace", flags); | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_info, "info", flags); | ||||
|  | ||||
| 	flags = flags_orig; | ||||
| 	if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) { | ||||
| 	    /* No output indicators were specified; these levels go to stderr. */ | ||||
| 	    flags |= DUK_CONSOLE_STDERR_ONLY; | ||||
| 	} | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_warn, "warn", flags); | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_error, "error", flags); | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_error, "exception", flags);  /* alias to console.error */ | ||||
| 	duk__console_reg_vararg_func(ctx, duk__console_dir, "dir", flags); | ||||
|  | ||||
| 	duk_put_global_string(ctx, "console"); | ||||
|  | ||||
| 	/* Proxy wrapping: ensures any undefined console method calls are | ||||
| 	 * ignored silently.  This was required specifically by the | ||||
| 	 * DeveloperToolsWG proposal (and was implemented also by Firefox: | ||||
| 	 * https://bugzilla.mozilla.org/show_bug.cgi?id=629607).  This is | ||||
| 	 * apparently no longer the preferred way of implementing console. | ||||
| 	 * When Proxy is enabled, whitelist at least .toJSON() to avoid | ||||
| 	 * confusing JX serialization of the console object. | ||||
| 	 */ | ||||
|  | ||||
| 	if (flags & DUK_CONSOLE_PROXY_WRAPPER) { | ||||
| 		/* Tolerate failure to initialize Proxy wrapper in case | ||||
| 		 * Proxy support is disabled. | ||||
| 		 */ | ||||
| 		(void) duk_peval_string_noresult(ctx, | ||||
| 			"(function(){" | ||||
| 			    "var D=function(){};" | ||||
| 			    "var W={toJSON:true};"  /* whitelisted */ | ||||
| 			    "console=new Proxy(console,{" | ||||
| 			        "get:function(t,k){" | ||||
| 			            "var v=t[k];" | ||||
| 			            "return typeof v==='function'||W[k]?v:D;" | ||||
| 			        "}" | ||||
| 			    "});" | ||||
| 			"})();" | ||||
| 		); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										29
									
								
								core/src/duktape/duk_console.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								core/src/duktape/duk_console.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| #if !defined(DUK_CONSOLE_H_INCLUDED) | ||||
| #define DUK_CONSOLE_H_INCLUDED | ||||
|  | ||||
| #include "duktape.h" | ||||
|  | ||||
| #if defined(__cplusplus) | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| /* Use a proxy wrapper to make undefined methods (console.foo()) no-ops. */ | ||||
| #define DUK_CONSOLE_PROXY_WRAPPER  (1U << 0) | ||||
|  | ||||
| /* Flush output after every call. */ | ||||
| #define DUK_CONSOLE_FLUSH          (1U << 1) | ||||
|  | ||||
| /* Send output to stdout only (default is mixed stdout/stderr). */ | ||||
| #define DUK_CONSOLE_STDOUT_ONLY    (1U << 2) | ||||
|  | ||||
| /* Send output to stderr only (default is mixed stdout/stderr). */ | ||||
| #define DUK_CONSOLE_STDERR_ONLY    (1U << 3) | ||||
|  | ||||
| /* Initialize the console system */ | ||||
| extern void duk_console_init(duk_context *ctx, duk_uint_t flags); | ||||
|  | ||||
| #if defined(__cplusplus) | ||||
| } | ||||
| #endif  /* end 'extern "C"' wrapper */ | ||||
|  | ||||
| #endif  /* DUK_CONSOLE_H_INCLUDED */ | ||||
							
								
								
									
										99755
									
								
								core/src/duktape/duktape.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99755
									
								
								core/src/duktape/duktape.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1450
									
								
								core/src/duktape/duktape.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1450
									
								
								core/src/duktape/duktape.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										43
									
								
								core/src/event.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								core/src/event.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| #pragma once | ||||
| #include <vector> | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| template <class T> | ||||
| class Event { | ||||
| public: | ||||
|     Event() {} | ||||
|     ~Event() {} | ||||
|  | ||||
|     struct EventHandler { | ||||
|         EventHandler() {} | ||||
|         EventHandler(void (*handler)(T, void*), void* ctx) { | ||||
|             this->handler = handler; | ||||
|             this->ctx = ctx; | ||||
|         } | ||||
|  | ||||
|         void (*handler)(T, void*); | ||||
|         void* ctx; | ||||
|     }; | ||||
|  | ||||
|     void emit(T value) { | ||||
|         for (auto const& handler : handlers) { | ||||
|             handler.handler(value, handler.ctx); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void bindHandler(const EventHandler& handler) { | ||||
|         handlers.push_back(handler); | ||||
|     } | ||||
|  | ||||
|     void unbindHandler(const EventHandler& handler) { | ||||
|         if (handlers.find(handler) == handlers.end()) { | ||||
|             spdlog::error("Tried to remove a non-existant event handler"); | ||||
|             return; | ||||
|         } | ||||
|         handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end()); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::vector<EventHandler> handlers; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										49
									
								
								core/src/gui/colormaps.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								core/src/gui/colormaps.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| #include <gui/colormaps.h> | ||||
| #include <filesystem> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <fstream> | ||||
| #include <json.hpp> | ||||
|  | ||||
| using nlohmann::json; | ||||
|  | ||||
| namespace colormaps { | ||||
|     std::map<std::string, Map> maps; | ||||
|  | ||||
|     void loadMap(std::string path) { | ||||
|         if (!std::filesystem::is_regular_file(path)) { | ||||
|             spdlog::error("Could not load {0}, file doesn't exist", path); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         std::ifstream file(path.c_str()); | ||||
|         json data; | ||||
|         file >> data; | ||||
|         file.close(); | ||||
|  | ||||
|         Map map; | ||||
|         std::vector<std::string> mapTxt; | ||||
|  | ||||
|         try { | ||||
|             map.name = data["name"]; | ||||
|             map.author = data["author"]; | ||||
|             mapTxt = data["map"].get<std::vector<std::string>>(); | ||||
|         } | ||||
|         catch (const std::exception&) { | ||||
|             spdlog::error("Could not load {0}", path); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         map.entryCount = mapTxt.size(); | ||||
|         map.map = new float[mapTxt.size() * 3]; | ||||
|         int i = 0; | ||||
|         for(auto const& col : mapTxt) { | ||||
|             uint8_t r, g, b, a; | ||||
|             map.map[i * 3] = std::stoi(col.substr(1, 2), NULL, 16); | ||||
|             map.map[(i * 3) + 1] = std::stoi(col.substr(3, 2), NULL, 16); | ||||
|             map.map[(i * 3) + 2] = std::stoi(col.substr(5, 2), NULL, 16); | ||||
|             i++; | ||||
|         } | ||||
|  | ||||
|         maps[map.name] = map; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								core/src/gui/colormaps.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								core/src/gui/colormaps.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <module.h> | ||||
| #include <map> | ||||
|  | ||||
| namespace colormaps { | ||||
|     struct Map { | ||||
|         std::string name; | ||||
|         std::string author; | ||||
|         float* map; | ||||
|         int entryCount; | ||||
|     }; | ||||
|  | ||||
|     void loadMap(std::string path); | ||||
|  | ||||
|     SDRPP_EXPORT std::map<std::string, Map> maps; | ||||
| } | ||||
							
								
								
									
										61
									
								
								core/src/gui/dialogs/credits.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								core/src/gui/dialogs/credits.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| #include <gui/dialogs/credits.h> | ||||
| #include <imgui.h> | ||||
| #include <gui/icons.h> | ||||
| #include <gui/style.h> | ||||
| #include <config.h> | ||||
| #include <credits.h> | ||||
| #include <version.h> | ||||
|  | ||||
| namespace credits { | ||||
|     ImFont* bigFont; | ||||
|  | ||||
|     void init() { | ||||
|                  | ||||
|     } | ||||
|  | ||||
|     void show() { | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f)); | ||||
|         ImGui::OpenPopup("Credits"); | ||||
|         ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove); | ||||
|  | ||||
|         ImGui::PushFont(style::hugeFont); | ||||
|         ImGui::Text("SDR++    "); | ||||
|         ImGui::PopFont(); | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::Image(icons::LOGO, ImVec2(128, 128)); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|  | ||||
|         ImGui::Text("This software is brought to you by\n\n"); | ||||
|  | ||||
|         ImGui::Columns(3, "CreditColumns", true); | ||||
|  | ||||
|         ImGui::Text("Contributors"); | ||||
|         for (int i = 0; i < sdrpp_credits::contributorCount; i++) { | ||||
|             ImGui::BulletText("%s", sdrpp_credits::contributors[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Libraries"); | ||||
|         for (int i = 0; i < sdrpp_credits::libraryCount; i++) { | ||||
|             ImGui::BulletText("%s", sdrpp_credits::libraries[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Patrons"); | ||||
|         for (int i = 0; i < sdrpp_credits::patronCount; i++) { | ||||
|             ImGui::BulletText("%s", sdrpp_credits::patrons[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::Columns(1, "CreditColumnsEnd", true); | ||||
|  | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Text("SDR++ v" VERSION_STR); | ||||
|  | ||||
|         ImGui::EndPopup(); | ||||
|         ImGui::PopStyleVar(1); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										6
									
								
								core/src/gui/dialogs/credits.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/dialogs/credits.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace credits { | ||||
|     void init(); | ||||
|     void show(); | ||||
| } | ||||
							
								
								
									
										85
									
								
								core/src/gui/dialogs/loading_screen.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								core/src/gui/dialogs/loading_screen.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| #include <GL/glew.h> | ||||
| #include <gui/dialogs/loading_screen.h> | ||||
| #include <gui/main_window.h> | ||||
| #include <imgui.h> | ||||
| #include "imgui_impl_glfw.h" | ||||
| #include "imgui_impl_opengl3.h" | ||||
| #include <gui/icons.h> | ||||
| #include <gui/style.h> | ||||
| #include <credits.h> | ||||
|  | ||||
| namespace LoadingScreen { | ||||
|     GLFWwindow* _win; | ||||
|  | ||||
|     void setWindow(GLFWwindow* win) { | ||||
|         _win = win; | ||||
|     } | ||||
|  | ||||
|     void show(std::string msg) { | ||||
|         glfwPollEvents(); | ||||
|         ImGui_ImplOpenGL3_NewFrame(); | ||||
|         ImGui_ImplGlfw_NewFrame(); | ||||
|  | ||||
|         ImGui::NewFrame(); | ||||
|         ImGui::Begin("Main", NULL, WINDOW_FLAGS); | ||||
|  | ||||
|  | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f)); | ||||
|         ImGui::OpenPopup("Credits"); | ||||
|         ImGui::PushStyleColor(ImGuiCol_ModalWindowDarkening, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); | ||||
|         ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground); | ||||
|  | ||||
|         ImGui::PushFont(style::hugeFont); | ||||
|         ImGui::Text("SDR++    "); | ||||
|         ImGui::PopFont(); | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::Image(icons::LOGO, ImVec2(128, 128)); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|  | ||||
|         ImGui::Text("This software is brought to you by\n\n"); | ||||
|  | ||||
|         ImGui::Columns(3, "CreditColumns", true); | ||||
|  | ||||
|         ImGui::Text("Contributors"); | ||||
|         for (int i = 0; i < sdrpp_credits::contributorCount; i++) { | ||||
|             ImGui::BulletText("%s", sdrpp_credits::contributors[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Libraries"); | ||||
|         for (int i = 0; i < sdrpp_credits::libraryCount; i++) { | ||||
|             ImGui::BulletText("%s", sdrpp_credits::libraries[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::NextColumn(); | ||||
|         ImGui::Text("Patrons"); | ||||
|         for (int i = 0; i < sdrpp_credits::patronCount; i++) { | ||||
|             ImGui::BulletText("%s", sdrpp_credits::patrons[i]); | ||||
|         } | ||||
|  | ||||
|         ImGui::Columns(1, "CreditColumnsEnd", true); | ||||
|  | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Spacing(); | ||||
|         ImGui::Text("%s", msg.c_str()); | ||||
|  | ||||
|         ImGui::EndPopup(); | ||||
|         ImGui::PopStyleVar(1); | ||||
|         ImGui::PopStyleColor(1); | ||||
|  | ||||
|         ImGui::End(); | ||||
|  | ||||
|         ImGui::Render(); | ||||
|         int display_w, display_h; | ||||
|         glfwGetFramebufferSize(_win, &display_w, &display_h); | ||||
|         glViewport(0, 0, display_w, display_h); | ||||
|         glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f); | ||||
|         glClear(GL_COLOR_BUFFER_BIT); | ||||
|         ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); | ||||
|  | ||||
|         glfwSwapBuffers(_win); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								core/src/gui/dialogs/loading_screen.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								core/src/gui/dialogs/loading_screen.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #pragma once | ||||
| #include <thread> | ||||
| #include <string> | ||||
| #include <mutex> | ||||
| #include <GLFW/glfw3.h> | ||||
|  | ||||
| namespace LoadingScreen { | ||||
|     void setWindow(GLFWwindow* win); | ||||
|     void show(std::string msg); | ||||
| }; | ||||
							
								
								
									
										7
									
								
								core/src/gui/gui.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								core/src/gui/gui.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| #include <gui/gui.h> | ||||
|  | ||||
| namespace gui { | ||||
|     ImGui::WaterFall waterfall; | ||||
|     FrequencySelect freqSelect; | ||||
|     Menu menu; | ||||
| }; | ||||
							
								
								
									
										14
									
								
								core/src/gui/gui.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								core/src/gui/gui.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| #include <gui/widgets/waterfall.h> | ||||
| #include <gui/widgets/frequency_select.h> | ||||
| #include <gui/widgets/menu.h> | ||||
| #include <gui/dialogs/loading_screen.h> | ||||
| #include <module.h> | ||||
|  | ||||
| namespace gui { | ||||
|     SDRPP_EXPORT ImGui::WaterFall waterfall; | ||||
|     SDRPP_EXPORT FrequencySelect freqSelect; | ||||
|     SDRPP_EXPORT Menu menu; | ||||
|  | ||||
|     void selectSource(std::string name); | ||||
| }; | ||||
							
								
								
									
										53
									
								
								core/src/gui/icons.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								core/src/gui/icons.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| #include <gui/icons.h> | ||||
| #include <stdint.h> | ||||
| #include <GL/glew.h> | ||||
| #include <config.h> | ||||
| #include <options.h> | ||||
|  | ||||
| #define STB_IMAGE_IMPLEMENTATION | ||||
| #include <imgui/stb_image.h> | ||||
| #include <filesystem> | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| namespace icons { | ||||
|     ImTextureID LOGO; | ||||
|     ImTextureID PLAY; | ||||
|     ImTextureID STOP; | ||||
|     ImTextureID MENU; | ||||
|     ImTextureID MUTED; | ||||
|     ImTextureID UNMUTED; | ||||
|     ImTextureID NORMAL_TUNING; | ||||
|     ImTextureID CENTER_TUNING; | ||||
|  | ||||
|     GLuint loadTexture(std::string path) { | ||||
|         int w,h,n; | ||||
|         stbi_uc* data = stbi_load(path.c_str(), &w, &h, &n, 0); | ||||
|         GLuint texId; | ||||
|         glGenTextures(1, &texId); | ||||
|         glBindTexture(GL_TEXTURE_2D, texId); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||
|         glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||||
|         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)data); | ||||
|         stbi_image_free(data); | ||||
|         return texId; | ||||
|     } | ||||
|  | ||||
|     bool load(std::string resDir) { | ||||
|         if (!std::filesystem::is_directory(resDir)) { | ||||
|             spdlog::error("Inavlid resource directory: {0}", resDir); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         LOGO = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/sdrpp.png"); | ||||
|         PLAY = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/play.png"); | ||||
|         STOP = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/stop.png"); | ||||
|         MENU = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/menu.png"); | ||||
|         MUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/muted.png"); | ||||
|         UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png"); | ||||
|         NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png"); | ||||
|         CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png"); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								core/src/gui/icons.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								core/src/gui/icons.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
| #include <imgui/imgui.h> | ||||
| #include <GL/glew.h> | ||||
| #include <string> | ||||
|  | ||||
| namespace icons { | ||||
|     extern ImTextureID LOGO; | ||||
|     extern ImTextureID PLAY; | ||||
|     extern ImTextureID STOP; | ||||
|     extern ImTextureID MENU; | ||||
|     extern ImTextureID MUTED; | ||||
|     extern ImTextureID UNMUTED; | ||||
|     extern ImTextureID NORMAL_TUNING; | ||||
|     extern ImTextureID CENTER_TUNING; | ||||
|  | ||||
|     GLuint loadTexture(std::string path); | ||||
|     bool load(std::string resDir); | ||||
| } | ||||
							
								
								
									
										642
									
								
								core/src/gui/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										642
									
								
								core/src/gui/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,642 @@ | ||||
| #include <gui/main_window.h> | ||||
| #include <gui/gui.h> | ||||
| #include "imgui.h" | ||||
| #include "imgui_impl_glfw.h" | ||||
| #include "imgui_impl_opengl3.h" | ||||
| #include <stdio.h> | ||||
| #include <GL/glew.h> | ||||
| #include <GLFW/glfw3.h> | ||||
| #include <imgui_plot.h> | ||||
| #include <thread> | ||||
| #include <complex> | ||||
| #include <gui/widgets/waterfall.h> | ||||
| #include <gui/widgets/frequency_select.h> | ||||
| #include <fftw3.h> | ||||
| #include <signal_path/dsp.h> | ||||
| #include <gui/icons.h> | ||||
| #include <gui/widgets/bandplan.h> | ||||
| #include <watcher.h> | ||||
| #include <signal_path/vfo_manager.h> | ||||
| #include <gui/style.h> | ||||
| #include <config.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <gui/menus/source.h> | ||||
| #include <gui/menus/display.h> | ||||
| #include <gui/menus/bandplan.h> | ||||
| #include <gui/menus/sink.h> | ||||
| #include <gui/menus/scripting.h> | ||||
| #include <gui/dialogs/credits.h> | ||||
| #include <filesystem> | ||||
| #include <signal_path/source.h> | ||||
| #include <gui/dialogs/loading_screen.h> | ||||
| #include <options.h> | ||||
| #include <gui/colormaps.h> | ||||
|  | ||||
| // const int FFTSizes[] = { | ||||
| //     65536, | ||||
| //     32768, | ||||
| //     16384, | ||||
| //     8192, | ||||
| //     4096, | ||||
| //     2048 | ||||
| // }; | ||||
|  | ||||
| // const char* FFTSizesStr[] = { | ||||
| //     "65536", | ||||
| //     "32768", | ||||
| //     "16384", | ||||
| //     "8192", | ||||
| //     "4096", | ||||
| //     "2048" | ||||
| // }; | ||||
|  | ||||
| // int fftSizeId = 0; | ||||
| int fftSize = 8192 * 8; | ||||
|  | ||||
| std::thread worker; | ||||
| std::mutex fft_mtx; | ||||
| fftwf_complex *fft_in, *fft_out; | ||||
| fftwf_plan p; | ||||
| float* tempFFT; | ||||
| float* FFTdata; | ||||
| char buf[1024]; | ||||
| bool experimentalZoom = false; | ||||
|  | ||||
|  | ||||
|  | ||||
| void fftHandler(dsp::complex_t* samples, int count, void* ctx) { | ||||
|     memcpy(fft_in, samples, count * sizeof(dsp::complex_t)); | ||||
|     fftwf_execute(p); | ||||
|     int half = count / 2; | ||||
|  | ||||
|     volk_32fc_s32f_power_spectrum_32f(tempFFT, (lv_32fc_t*)fft_out, count, count); | ||||
|     volk_32f_s32f_multiply_32f(FFTdata, tempFFT, 0.5f, count); | ||||
|  | ||||
|     memcpy(tempFFT, &FFTdata[half], half * sizeof(float)); | ||||
|     memmove(&FFTdata[half], FFTdata, half * sizeof(float)); | ||||
|     memcpy(FFTdata, tempFFT, half * sizeof(float)); | ||||
|  | ||||
|     float* fftBuf = gui::waterfall.getFFTBuffer(); | ||||
|     if (fftBuf == NULL) { | ||||
|         gui::waterfall.pushFFT(); | ||||
|         return; | ||||
|     } | ||||
|     float last = FFTdata[0]; | ||||
|     for (int i = 0; i < count; i++) { | ||||
|         last = (FFTdata[i] * 0.1f) + (last * 0.9f); | ||||
|         fftBuf[i] = last; | ||||
|     } | ||||
|     gui::waterfall.pushFFT(); | ||||
| } | ||||
|  | ||||
| watcher<uint64_t> freq((uint64_t)90500000); | ||||
| watcher<double> vfoFreq(92000000.0); | ||||
| float fftMin = -70.0; | ||||
| float fftMax = 0.0; | ||||
| watcher<double> offset(0.0, true); | ||||
| float bw = 8000000; | ||||
| bool playing = false; | ||||
| watcher<bool> dcbias(false, false); | ||||
| bool showCredits = false; | ||||
| std::string audioStreamName = ""; | ||||
| std::string sourceName = ""; | ||||
| int menuWidth = 300; | ||||
| bool grabbingMenu = false; | ||||
| int newWidth = 300; | ||||
| int fftHeight = 300; | ||||
| bool showMenu = true; | ||||
| bool centerTuning = false; | ||||
| dsp::stream<dsp::complex_t> dummyStream; | ||||
| bool demoWindow = false; | ||||
|  | ||||
| void windowInit() { | ||||
|     LoadingScreen::show("Initializing UI"); | ||||
|     gui::waterfall.init(); | ||||
|     gui::waterfall.setRawFFTSize(fftSize); | ||||
|  | ||||
|     tempFFT = new float[fftSize]; | ||||
|     FFTdata = new float[fftSize]; | ||||
|  | ||||
|     credits::init(); | ||||
|  | ||||
|     core::configManager.aquire(); | ||||
|     gui::menu.order = core::configManager.conf["menuOrder"].get<std::vector<std::string>>(); | ||||
|     std::string modulesDir = core::configManager.conf["modulesDirectory"]; | ||||
|     std::string resourcesDir = core::configManager.conf["resourcesDirectory"]; | ||||
|     core::configManager.release(); | ||||
|  | ||||
|     gui::menu.registerEntry("Source", sourecmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Scripting", scriptingmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Display", displaymenu::draw, NULL); | ||||
|      | ||||
|     gui::freqSelect.init(); | ||||
|  | ||||
|     // Set default values for waterfall in case no source init's it | ||||
|     gui::waterfall.setBandwidth(8000000); | ||||
|     gui::waterfall.setViewBandwidth(8000000); | ||||
|      | ||||
|     fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize); | ||||
|     fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize); | ||||
|     p = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE); | ||||
|  | ||||
|     sigpath::signalPath.init(8000000, 20, fftSize, &dummyStream, (dsp::complex_t*)fft_in, fftHandler); | ||||
|     sigpath::signalPath.start(); | ||||
|  | ||||
|     spdlog::info("Loading modules"); | ||||
|  | ||||
|     // Load modules from /module directory | ||||
|     if (std::filesystem::is_directory(modulesDir)) { | ||||
|         for (const auto & file : std::filesystem::directory_iterator(modulesDir)) { | ||||
|             std::string path = file.path().generic_string(); | ||||
|             if (file.path().extension().generic_string() != SDRPP_MOD_EXTENTSION) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (!file.is_regular_file()) { continue; } | ||||
|             spdlog::info("Loading {0}", path); | ||||
|             LoadingScreen::show("Loading " + path); | ||||
|             core::moduleManager.loadModule(path); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         spdlog::warn("Module directory {0} does not exist, not loading modules from directory", modulesDir); | ||||
|     } | ||||
|  | ||||
|     // Read module config | ||||
|     core::configManager.aquire(); | ||||
|     std::vector<std::string> modules = core::configManager.conf["modules"]; | ||||
|     std::map<std::string, std::string> modList = core::configManager.conf["moduleInstances"]; | ||||
|     core::configManager.release(); | ||||
|  | ||||
|     // Load additional modules specified through config | ||||
|     for (auto const& path : modules) { | ||||
|         spdlog::info("Loading {0}", path); | ||||
|         LoadingScreen::show("Loading " + path); | ||||
|         core::moduleManager.loadModule(path); | ||||
|     } | ||||
|  | ||||
|     // Create module instances | ||||
|     for (auto const& [name, module] : modList) { | ||||
|         spdlog::info("Initializing {0} ({1})", name, module); | ||||
|         LoadingScreen::show("Initializing " + name + " (" + module + ")"); | ||||
|         core::moduleManager.createInstance(name, module); | ||||
|     } | ||||
|  | ||||
|     // Load color maps | ||||
|     LoadingScreen::show("Loading color maps"); | ||||
|     spdlog::info("Loading color maps"); | ||||
|     if (std::filesystem::is_directory(resourcesDir + "/colormaps")) { | ||||
|         for (const auto & file : std::filesystem::directory_iterator(resourcesDir + "/colormaps")) { | ||||
|             std::string path = file.path().generic_string(); | ||||
|             LoadingScreen::show("Loading " + path); | ||||
|             spdlog::info("Loading {0}", path); | ||||
|             if (file.path().extension().generic_string() != ".json") { | ||||
|                 continue; | ||||
|             } | ||||
|             if (!file.is_regular_file()) { continue; } | ||||
|             colormaps::loadMap(path); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         spdlog::warn("Color map directory {0} does not exist, not loading modules from directory", modulesDir); | ||||
|     } | ||||
|  | ||||
|     gui::waterfall.updatePalletteFromArray(colormaps::maps["Turbo"].map, colormaps::maps["Turbo"].entryCount); | ||||
|  | ||||
|     sourecmenu::init(); | ||||
|     sinkmenu::init(); | ||||
|     scriptingmenu::init(); | ||||
|     bandplanmenu::init(); | ||||
|     displaymenu::init(); | ||||
|  | ||||
|     // TODO for 0.2.5 | ||||
|     // Add "select file" option for the file source | ||||
|     // Have a good directory system on both linux and windows | ||||
|     // Switch to double buffering (should fix occassional underruns) | ||||
|     // Fix gain not updated on startup, soapysdr | ||||
|  | ||||
|     // TODO for 0.2.6 | ||||
|     // Add a module add/remove/change order menu | ||||
|  | ||||
|     // Update UI settings | ||||
|     LoadingScreen::show("Loading configuration"); | ||||
|     core::configManager.aquire(); | ||||
|     fftMin = core::configManager.conf["min"]; | ||||
|     fftMax = core::configManager.conf["max"]; | ||||
|     gui::waterfall.setFFTMin(fftMin); | ||||
|     gui::waterfall.setWaterfallMin(fftMin); | ||||
|     gui::waterfall.setFFTMax(fftMax); | ||||
|     gui::waterfall.setWaterfallMax(fftMax); | ||||
|  | ||||
|     double frequency = core::configManager.conf["frequency"]; | ||||
|  | ||||
|     gui::freqSelect.setFrequency(frequency); | ||||
|     gui::freqSelect.frequencyChanged = false; | ||||
|     sigpath::sourceManager.tune(frequency); | ||||
|     gui::waterfall.setCenterFrequency(frequency); | ||||
|     bw = gui::waterfall.getBandwidth(); | ||||
|     gui::waterfall.vfoFreqChanged = false; | ||||
|     gui::waterfall.centerFreqMoved = false; | ||||
|     gui::waterfall.selectFirstVFO(); | ||||
|  | ||||
|     menuWidth = core::configManager.conf["menuWidth"]; | ||||
|     newWidth = menuWidth; | ||||
|  | ||||
|     fftHeight = core::configManager.conf["fftHeight"]; | ||||
|     gui::waterfall.setFFTHeight(fftHeight); | ||||
|  | ||||
|     centerTuning = core::configManager.conf["centerTuning"]; | ||||
|  | ||||
|     core::configManager.release(); | ||||
| } | ||||
|  | ||||
| void setVFO(double freq) { | ||||
|     double viewBW = gui::waterfall.getViewBandwidth(); | ||||
|     double BW = gui::waterfall.getBandwidth(); | ||||
|     if (gui::waterfall.selectedVFO == "") { | ||||
|         gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0)); | ||||
|         gui::waterfall.setCenterFrequency(freq); | ||||
|         sigpath::sourceManager.tune(freq); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO]; | ||||
|  | ||||
|     double currentOff =  vfo->centerOffset; | ||||
|     double currentTune = gui::waterfall.getCenterFrequency() + vfo->generalOffset; | ||||
|     double delta = freq - currentTune; | ||||
|  | ||||
|     double newVFO = currentOff + delta; | ||||
|     double vfoBW = vfo->bandwidth; | ||||
|     double vfoBottom = newVFO - (vfoBW / 2.0); | ||||
|     double vfoTop = newVFO + (vfoBW / 2.0); | ||||
|  | ||||
|     double view = gui::waterfall.getViewOffset(); | ||||
|     double viewBottom = view - (viewBW / 2.0); | ||||
|     double viewTop = view + (viewBW / 2.0); | ||||
|  | ||||
|     double wholeFreq = gui::waterfall.getCenterFrequency(); | ||||
|     double bottom = -(BW / 2.0); | ||||
|     double top = (BW / 2.0); | ||||
|  | ||||
|     if (centerTuning) { | ||||
|         gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0)); | ||||
|         gui::waterfall.setCenterFrequency(freq); | ||||
|         gui::waterfall.setViewOffset(0); | ||||
|         sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, 0); | ||||
|         sigpath::sourceManager.tune(freq); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // VFO still fints in the view | ||||
|     if (vfoBottom > viewBottom && vfoTop < viewTop) { | ||||
|         sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // VFO too low for current SDR tuning | ||||
|     if (vfoBottom < bottom) { | ||||
|         gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0)); | ||||
|         double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0); | ||||
|         sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, newVFOOffset); | ||||
|         gui::waterfall.setCenterFrequency(freq - newVFOOffset); | ||||
|         sigpath::sourceManager.tune(freq - newVFOOffset); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // VFO too high for current SDR tuning | ||||
|     if (vfoTop > top) { | ||||
|         gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0)); | ||||
|         double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0); | ||||
|         sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, newVFOOffset); | ||||
|         gui::waterfall.setCenterFrequency(freq - newVFOOffset); | ||||
|         sigpath::sourceManager.tune(freq - newVFOOffset); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // VFO is still without the SDR's bandwidth | ||||
|     if (delta < 0) { | ||||
|         double newViewOff = vfoTop - (viewBW / 2.0) + (viewBW / 10.0); | ||||
|         double newViewBottom = newViewOff - (viewBW / 2.0); | ||||
|         double newViewTop = newViewOff + (viewBW / 2.0); | ||||
|  | ||||
|         if (newViewBottom > bottom) { | ||||
|             gui::waterfall.setViewOffset(newViewOff); | ||||
|             sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0)); | ||||
|         double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0); | ||||
|         sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset); | ||||
|         gui::waterfall.setCenterFrequency(freq - newVFOOffset); | ||||
|         sigpath::sourceManager.tune(freq - newVFOOffset); | ||||
|     } | ||||
|     else { | ||||
|         double newViewOff = vfoBottom + (viewBW / 2.0) - (viewBW / 10.0); | ||||
|         double newViewBottom = newViewOff - (viewBW / 2.0); | ||||
|         double newViewTop = newViewOff + (viewBW / 2.0); | ||||
|  | ||||
|         if (newViewTop < top) { | ||||
|             gui::waterfall.setViewOffset(newViewOff); | ||||
|             sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFO); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0)); | ||||
|         double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0); | ||||
|         sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, newVFOOffset); | ||||
|         gui::waterfall.setCenterFrequency(freq - newVFOOffset); | ||||
|         sigpath::sourceManager.tune(freq - newVFOOffset); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void drawWindow() { | ||||
|     ImGui::Begin("Main", NULL, WINDOW_FLAGS); | ||||
|  | ||||
|     ImGui::WaterfallVFO* vfo = NULL; | ||||
|     if (gui::waterfall.selectedVFO != "") { | ||||
|         vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO]; | ||||
|     } | ||||
|  | ||||
|     if (vfo != NULL) { | ||||
|         if (vfo->centerOffsetChanged) { | ||||
|             if (centerTuning) { | ||||
|                 setVFO(gui::waterfall.getCenterFrequency() + vfo->generalOffset); | ||||
|             } | ||||
|             gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset); | ||||
|             gui::freqSelect.frequencyChanged = false; | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["frequency"] = gui::freqSelect.frequency; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     sigpath::vfoManager.updateFromWaterfall(&gui::waterfall); | ||||
|  | ||||
|     if (gui::waterfall.selectedVFOChanged && vfo != NULL) { | ||||
|         gui::waterfall.selectedVFOChanged = false; | ||||
|         gui::freqSelect.setFrequency(vfo->generalOffset + gui::waterfall.getCenterFrequency()); | ||||
|         gui::freqSelect.frequencyChanged = false; | ||||
|         core::configManager.aquire(); | ||||
|         core::configManager.conf["frequency"] = gui::freqSelect.frequency; | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|  | ||||
|     if (gui::freqSelect.frequencyChanged) { | ||||
|         gui::freqSelect.frequencyChanged = false; | ||||
|         setVFO(gui::freqSelect.frequency); | ||||
|         if (vfo != NULL) { | ||||
|             vfo->centerOffsetChanged = false; | ||||
|             vfo->lowerOffsetChanged = false; | ||||
|             vfo->upperOffsetChanged = false; | ||||
|         } | ||||
|         core::configManager.aquire(); | ||||
|         core::configManager.conf["frequency"] = gui::freqSelect.frequency; | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|  | ||||
|     if (gui::waterfall.centerFreqMoved) { | ||||
|         gui::waterfall.centerFreqMoved = false; | ||||
|         sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency()); | ||||
|         if (vfo != NULL) { | ||||
|             gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset); | ||||
|         } | ||||
|         else { | ||||
|             gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency()); | ||||
|         } | ||||
|         core::configManager.aquire(); | ||||
|         core::configManager.conf["frequency"] = gui::freqSelect.frequency; | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|  | ||||
|     int _fftHeight = gui::waterfall.getFFTHeight(); | ||||
|     if (fftHeight != _fftHeight) { | ||||
|         fftHeight = _fftHeight; | ||||
|         core::configManager.aquire(); | ||||
|         core::configManager.conf["fftHeight"] = fftHeight; | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|  | ||||
|     ImVec2 vMin = ImGui::GetWindowContentRegionMin(); | ||||
|     ImVec2 vMax = ImGui::GetWindowContentRegionMax(); | ||||
|  | ||||
|     int width = vMax.x - vMin.x; | ||||
|     int height = vMax.y - vMin.y; | ||||
|  | ||||
|     // To Bar | ||||
|     ImGui::PushID(ImGui::GetID("sdrpp_menu_btn")); | ||||
|     if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) { | ||||
|         showMenu = !showMenu; | ||||
|     } | ||||
|     ImGui::PopID(); | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|  | ||||
|     if (playing) { | ||||
|         ImGui::PushID(ImGui::GetID("sdrpp_stop_btn")); | ||||
|         if (ImGui::ImageButton(icons::STOP, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) { | ||||
|             sigpath::sourceManager.stop(); | ||||
|             playing = false; | ||||
|         } | ||||
|         ImGui::PopID(); | ||||
|     } | ||||
|     else { // TODO: Might need to check if there even is a device | ||||
|         ImGui::PushID(ImGui::GetID("sdrpp_play_btn")); | ||||
|         if (ImGui::ImageButton(icons::PLAY, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) { | ||||
|             sigpath::sourceManager.start(); | ||||
|             // TODO: tune in module instead | ||||
|             sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency()); | ||||
|             playing = true; | ||||
|         } | ||||
|         ImGui::PopID(); | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|  | ||||
|     //ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8); | ||||
|     sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248, 30, 5, true); | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|  | ||||
|     gui::freqSelect.draw(); | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|  | ||||
|     ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9); | ||||
|     if (centerTuning) { | ||||
|         ImGui::PushID(ImGui::GetID("sdrpp_ena_st_btn")); | ||||
|         if (ImGui::ImageButton(icons::CENTER_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) { | ||||
|             centerTuning = false; | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["centerTuning"] = centerTuning; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::PopID(); | ||||
|     } | ||||
|     else { // TODO: Might need to check if there even is a device | ||||
|         ImGui::PushID(ImGui::GetID("sdrpp_dis_st_btn")); | ||||
|         if (ImGui::ImageButton(icons::NORMAL_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) { | ||||
|             centerTuning = true; | ||||
|             setVFO(gui::freqSelect.frequency); | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["centerTuning"] = centerTuning; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::PopID(); | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|  | ||||
|     // Logo button | ||||
|     ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48); | ||||
|     ImGui::SetCursorPosY(10); | ||||
|     if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) { | ||||
|         showCredits = true; | ||||
|     } | ||||
|     if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { | ||||
|         showCredits = false; | ||||
|     } | ||||
|     if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) { | ||||
|         showCredits = false; | ||||
|     } | ||||
|  | ||||
|     // Handle menu resize | ||||
|     float curY = ImGui::GetCursorPosY(); | ||||
|     ImVec2 winSize = ImGui::GetWindowSize(); | ||||
|     ImVec2 mousePos = ImGui::GetMousePos(); | ||||
|     bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left); | ||||
|     bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left); | ||||
|     if (grabbingMenu) { | ||||
|         newWidth = mousePos.x; | ||||
|         newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250); | ||||
|         ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive)); | ||||
|     } | ||||
|     if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) { | ||||
|         ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); | ||||
|         if (click) { | ||||
|             grabbingMenu = true; | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow); | ||||
|     } | ||||
|     if(!down && grabbingMenu) { | ||||
|         grabbingMenu = false; | ||||
|         menuWidth = newWidth; | ||||
|         core::configManager.aquire(); | ||||
|         core::configManager.conf["menuWidth"] = menuWidth; | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|  | ||||
|     // Left Column | ||||
|  | ||||
|     if (showMenu) { | ||||
|         ImGui::Columns(3, "WindowColumns", false); | ||||
|         ImGui::SetColumnWidth(0, menuWidth); | ||||
|         ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60); | ||||
|         ImGui::SetColumnWidth(2, 60); | ||||
|         ImGui::BeginChild("Left Column"); | ||||
|         float menuColumnWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|  | ||||
|         gui::menu.draw(); | ||||
|  | ||||
|         if(ImGui::CollapsingHeader("Debug")) { | ||||
|             ImGui::Text("Frame time: %.3f ms/frame", 1000.0 / ImGui::GetIO().Framerate); | ||||
|             ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate); | ||||
|             ImGui::Text("Center Frequency: %.0f Hz", gui::waterfall.getCenterFrequency()); | ||||
|             ImGui::Text("Source name: %s", sourceName.c_str()); | ||||
|             if (ImGui::Checkbox("Test technique", &dcbias.val)) { | ||||
|                 //sigpath::signalPath.setDCBiasCorrection(dcbias.val); | ||||
|             } | ||||
|             ImGui::Checkbox("Show demo window", &demoWindow); | ||||
|             ImGui::Checkbox("Experimental zoom", &experimentalZoom); | ||||
|             ImGui::Spacing(); | ||||
|         } | ||||
|  | ||||
|         ImGui::EndChild(); | ||||
|     } | ||||
|     else { | ||||
|         // When hiding the menu bar | ||||
|         ImGui::Columns(3, "WindowColumns", false); | ||||
|         ImGui::SetColumnWidth(0, 8); | ||||
|         ImGui::SetColumnWidth(1, winSize.x - 8 - 60); | ||||
|         ImGui::SetColumnWidth(2, 60); | ||||
|     } | ||||
|  | ||||
|     // Right Column | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); | ||||
|     ImGui::NextColumn(); | ||||
|     ImGui::PopStyleVar(); | ||||
|  | ||||
|     ImGui::BeginChild("Waterfall"); | ||||
|  | ||||
|     gui::waterfall.draw();     | ||||
|  | ||||
|     ImGui::EndChild(); | ||||
|  | ||||
|      | ||||
|     ImGui::NextColumn(); | ||||
|     ImGui::BeginChild("WaterfallControls"); | ||||
|  | ||||
|     ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Zoom").x / 2.0)); | ||||
|     ImGui::Text("Zoom"); | ||||
|     ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10); | ||||
|     if (ImGui::VSliderFloat("##_7_", ImVec2(20.0, 150.0), &bw, gui::waterfall.getBandwidth(), 1000.0, "", (experimentalZoom ? 2.0 : 1.0))) { | ||||
|         gui::waterfall.setViewBandwidth(bw); | ||||
|         if (vfo != NULL) { | ||||
|             gui::waterfall.setViewOffset(vfo->centerOffset); // center vfo on screen | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ImGui::NewLine(); | ||||
|  | ||||
|     ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Max").x / 2.0)); | ||||
|     ImGui::Text("Max"); | ||||
|     ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10); | ||||
|     if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -100.0, "")) { | ||||
|         fftMax = std::max<float>(fftMax, fftMin + 10); | ||||
|         core::configManager.aquire(); | ||||
|         core::configManager.conf["max"] = fftMax; | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|  | ||||
|     ImGui::NewLine(); | ||||
|  | ||||
|     ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Min").x / 2.0)); | ||||
|     ImGui::Text("Min"); | ||||
|     ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10); | ||||
|     if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -100.0, "")) { | ||||
|         fftMin = std::min<float>(fftMax - 10, fftMin); | ||||
|         core::configManager.aquire(); | ||||
|         core::configManager.conf["min"] = fftMin; | ||||
|         core::configManager.release(true); | ||||
|     } | ||||
|  | ||||
|     ImGui::EndChild(); | ||||
|  | ||||
|     gui::waterfall.setFFTMin(fftMin); | ||||
|     gui::waterfall.setFFTMax(fftMax); | ||||
|     gui::waterfall.setWaterfallMin(fftMin); | ||||
|     gui::waterfall.setWaterfallMax(fftMax); | ||||
|  | ||||
|     ImGui::End(); | ||||
|  | ||||
|     if (showCredits) { | ||||
|         credits::show(); | ||||
|     } | ||||
|  | ||||
|     if (demoWindow) { | ||||
|         ImGui::ShowDemoWindow(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void setViewBandwidthSlider(float bandwidth) { | ||||
|     bw = bandwidth; | ||||
| } | ||||
|  | ||||
| bool sdrIsRunning() { | ||||
|     return playing; | ||||
| } | ||||
| @@ -1,12 +1,9 @@ | ||||
| #pragma once | ||||
| #include "imgui.h" | ||||
| #include "imgui_impl_glfw.h" | ||||
| #include "imgui_impl_opengl3.h" | ||||
| #include <stdio.h> | ||||
| #include <GL/glew.h> | ||||
| #include <GLFW/glfw3.h> | ||||
| 
 | ||||
| #define WINDOW_FLAGS    ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | ||||
| 
 | ||||
| void windowInit(); | ||||
| void drawWindow(); | ||||
| void drawWindow(); | ||||
| void setViewBandwidthSlider(float bandwidth); | ||||
| bool sdrIsRunning(); | ||||
							
								
								
									
										51
									
								
								core/src/gui/menus/bandplan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								core/src/gui/menus/bandplan.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| #include <gui/menus/bandplan.h> | ||||
| #include <gui/widgets/bandplan.h> | ||||
| #include <gui/gui.h> | ||||
| #include <core.h> | ||||
|  | ||||
| namespace bandplanmenu { | ||||
|     int bandplanId; | ||||
|     bool bandPlanEnabled; | ||||
|  | ||||
|     void init() { | ||||
|         // todo: check if the bandplan wasn't removed | ||||
|         if (bandplan::bandplanNames.size() == 0) { | ||||
|             gui::waterfall.hideBandplan(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (bandplan::bandplans.find(core::configManager.conf["bandPlan"]) != bandplan::bandplans.end()) { | ||||
|             std::string name = core::configManager.conf["bandPlan"]; | ||||
|             bandplanId = std::distance(bandplan::bandplanNames.begin(), std::find(bandplan::bandplanNames.begin(), | ||||
|                                     bandplan::bandplanNames.end(), name)); | ||||
|             gui::waterfall.bandplan = &bandplan::bandplans[name]; | ||||
|         } | ||||
|         else { | ||||
|             gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[0]]; | ||||
|         } | ||||
|  | ||||
|         bandPlanEnabled = core::configManager.conf["bandPlanEnabled"]; | ||||
|         bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan(); | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         float menuColumnWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|         ImGui::PushItemWidth(menuColumnWidth); | ||||
|         if (ImGui::Combo("##_4_", &bandplanId, bandplan::bandplanNameTxt.c_str())) { | ||||
|             gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[bandplanId]]; | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["bandPlan"] = bandplan::bandplanNames[bandplanId]; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::PopItemWidth(); | ||||
|         if (ImGui::Checkbox("Enabled", &bandPlanEnabled)) { | ||||
|             bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan(); | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["bandPlanEnabled"] = bandPlanEnabled; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId]]; | ||||
|         ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str()); | ||||
|         ImGui::Text("Author: %s", plan.authorName.c_str()); | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										6
									
								
								core/src/gui/menus/bandplan.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/menus/bandplan.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace bandplanmenu { | ||||
|     void init(); | ||||
|     void draw(void* ctx); | ||||
| }; | ||||
							
								
								
									
										59
									
								
								core/src/gui/menus/display.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								core/src/gui/menus/display.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| #include <gui/menus/display.h> | ||||
| #include <imgui.h> | ||||
| #include <gui/gui.h> | ||||
| #include <core.h> | ||||
| #include <gui/colormaps.h> | ||||
| #include <gui/gui.h> | ||||
|  | ||||
| namespace displaymenu { | ||||
|     bool showWaterfall; | ||||
|     int colorMapId = 0; | ||||
|     std::vector<std::string> colorMapNames; | ||||
|     std::string colorMapNamesTxt = ""; | ||||
|     std::string colorMapAuthor = ""; | ||||
|  | ||||
|     void init() { | ||||
|         showWaterfall = core::configManager.conf["showWaterfall"]; | ||||
|         showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall(); | ||||
|         std::string colormapName = core::configManager.conf["colorMap"]; | ||||
|         if (colormaps::maps.find(colormapName) != colormaps::maps.end()) { | ||||
|             colormaps::Map map = colormaps::maps[colormapName]; | ||||
|             gui::waterfall.updatePalletteFromArray(map.map, map.entryCount); | ||||
|         } | ||||
|  | ||||
|         for (auto const& [name, map] : colormaps::maps) { | ||||
|             colorMapNames.push_back(name); | ||||
|             colorMapNamesTxt += name; | ||||
|             colorMapNamesTxt += '\0'; | ||||
|             if (name == colormapName) { | ||||
|                 colorMapId = (colorMapNames.size() - 1); | ||||
|                 colorMapAuthor = map.author; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         float menuWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|         if (ImGui::Checkbox("Show Waterfall", &showWaterfall)) { | ||||
|             showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall(); | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["showWaterfall"] = showWaterfall; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         if (colorMapNames.size() > 0) { | ||||
|             ImGui::Text("Color Map"); | ||||
|             ImGui::SameLine(); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             if (ImGui::Combo("##_sdrpp_color_map_sel", &colorMapId, colorMapNamesTxt.c_str())) { | ||||
|                 colormaps::Map map = colormaps::maps[colorMapNames[colorMapId]]; | ||||
|                 gui::waterfall.updatePalletteFromArray(map.map, map.entryCount); | ||||
|                 core::configManager.aquire(); | ||||
|                 core::configManager.conf["colorMap"] = colorMapNames[colorMapId]; | ||||
|                 core::configManager.release(true); | ||||
|                 colorMapAuthor = map.author; | ||||
|             } | ||||
|             ImGui::Text("Color map Author: %s", colorMapAuthor.c_str()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										6
									
								
								core/src/gui/menus/display.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/menus/display.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace displaymenu { | ||||
|     void init(); | ||||
|     void draw(void* ctx); | ||||
| } | ||||
							
								
								
									
										22
									
								
								core/src/gui/menus/scripting.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								core/src/gui/menus/scripting.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| #include <gui/menus/scripting.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <imgui/imgui.h> | ||||
|  | ||||
| namespace scriptingmenu { | ||||
|     void init() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         float menuWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|         for (const auto& [name, script] : core::scriptManager.scripts) { | ||||
|             bool running = script->running; | ||||
|             if (running) { style::beginDisabled(); } | ||||
|             if (ImGui::Button(name.c_str(), ImVec2(menuWidth, 0))) { | ||||
|                 script->run(); | ||||
|             } | ||||
|             if (running) { style::endDisabled(); } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										6
									
								
								core/src/gui/menus/scripting.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/menus/scripting.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace scriptingmenu { | ||||
|     void init(); | ||||
|     void draw(void* ctx); | ||||
| } | ||||
							
								
								
									
										15
									
								
								core/src/gui/menus/sink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								core/src/gui/menus/sink.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| #include <gui/menus/sink.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
|  | ||||
| namespace sinkmenu { | ||||
|     void init() { | ||||
|         core::configManager.aquire(); | ||||
|         sigpath::sinkManager.loadSinksFromConfig(); | ||||
|         core::configManager.release(); | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         sigpath::sinkManager.showMenu(); | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										6
									
								
								core/src/gui/menus/sink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/menus/sink.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace sinkmenu { | ||||
|     void init(); | ||||
|     void draw(void* ctx); | ||||
| }; | ||||
							
								
								
									
										61
									
								
								core/src/gui/menus/source.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								core/src/gui/menus/source.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| #include <gui/menus/source.h> | ||||
| #include <imgui.h> | ||||
| #include <gui/gui.h> | ||||
| #include <core.h> | ||||
| #include <gui/main_window.h> | ||||
| #include <gui/style.h> | ||||
| #include <signal_path/signal_path.h> | ||||
|  | ||||
| namespace sourecmenu { | ||||
|     int sourceId = 0; | ||||
|     double freqOffset = 0.0; | ||||
|  | ||||
|     void init() { | ||||
|         core::configManager.aquire(); | ||||
|         std::string name = core::configManager.conf["source"]; | ||||
|         auto it = std::find(sigpath::sourceManager.sourceNames.begin(), sigpath::sourceManager.sourceNames.end(), name); | ||||
|         if (it != sigpath::sourceManager.sourceNames.end()) { | ||||
|             sigpath::sourceManager.selectSource(name); | ||||
|             sourceId = std::distance(sigpath::sourceManager.sourceNames.begin(), it); | ||||
|         } | ||||
|         else if (sigpath::sourceManager.sourceNames.size() > 0) { | ||||
|             sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[0]); | ||||
|         } | ||||
|         else { | ||||
|             spdlog::warn("No source available..."); | ||||
|         } | ||||
|         sigpath::sourceManager.setTuningOffset(core::configManager.conf["offset"]); | ||||
|         core::configManager.release(); | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         std::string items = ""; | ||||
|         for (std::string name : sigpath::sourceManager.sourceNames) { | ||||
|             items += name; | ||||
|             items += '\0'; | ||||
|         } | ||||
|         float itemWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|  | ||||
|         if (sdrIsRunning()) { style::beginDisabled(); } | ||||
|          | ||||
|         ImGui::SetNextItemWidth(itemWidth); | ||||
|         if (ImGui::Combo("##source", &sourceId, items.c_str())) { | ||||
|             sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[sourceId]); | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["source"] = sigpath::sourceManager.sourceNames[sourceId]; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         if (sdrIsRunning()) { style::endDisabled(); } | ||||
|  | ||||
|         sigpath::sourceManager.showSelectedMenu(); | ||||
|  | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::CalcTextSize("Offset (Hz)").x - 10); | ||||
|         if (ImGui::InputDouble("Offset (Hz)##freq_offset", &freqOffset, 1.0, 100.0)) { | ||||
|             sigpath::sourceManager.setTuningOffset(freqOffset); | ||||
|             core::configManager.aquire(); | ||||
|             core::configManager.conf["offset"] = freqOffset; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										6
									
								
								core/src/gui/menus/source.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/menus/source.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace sourecmenu { | ||||
|     void init(); | ||||
|     void draw(void* ctx); | ||||
| } | ||||
							
								
								
									
										121
									
								
								core/src/gui/style.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								core/src/gui/style.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| #include <gui/style.h> | ||||
| #include <imgui.h> | ||||
| #include <imgui_internal.h> | ||||
| #include <config.h> | ||||
| #include <options.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <filesystem> | ||||
|  | ||||
| namespace style { | ||||
|     ImFont* baseFont; | ||||
|     ImFont* bigFont; | ||||
|     ImFont* hugeFont; | ||||
|  | ||||
|     bool setDefaultStyle(std::string resDir) { | ||||
|         if (!std::filesystem::is_directory(resDir)) { | ||||
|             spdlog::error("Inavlid resource directory: {0}", resDir); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         ImGui::GetStyle().WindowRounding = 0.0f; | ||||
|         ImGui::GetStyle().ChildRounding = 0.0f; | ||||
|         ImGui::GetStyle().FrameRounding = 0.0f; | ||||
|         ImGui::GetStyle().GrabRounding = 0.0f; | ||||
|         ImGui::GetStyle().PopupRounding = 0.0f; | ||||
|         ImGui::GetStyle().ScrollbarRounding = 0.0f; | ||||
|  | ||||
|         baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f); | ||||
|         bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f); | ||||
|         hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f); | ||||
|  | ||||
|         ImGui::StyleColorsDark(); | ||||
|         //ImGui::StyleColorsLight(); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     void testtt() { | ||||
|         ImGui::StyleColorsLight(); | ||||
|     } | ||||
|  | ||||
|     bool setDarkStyle(std::string resDir) { | ||||
|         if (!std::filesystem::is_directory(resDir)) { | ||||
|             spdlog::error("Inavlid resource directory: {0}", resDir); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         ImGui::GetStyle().WindowRounding = 0.0f; | ||||
|         ImGui::GetStyle().ChildRounding = 0.0f; | ||||
|         ImGui::GetStyle().FrameRounding = 0.0f; | ||||
|         ImGui::GetStyle().GrabRounding = 0.0f; | ||||
|         ImGui::GetStyle().PopupRounding = 0.0f; | ||||
|         ImGui::GetStyle().ScrollbarRounding = 0.0f; | ||||
|  | ||||
|         baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f); | ||||
|         bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f); | ||||
|         hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f); | ||||
|  | ||||
|         ImGui::StyleColorsDark(); | ||||
|  | ||||
|         auto& style = ImGui::GetStyle(); | ||||
|  | ||||
|         ImVec4* colors = style.Colors; | ||||
|  | ||||
|         colors[ImGuiCol_Text]                   = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); | ||||
|         colors[ImGuiCol_TextDisabled]           = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); | ||||
|         colors[ImGuiCol_WindowBg]               = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); | ||||
|         colors[ImGuiCol_ChildBg]                = ImVec4(1.00f, 1.00f, 1.00f, 0.00f); | ||||
|         colors[ImGuiCol_PopupBg]                = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); | ||||
|         colors[ImGuiCol_Border]                 = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); | ||||
|         colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); | ||||
|         colors[ImGuiCol_FrameBg]                = ImVec4(0.20f, 0.21f, 0.22f, 0.54f); | ||||
|         colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.20f, 0.21f, 0.22f, 0.54f); | ||||
|         colors[ImGuiCol_FrameBgActive]          = ImVec4(0.20f, 0.21f, 0.22f, 0.54f); | ||||
|         colors[ImGuiCol_TitleBg]                = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); | ||||
|         colors[ImGuiCol_TitleBgActive]          = ImVec4(0.29f, 0.29f, 0.29f, 1.00f); | ||||
|         colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); | ||||
|         colors[ImGuiCol_MenuBarBg]              = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); | ||||
|         colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); | ||||
|         colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); | ||||
|         colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); | ||||
|         colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); | ||||
|         colors[ImGuiCol_CheckMark]              = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); | ||||
|         colors[ImGuiCol_SliderGrab]             = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); | ||||
|         colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); | ||||
|         colors[ImGuiCol_Button]                 = ImVec4(0.44f, 0.44f, 0.44f, 0.40f); | ||||
|         colors[ImGuiCol_ButtonHovered]          = ImVec4(0.44f, 0.44f, 0.44f, 0.45f); | ||||
|         colors[ImGuiCol_ButtonActive]           = ImVec4(0.44f, 0.44f, 0.44f, 0.40f); | ||||
|         colors[ImGuiCol_Header]                 = ImVec4(0.63f, 0.63f, 0.70f, 0.31f); | ||||
|         colors[ImGuiCol_HeaderHovered]          = ImVec4(0.63f, 0.63f, 0.70f, 0.40f); | ||||
|         colors[ImGuiCol_HeaderActive]           = ImVec4(0.63f, 0.63f, 0.70f, 0.31f); | ||||
|         colors[ImGuiCol_Separator]              = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); | ||||
|         colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.72f, 0.72f, 0.72f, 0.78f); | ||||
|         colors[ImGuiCol_SeparatorActive]        = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); | ||||
|         colors[ImGuiCol_ResizeGrip]             = ImVec4(0.91f, 0.91f, 0.91f, 0.25f); | ||||
|         colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.81f, 0.81f, 0.81f, 0.67f); | ||||
|         colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.46f, 0.46f, 0.46f, 0.95f); | ||||
|         colors[ImGuiCol_PlotLines]              = ImVec4(0.4f, 0.9f, 1.0f, 1.00f); | ||||
|         colors[ImGuiCol_PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); | ||||
|         colors[ImGuiCol_PlotHistogram]          = ImVec4(0.73f, 0.60f, 0.15f, 1.00f); | ||||
|         colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); | ||||
|         colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.87f, 0.87f, 0.87f, 0.35f); | ||||
|         colors[ImGuiCol_ModalWindowDarkening]   = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); | ||||
|         colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); | ||||
|         colors[ImGuiCol_NavHighlight]           = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); | ||||
|         colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     void beginDisabled() { | ||||
|         ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); | ||||
|         ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.44f, 0.44f, 0.44f, 0.15f)); | ||||
|         ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.20f, 0.21f, 0.22f, 0.30f)); | ||||
|         ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 0.65f)); | ||||
|     } | ||||
|  | ||||
|     void endDisabled() { | ||||
|         ImGui::PopItemFlag(); | ||||
|         ImGui::PopStyleColor(3); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								core/src/gui/style.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								core/src/gui/style.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| #pragma once | ||||
| #include <imgui.h> | ||||
| #include <string> | ||||
|  | ||||
| namespace style { | ||||
|     extern ImFont* baseFont; | ||||
|     extern ImFont* bigFont; | ||||
|     extern ImFont* hugeFont; | ||||
|  | ||||
|     bool setDefaultStyle(std::string resDir); | ||||
|     bool setDarkStyle(std::string resDir); | ||||
|     void beginDisabled(); | ||||
|     void endDisabled(); | ||||
|     void testtt(); | ||||
| } | ||||
							
								
								
									
										114
									
								
								core/src/gui/widgets/bandplan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								core/src/gui/widgets/bandplan.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| #include <gui/widgets/bandplan.h> | ||||
| #include <fstream> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <filesystem> | ||||
| #include <sstream> | ||||
| #include <iomanip> | ||||
|  | ||||
| namespace bandplan { | ||||
|     std::map<std::string, BandPlan_t> bandplans; | ||||
|     std::vector<std::string> bandplanNames; | ||||
|     std::string bandplanNameTxt; | ||||
|     std::map<std::string, BandPlanColor_t> colorTable; | ||||
|  | ||||
|     void generateTxt() { | ||||
|         bandplanNameTxt = ""; | ||||
|         for (int i = 0; i < bandplanNames.size(); i++) { | ||||
|             bandplanNameTxt += bandplanNames[i]; | ||||
|             bandplanNameTxt += '\0'; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void to_json(json& j, const Band_t& b) { | ||||
|         j = json{ | ||||
|             {"name", b.name}, | ||||
|             {"type", b.type}, | ||||
|             {"start", b.start}, | ||||
|             {"end", b.end}, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     void from_json(const json& j, Band_t& b) { | ||||
|         j.at("name").get_to(b.name); | ||||
|         j.at("type").get_to(b.type); | ||||
|         j.at("start").get_to(b.start); | ||||
|         j.at("end").get_to(b.end); | ||||
|     } | ||||
|  | ||||
|     void to_json(json& j, const BandPlan_t& b) { | ||||
|         j = json{ | ||||
|             {"name", b.name}, | ||||
|             {"country_name", b.countryName}, | ||||
|             {"country_code", b.countryCode}, | ||||
|             {"author_name", b.authorName}, | ||||
|             {"author_url", b.authorURL}, | ||||
|             {"bands", b.bands} | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     void from_json(const json& j, BandPlan_t& b) { | ||||
|         j.at("name").get_to(b.name); | ||||
|         j.at("country_name").get_to(b.countryName); | ||||
|         j.at("country_code").get_to(b.countryCode); | ||||
|         j.at("author_name").get_to(b.authorName); | ||||
|         j.at("author_url").get_to(b.authorURL); | ||||
|         j.at("bands").get_to(b.bands); | ||||
|     } | ||||
|  | ||||
|     void to_json(json& j, const BandPlanColor_t& ct) { | ||||
|         spdlog::error("ImGui color to JSON not implemented!!!"); | ||||
|     } | ||||
|      | ||||
|     void from_json(const json& j, BandPlanColor_t& ct) { | ||||
|         std::string col = j.get<std::string>(); | ||||
|         if (col[0] != '#' || !std::all_of(col.begin() + 1, col.end(), ::isxdigit)) { | ||||
|             return; | ||||
|         } | ||||
|         uint8_t r, g, b, a; | ||||
|         r = std::stoi(col.substr(1, 2), NULL, 16); | ||||
|         g = std::stoi(col.substr(3, 2), NULL, 16); | ||||
|         b = std::stoi(col.substr(5, 2), NULL, 16); | ||||
|         a = std::stoi(col.substr(7, 2), NULL, 16); | ||||
|         ct.colorValue = IM_COL32(r, g, b, a); | ||||
|         ct.transColorValue = IM_COL32(r, g, b, 100); | ||||
|     } | ||||
|  | ||||
|     void loadBandPlan(std::string path) { | ||||
|         std::ifstream file(path.c_str()); | ||||
|         json data; | ||||
|         file >> data; | ||||
|         file.close(); | ||||
|  | ||||
|         BandPlan_t plan = data.get<BandPlan_t>(); | ||||
|         if (bandplans.find(plan.name) != bandplans.end()) { | ||||
|             spdlog::error("Duplicate band plan name ({0}), not loading.", plan.name); | ||||
|             return; | ||||
|         } | ||||
|         bandplans[plan.name] = plan; | ||||
|         bandplanNames.push_back(plan.name); | ||||
|         generateTxt(); | ||||
|     } | ||||
|  | ||||
|     void loadFromDir(std::string path) { | ||||
|         if (!std::filesystem::exists(path)) { | ||||
|             spdlog::error("Band Plan directory does not exist"); | ||||
|             return; | ||||
|         } | ||||
|         if (!std::filesystem::is_directory(path)) { | ||||
|             spdlog::error("Band Plan directory isn't a directory..."); | ||||
|             return; | ||||
|         } | ||||
|         bandplans.clear(); | ||||
|         for (const auto & file : std::filesystem::directory_iterator(path)) { | ||||
|             std::string path = file.path().generic_string(); | ||||
|             if (file.path().extension().generic_string() != ".json") { | ||||
|                 continue; | ||||
|             } | ||||
|             loadBandPlan(path); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void loadColorTable(json table) { | ||||
|         colorTable = table.get<std::map<std::string, BandPlanColor_t>>(); | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										47
									
								
								core/src/gui/widgets/bandplan.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								core/src/gui/widgets/bandplan.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #pragma once | ||||
| #include <json.hpp> | ||||
| #include <imgui/imgui.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
| using nlohmann::json; | ||||
|  | ||||
| namespace bandplan { | ||||
|     struct Band_t { | ||||
|         std::string name; | ||||
|         std::string type; | ||||
|         double start; | ||||
|         double end; | ||||
|     }; | ||||
|  | ||||
|     void to_json(json& j, const Band_t& b); | ||||
|     void from_json(const json& j, Band_t& b); | ||||
|  | ||||
|     struct BandPlan_t { | ||||
|         std::string name; | ||||
|         std::string countryName; | ||||
|         std::string countryCode; | ||||
|         std::string authorName; | ||||
|         std::string authorURL; | ||||
|         std::vector<Band_t> bands; | ||||
|     }; | ||||
|  | ||||
|     void to_json(json& j, const BandPlan_t& b); | ||||
|     void from_json(const json& j, BandPlan_t& b); | ||||
|  | ||||
|     struct BandPlanColor_t { | ||||
|         uint32_t colorValue; | ||||
|         uint32_t transColorValue; | ||||
|     }; | ||||
|  | ||||
|     void to_json(json& j, const BandPlanColor_t& ct); | ||||
|     void from_json(const json& j, BandPlanColor_t& ct); | ||||
|      | ||||
|     void loadBandPlan(std::string path); | ||||
|     void loadFromDir(std::string path); | ||||
|     void loadColorTable(json table); | ||||
|  | ||||
|     extern std::map<std::string, BandPlan_t> bandplans; | ||||
|     extern std::vector<std::string> bandplanNames; | ||||
|     extern std::string bandplanNameTxt; | ||||
|     extern std::map<std::string, BandPlanColor_t> colorTable; | ||||
| }; | ||||
| @@ -1,4 +1,11 @@ | ||||
| #include <frequency_select.h> | ||||
| #include <gui/widgets/frequency_select.h> | ||||
| #include <config.h> | ||||
| #include <gui/style.h> | ||||
| 
 | ||||
| #ifndef IMGUI_DEFINE_MATH_OPERATORS | ||||
| #define IMGUI_DEFINE_MATH_OPERATORS | ||||
| #endif | ||||
| #include <imgui/imgui_internal.h> | ||||
| 
 | ||||
| bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) { | ||||
|     return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y; | ||||
| @@ -9,24 +16,27 @@ FrequencySelect::FrequencySelect() { | ||||
| } | ||||
| 
 | ||||
| void FrequencySelect::init() { | ||||
|     font = ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 42.0f); | ||||
|     for (int i = 0; i < 12; i++) { | ||||
|         digits[i] = 0; | ||||
|          | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FrequencySelect::onPosChange() { | ||||
|     int digitHeight = ImGui::CalcTextSize("0").y; | ||||
|     ImVec2 digitSz = ImGui::CalcTextSize("0"); | ||||
|     ImVec2 commaSz = ImGui::CalcTextSize("."); | ||||
|     int digitHeight = digitSz.y; | ||||
|     int digitWidth = digitSz.x; | ||||
|     int commaOffset = 0; | ||||
|     for (int i = 0; i < 12; i++) { | ||||
|         digitTopMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y); | ||||
|         digitBottomMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y + (digitHeight / 2)); | ||||
|         digitTopMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y); | ||||
|         digitBottomMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y + (digitHeight / 2)); | ||||
| 
 | ||||
|         digitTopMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + (digitHeight / 2)); | ||||
|         digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + digitHeight); | ||||
|         digitTopMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + (digitHeight / 2)); | ||||
|         digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + digitHeight); | ||||
| 
 | ||||
|         if ((i + 1) % 3 == 0 && i < 11) { | ||||
|             commaOffset += 12; | ||||
|             commaOffset += commaSz.x; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -67,13 +77,14 @@ void FrequencySelect::draw() { | ||||
|     window = ImGui::GetCurrentWindow(); | ||||
|     widgetPos = ImGui::GetWindowContentRegionMin(); | ||||
|     widgetEndPos = ImGui::GetWindowContentRegionMax(); | ||||
|     widgetPos.x += window->Pos.x + 255; | ||||
|     ImVec2 cursorPos = ImGui::GetCursorPos(); | ||||
|     widgetPos.x += window->Pos.x + cursorPos.x; | ||||
|     widgetPos.y += window->Pos.y - 3; | ||||
|     widgetEndPos.x += window->Pos.x + 255; | ||||
|     widgetEndPos.x += window->Pos.x + cursorPos.x; | ||||
|     widgetEndPos.y += window->Pos.y - 3; | ||||
|     widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y); | ||||
| 
 | ||||
|     ImGui::PushFont(font); | ||||
|     ImGui::PushFont(style::bigFont); | ||||
| 
 | ||||
|     if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) { | ||||
|         lastWidgetPos = widgetPos; | ||||
| @@ -84,19 +95,29 @@ void FrequencySelect::draw() { | ||||
|         onResize(); | ||||
|     } | ||||
| 
 | ||||
|     ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f); | ||||
|     ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); | ||||
| 
 | ||||
|     ImVec2 digitSz = ImGui::CalcTextSize("0"); | ||||
|     ImVec2 commaSz = ImGui::CalcTextSize("."); | ||||
|     int digitHeight = digitSz.y; | ||||
|     int digitWidth = digitSz.x; | ||||
|     int commaOffset = 0; | ||||
|     bool zeros = true; | ||||
|      | ||||
|     ImGui::ItemSize(ImRect(digitTopMins[0], ImVec2(digitBottomMaxs[11].x + 15, digitBottomMaxs[11].y))); | ||||
| 
 | ||||
|     for (int i = 0; i < 12; i++) { | ||||
|         if (digits[i] != 0) { | ||||
|             zeros = false; | ||||
|         } | ||||
|         sprintf(buf, "%d", digits[i]); | ||||
|         window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y),  | ||||
|                                 zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), buf); | ||||
|         window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y),  | ||||
|                                 zeros ? disabledColor : textColor, buf); | ||||
|         if ((i + 1) % 3 == 0 && i < 11) { | ||||
|             commaOffset += 12; | ||||
|             window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset + 10, widgetPos.y),  | ||||
|                                     zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), "."); | ||||
|             commaOffset += commaSz.x; | ||||
|             window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + 11, widgetPos.y),  | ||||
|                                     zeros ? disabledColor : textColor, "."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @@ -138,19 +159,22 @@ void FrequencySelect::draw() { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     long freq = 0; | ||||
|     uint64_t freq = 0; | ||||
|     for (int i = 0; i < 12; i++) { | ||||
|         freq += digits[i] * pow(10, 11 - i); | ||||
|     } | ||||
|     frequency = freq; | ||||
| 
 | ||||
|     ImGui::PopFont(); | ||||
|     ImGui::NewLine(); | ||||
| 
 | ||||
|     ImGui::SetCursorPosX(digitBottomMaxs[11].x + 17); | ||||
| 
 | ||||
|     //ImGui::NewLine();
 | ||||
| } | ||||
| 
 | ||||
| void FrequencySelect::setFrequency(long freq) { | ||||
| void FrequencySelect::setFrequency(uint64_t freq) { | ||||
|     int i = 11; | ||||
|     for (long f = freq; i >= 0; i--) { | ||||
|     for (uint64_t f = freq; i >= 0; i--) { | ||||
|         digits[i] = f % 10; | ||||
|         f -= digits[i]; | ||||
|         f /= 10; | ||||
| @@ -1,15 +1,16 @@ | ||||
| #pragma once | ||||
| #include <imgui.h> | ||||
| #include <imgui_internal.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| class FrequencySelect { | ||||
| public: | ||||
|     FrequencySelect(); | ||||
|     void init(); | ||||
|     void draw(); | ||||
|     void setFrequency(long freq); | ||||
|     void setFrequency(uint64_t freq); | ||||
| 
 | ||||
|     long frequency; | ||||
|     uint64_t frequency; | ||||
|     bool frequencyChanged = false; | ||||
| 
 | ||||
| private: | ||||
| @@ -26,7 +27,6 @@ private: | ||||
|     ImVec2 lastWidgetSize; | ||||
| 
 | ||||
|     ImGuiWindow* window; | ||||
|     ImFont* font; | ||||
| 
 | ||||
|     int digits[12]; | ||||
|     ImVec2 digitBottomMins[12]; | ||||
							
								
								
									
										77
									
								
								core/src/gui/widgets/menu.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								core/src/gui/widgets/menu.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| #include <gui/widgets/menu.h> | ||||
| #include <imgui/imgui.h> | ||||
| #include <imgui/imgui_internal.h> | ||||
|  | ||||
| Menu::Menu() { | ||||
|  | ||||
| } | ||||
|  | ||||
| void Menu::registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx, ModuleManager::Instance* inst) { | ||||
|     MenuItem_t item; | ||||
|     item.drawHandler = drawHandler; | ||||
|     item.ctx = ctx; | ||||
|     item.inst = inst; | ||||
|     items[name] = item; | ||||
|     if (!isInOrderList(name)) { | ||||
|         order.push_back(name); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Menu::removeEntry(std::string name) { | ||||
|     items.erase(name); | ||||
| } | ||||
|  | ||||
| void Menu::draw() { | ||||
|     MenuItem_t item; | ||||
|     float menuWidth = ImGui::GetContentRegionAvailWidth(); | ||||
|     ImGuiWindow* window = ImGui::GetCurrentWindow(); | ||||
|     for (std::string name : order) { | ||||
|         if (items.find(name) == items.end()) { | ||||
|             continue; | ||||
|         } | ||||
|         item = items[name]; | ||||
|  | ||||
|         ImRect orginalRect = window->WorkRect; | ||||
|         if (item.inst != NULL) { | ||||
|              | ||||
|             window->WorkRect = ImRect(orginalRect.Min, ImVec2(orginalRect.Max.x - ImGui::GetTextLineHeight() - 6, orginalRect.Max.y)); | ||||
|         } | ||||
|  | ||||
|         if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { | ||||
|             if (item.inst != NULL) { | ||||
|                 window->WorkRect = orginalRect; | ||||
|                 ImVec2 pos = ImGui::GetCursorPos(); | ||||
|                 ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); | ||||
|                 ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); | ||||
|                 bool enabled = item.inst->isEnabled(); | ||||
|                 if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) { | ||||
|                     enabled ? item.inst->enable() : item.inst->disable(); | ||||
|                 } | ||||
|                 ImGui::SetCursorPos(pos); | ||||
|             } | ||||
|  | ||||
|             item.drawHandler(item.ctx); | ||||
|             ImGui::Spacing(); | ||||
|         } | ||||
|         else if (item.inst != NULL) { | ||||
|             window->WorkRect = orginalRect; | ||||
|             ImVec2 pos = ImGui::GetCursorPos(); | ||||
|             ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); | ||||
|             ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); | ||||
|             bool enabled = item.inst->isEnabled(); | ||||
|             if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) { | ||||
|                 enabled ? item.inst->enable() : item.inst->disable(); | ||||
|             } | ||||
|             ImGui::SetCursorPos(pos); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool Menu::isInOrderList(std::string name) { | ||||
|     for (std::string _name : order) { | ||||
|         if (_name == name) { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
							
								
								
									
										27
									
								
								core/src/gui/widgets/menu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								core/src/gui/widgets/menu.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <module.h> | ||||
|  | ||||
| class Menu { | ||||
| public: | ||||
|     Menu(); | ||||
|  | ||||
|     struct MenuItem_t { | ||||
|         void (*drawHandler)(void* ctx); | ||||
|         void* ctx; | ||||
|         ModuleManager::Instance* inst; | ||||
|     }; | ||||
|  | ||||
|     void registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx = NULL, ModuleManager::Instance* inst = NULL); | ||||
|     void removeEntry(std::string name); | ||||
|     void draw(); | ||||
|  | ||||
|     std::vector<std::string> order; | ||||
|  | ||||
| private: | ||||
|     bool isInOrderList(std::string name); | ||||
|  | ||||
|     std::map<std::string, MenuItem_t> items; | ||||
| }; | ||||
							
								
								
									
										23
									
								
								core/src/gui/widgets/stepped_slider.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								core/src/gui/widgets/stepped_slider.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| #include <gui/widgets/stepped_slider.h> | ||||
| #include <imgui.h> | ||||
| #include <imgui_internal.h> | ||||
|  | ||||
| namespace ImGui { | ||||
|     bool SliderFloatWithSteps(const char* label, float* v, float v_min, float v_max, float v_step, const char* display_format) { | ||||
|         if (!display_format) { | ||||
|             display_format = "%.3f"; | ||||
|         } | ||||
|  | ||||
|         char text_buf[64] = {}; | ||||
|         ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), display_format, *v); | ||||
|  | ||||
|         // Map from [v_min,v_max] to [0,N] | ||||
|         const int countValues = int((v_max-v_min)/v_step); | ||||
|         int v_i = int((*v - v_min)/v_step); | ||||
|         const bool value_changed = ImGui::SliderInt(label, &v_i, 0, countValues, text_buf); | ||||
|  | ||||
|         // Remap from [0,N] to [v_min,v_max] | ||||
|         *v = v_min + float(v_i) * v_step; | ||||
|         return value_changed; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										5
									
								
								core/src/gui/widgets/stepped_slider.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								core/src/gui/widgets/stepped_slider.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace ImGui { | ||||
|     bool SliderFloatWithSteps(const char* label, float* v, float v_min, float v_max, float v_step, const char* display_format = "%.3f"); | ||||
| } | ||||
							
								
								
									
										53
									
								
								core/src/gui/widgets/volume_meter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								core/src/gui/widgets/volume_meter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| #include <gui/widgets/volume_meter.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| #ifndef IMGUI_DEFINE_MATH_OPERATORS | ||||
| #define IMGUI_DEFINE_MATH_OPERATORS | ||||
| #endif | ||||
| #include <imgui/imgui_internal.h> | ||||
|  | ||||
| namespace ImGui { | ||||
|     void VolumeMeter(float avg, float peak, float val_min, float val_max, const ImVec2& size_arg) { | ||||
|         ImGuiWindow* window = GetCurrentWindow(); | ||||
|         ImGuiStyle& style = GImGui->Style; | ||||
|  | ||||
|         avg = std::clamp<float>(avg, val_min, val_max); | ||||
|         peak = std::clamp<float>(peak, val_min, val_max); | ||||
|  | ||||
|         float pad = style.FramePadding.y; | ||||
|  | ||||
|         ImVec2 min = window->DC.CursorPos; | ||||
|         ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (GImGui->FontSize / 2) + style.FramePadding.y); | ||||
|         ImRect bb(min, min + size); | ||||
|  | ||||
|         float lineHeight = size.y; | ||||
|  | ||||
|         ItemSize(size, style.FramePadding.y); | ||||
|         if (!ItemAdd(bb, 0)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         float zeroDb = roundf(((-val_min) / (val_max - val_min)) * size.x); | ||||
|  | ||||
|         window->DrawList->AddRectFilled(min, min + ImVec2(zeroDb, lineHeight), IM_COL32( 0, 255, 0, 127 )); | ||||
|         window->DrawList->AddRectFilled(min + ImVec2(zeroDb, 0), min + ImVec2(size.x, lineHeight), IM_COL32( 255, 0, 0, 127 )); | ||||
|  | ||||
|         float end = roundf(((avg - val_min) / (val_max - val_min)) * size.x); | ||||
|         float endP = roundf(((peak - val_min) / (val_max - val_min)) * size.x); | ||||
|  | ||||
|         if (avg <= 0) { | ||||
|             window->DrawList->AddRectFilled(min, min + ImVec2(end, lineHeight), IM_COL32( 0, 255, 0, 255 )); | ||||
|         } | ||||
|         else { | ||||
|             window->DrawList->AddRectFilled(min, min + ImVec2(zeroDb, lineHeight), IM_COL32( 0, 255, 0, 255 )); | ||||
|             window->DrawList->AddRectFilled(min + ImVec2(zeroDb, 0), min + ImVec2(end, lineHeight), IM_COL32( 255, 0, 0, 255 )); | ||||
|         } | ||||
|  | ||||
|         if (peak <= 0) { | ||||
|             window->DrawList->AddLine(min + ImVec2(endP, -1), min + ImVec2(endP, lineHeight - 1), IM_COL32( 127, 255, 127, 255 )); | ||||
|         } | ||||
|         else { | ||||
|             window->DrawList->AddLine(min + ImVec2(endP, -1), min + ImVec2(endP, lineHeight - 1), IM_COL32( 255, 127, 127, 255 )); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										6
									
								
								core/src/gui/widgets/volume_meter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/widgets/volume_meter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
| #include <imgui/imgui.h> | ||||
|  | ||||
| namespace ImGui { | ||||
|     void VolumeMeter(float avg, float peak, float val_min, float val_max, const ImVec2& size_arg = ImVec2(0, 0)); | ||||
| } | ||||
							
								
								
									
										919
									
								
								core/src/gui/widgets/waterfall.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										919
									
								
								core/src/gui/widgets/waterfall.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,919 @@ | ||||
| #include <gui/widgets/waterfall.h> | ||||
| #include <imgui.h> | ||||
| #include <imgui_internal.h> | ||||
| #include <GL/glew.h> | ||||
| #include <imutils.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| float DEFAULT_COLOR_MAP[][3] = { | ||||
|     {0x00, 0x00, 0x20}, | ||||
|     {0x00, 0x00, 0x30}, | ||||
|     {0x00, 0x00, 0x50}, | ||||
|     {0x00, 0x00, 0x91}, | ||||
|     {0x1E, 0x90, 0xFF}, | ||||
|     {0xFF, 0xFF, 0xFF}, | ||||
|     {0xFF, 0xFF, 0x00}, | ||||
|     {0xFE, 0x6D, 0x16}, | ||||
|     {0xFF, 0x00, 0x00}, | ||||
|     {0xC6, 0x00, 0x00}, | ||||
|     {0x9F, 0x00, 0x00}, | ||||
|     {0x75, 0x00, 0x00}, | ||||
|     {0x4A, 0x00, 0x00} | ||||
| }; | ||||
|  | ||||
| void doZoom(int offset, int width, int outWidth, float* data, float* out) { | ||||
|     // NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX | ||||
|     if (offset < 0) { | ||||
|         offset = 0; | ||||
|     } | ||||
|     if (width > 65535) { | ||||
|         width = 65535; | ||||
|     } | ||||
|  | ||||
|     float factor = (float)width / (float)outWidth; | ||||
|     for (int i = 0; i < outWidth; i++) { | ||||
|         out[i] = data[(int)(offset + ((float)i * factor))]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| // TODO: Fix this hacky BS | ||||
|  | ||||
| double freq_ranges[] = { | ||||
|         1.0, 2.0, 2.5, 5.0, | ||||
|         10.0, 20.0, 25.0, 50.0, | ||||
|         100.0, 200.0, 250.0, 500.0, | ||||
|         1000.0, 2000.0, 2500.0, 5000.0, | ||||
|         10000.0, 20000.0, 25000.0, 50000.0, | ||||
|         100000.0, 200000.0, 250000.0, 500000.0, | ||||
|         1000000.0, 2000000.0, 2500000.0, 5000000.0, | ||||
|         10000000.0, 20000000.0, 25000000.0, 50000000.0 | ||||
| }; | ||||
|  | ||||
| double findBestRange(double bandwidth, int maxSteps) { | ||||
|     for (int i = 0; i < 32; i++) { | ||||
|         if (bandwidth / freq_ranges[i] < (double)maxSteps) { | ||||
|             return freq_ranges[i]; | ||||
|         } | ||||
|     } | ||||
|     return 50000000.0; | ||||
| } | ||||
|  | ||||
| void printAndScale(double freq, char* buf) { | ||||
|     if (freq < 1000) { | ||||
|         sprintf(buf, "%.3lf", freq); | ||||
|     } | ||||
|     else if (freq < 1000000) { | ||||
|         sprintf(buf, "%.3lfK", freq / 1000.0); | ||||
|     } | ||||
|     else if (freq < 1000000000) { | ||||
|         sprintf(buf, "%.3lfM", freq / 1000000.0); | ||||
|     } | ||||
|     else if (freq < 1000000000000) { | ||||
|         sprintf(buf, "%.3lfG", freq / 1000000000.0); | ||||
|     } | ||||
|     for (int i = strlen(buf) - 2; i >= 0; i--) { | ||||
|         if (buf[i] != '0') { | ||||
|             if (buf[i] == '.') { | ||||
|                 i--; | ||||
|             } | ||||
|             char scale = buf[strlen(buf) - 1]; | ||||
|             buf[i + 1] = scale; | ||||
|             buf[i + 2] = 0; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| namespace ImGui { | ||||
|     WaterFall::WaterFall() { | ||||
|         fftMin = -70.0; | ||||
|         fftMax = 0.0; | ||||
|         waterfallMin = -70.0; | ||||
|         waterfallMax = 0.0; | ||||
|         FFTAreaHeight = 300; | ||||
|         newFFTAreaHeight = FFTAreaHeight; | ||||
|         fftHeight = FFTAreaHeight - 50; | ||||
|         dataWidth = 600; | ||||
|         lastWidgetPos.x = 0; | ||||
|         lastWidgetPos.y = 0; | ||||
|         lastWidgetSize.x = 0; | ||||
|         lastWidgetSize.y = 0; | ||||
|         latestFFT = new float[1]; | ||||
|         waterfallFb = new uint32_t[1]; | ||||
|  | ||||
|         viewBandwidth = 1.0; | ||||
|         wholeBandwidth = 1.0; | ||||
|  | ||||
|         updatePallette(DEFAULT_COLOR_MAP, 13); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::init() { | ||||
|         glGenTextures(1, &textureId); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::drawFFT() { | ||||
|         // Calculate scaling factor | ||||
|         float startLine = floorf(fftMax / vRange) * vRange; | ||||
|         float vertRange = fftMax - fftMin; | ||||
|         float scaleFactor = fftHeight / vertRange; | ||||
|         char buf[100]; | ||||
|  | ||||
|         ImU32 trace = ImGui::GetColorU32(ImGuiCol_PlotLines); | ||||
|         ImU32 shadow = ImGui::GetColorU32(ImGuiCol_PlotLines, 0.2); | ||||
|  | ||||
|         // Vertical scale | ||||
|         for (float line = startLine; line > fftMin; line -= vRange) { | ||||
|             float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor); | ||||
|             window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)),  | ||||
|                                     ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)), | ||||
|                                     IM_COL32(50, 50, 50, 255), 1.0); | ||||
|             sprintf(buf, "%d", (int)line); | ||||
|             ImVec2 txtSz = ImGui::CalcTextSize(buf); | ||||
|             window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2.0))), IM_COL32( 255, 255, 255, 255 ), buf); | ||||
|         } | ||||
|  | ||||
|         // Horizontal scale | ||||
|         double startFreq = ceilf(lowerFreq / range) * range; | ||||
|         double horizScale = (double)dataWidth / viewBandwidth; | ||||
|         for (double freq = startFreq; freq < upperFreq; freq += range) { | ||||
|             double xPos = widgetPos.x + 50 + ((freq - lowerFreq) * horizScale); | ||||
|             window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + 10),  | ||||
|                                     ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10), | ||||
|                                     IM_COL32(50, 50, 50, 255), 1.0); | ||||
|             window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),  | ||||
|                                     ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17), | ||||
|                                     IM_COL32(255, 255, 255, 255), 1.0); | ||||
|             printAndScale(freq, buf); | ||||
|             ImVec2 txtSz = ImGui::CalcTextSize(buf); | ||||
|             window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), widgetPos.y + fftHeight + 10 + txtSz.y), IM_COL32( 255, 255, 255, 255 ), buf); | ||||
|         } | ||||
|  | ||||
|         // Data | ||||
|         if (latestFFT != NULL && fftLines != 0) { | ||||
|              for (int i = 1; i < dataWidth; i++) { | ||||
|                 double aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor); | ||||
|                 double bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor); | ||||
|                 aPos = std::clamp<double>(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10); | ||||
|                 bPos = std::clamp<double>(bPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10); | ||||
|                 window->DrawList->AddLine(ImVec2(widgetPos.x + 49 + i, roundf(aPos)),  | ||||
|                                         ImVec2(widgetPos.x + 50 + i, roundf(bPos)), trace, 1.0); | ||||
|                 window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)),  | ||||
|                                         ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10), shadow, 1.0); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // X Axis | ||||
|         window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),  | ||||
|                                     ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10), | ||||
|                                     IM_COL32(255, 255, 255, 255), 1.0); | ||||
|         // Y Axis | ||||
|         window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9),  | ||||
|                                     ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9), | ||||
|                                     IM_COL32(255, 255, 255, 255), 1.0); | ||||
|  | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void WaterFall::drawWaterfall() { | ||||
|         if (waterfallUpdate) { | ||||
|             waterfallUpdate = false; | ||||
|             updateWaterfallTexture(); | ||||
|         } | ||||
|         window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 51), | ||||
|                                 ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight)); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::drawVFOs() { | ||||
|         for (auto const& [name, vfo] : vfos) { | ||||
|             if (vfo->redrawRequired) { | ||||
|                 vfo->redrawRequired = false; | ||||
|                 vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight); | ||||
|             } | ||||
|             vfo->draw(window, name == selectedVFO); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WaterFall::selectFirstVFO() { | ||||
|         bool available = false; | ||||
|         for (auto const& [name, vfo] : vfos) { | ||||
|             available = true; | ||||
|             selectedVFO = name; | ||||
|             return; | ||||
|         } | ||||
|         if (!available) { | ||||
|             selectedVFO = ""; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WaterFall::processInputs() { | ||||
|         WaterfallVFO* vfo = NULL; | ||||
|         if (selectedVFO != "") { | ||||
|             vfo = vfos[selectedVFO]; | ||||
|         } | ||||
|         ImVec2 mousePos = ImGui::GetMousePos(); | ||||
|         ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); | ||||
|         ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y); | ||||
|  | ||||
|         bool mouseHovered, mouseHeld; | ||||
|         bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, fftAreaMax), GetID("WaterfallID"), &mouseHovered, &mouseHeld,  | ||||
|                                                 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick); | ||||
|          | ||||
|         bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsWindowFocused(); | ||||
|         bool mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax); | ||||
|         bool mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax); | ||||
|          | ||||
|  | ||||
|         // If mouse was clicked on a VFO, select VFO and return | ||||
|         // If mouse was clicked but not on a VFO, move selected VFO to position | ||||
|         if (mouseClicked) { | ||||
|             for (auto const& [name, _vfo] : vfos) { | ||||
|                 if (name == selectedVFO) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (IS_IN_AREA(mousePos, _vfo->rectMin, _vfo->rectMax)) { | ||||
|                     selectedVFO = name; | ||||
|                     selectedVFOChanged = true; | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             if (vfo != NULL) { | ||||
|                 int refCenter = mousePos.x - (widgetPos.x + 50); | ||||
|                 if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) { | ||||
|                     double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset; | ||||
|                     off += centerFreq; | ||||
|                     off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq; | ||||
|                     vfo->setOffset(off); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Draging VFO | ||||
|         if (draging && mouseInFFT) { | ||||
|             int refCenter = mousePos.x - (widgetPos.x + 50); | ||||
|             if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y) && vfo != NULL) { | ||||
|                 double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset; | ||||
|                 off += centerFreq; | ||||
|                 off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq; | ||||
|                 vfo->setOffset(off); | ||||
|             } | ||||
|         }  | ||||
|  | ||||
|         // Draging frequency scale | ||||
|         if (draging && mouseInFreq) { | ||||
|             double deltax = drag.x - lastDrag; | ||||
|             lastDrag = drag.x; | ||||
|             double viewDelta = deltax * (viewBandwidth / (double)dataWidth); | ||||
|  | ||||
|             viewOffset -= viewDelta; | ||||
|  | ||||
|             if (viewOffset + (viewBandwidth / 2.0) > wholeBandwidth / 2.0) { | ||||
|                 double freqOffset = (viewOffset + (viewBandwidth / 2.0)) - (wholeBandwidth / 2.0); | ||||
|                 viewOffset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0); | ||||
|                 centerFreq += freqOffset; | ||||
|                 centerFreqMoved = true; | ||||
|             } | ||||
|             if (viewOffset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) { | ||||
|                 double freqOffset = (viewOffset - (viewBandwidth / 2.0)) + (wholeBandwidth / 2.0); | ||||
|                 viewOffset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0); | ||||
|                 centerFreq += freqOffset; | ||||
|                 centerFreqMoved = true; | ||||
|             } | ||||
|  | ||||
|             lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0); | ||||
|             upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0); | ||||
|  | ||||
|             if (viewBandwidth != wholeBandwidth) { | ||||
|                 updateAllVFOs(); | ||||
|                 updateWaterfallFb(); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             lastDrag = 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WaterFall::updateWaterfallFb() { | ||||
|         if (!waterfallVisible || rawFFTs == NULL) { | ||||
|             return; | ||||
|         } | ||||
|         double offsetRatio = viewOffset / (wholeBandwidth / 2.0); | ||||
|         int drawDataSize; | ||||
|         int drawDataStart; | ||||
|         // TODO: Maybe put on the stack for faster alloc? | ||||
|         float* tempData = new float[dataWidth]; | ||||
|         float pixel; | ||||
|         float dataRange = waterfallMax - waterfallMin; | ||||
|         int count = std::min<float>(waterfallHeight, fftLines); | ||||
|         if (rawFFTs != NULL) { | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize; | ||||
|                 drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2); | ||||
|                 doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData); | ||||
|                 for (int j = 0; j < dataWidth; j++) { | ||||
|                     pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange; | ||||
|                     waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         delete[] tempData; | ||||
|         waterfallUpdate = true; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::drawBandPlan() { | ||||
|         int count = bandplan->bands.size(); | ||||
|         double horizScale = (double)dataWidth / viewBandwidth; | ||||
|         double start, end, center, aPos, bPos, cPos, width; | ||||
|         ImVec2 txtSz; | ||||
|         bool startVis, endVis; | ||||
|         uint32_t color, colorTrans; | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             start = bandplan->bands[i].start; | ||||
|             end = bandplan->bands[i].end; | ||||
|             if (start < lowerFreq && end < lowerFreq) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (start > upperFreq && end > upperFreq) { | ||||
|                 continue; | ||||
|             } | ||||
|             startVis = (start > lowerFreq); | ||||
|             endVis = (end < upperFreq); | ||||
|             start = std::clamp<double>(start, lowerFreq, upperFreq); | ||||
|             end = std::clamp<double>(end, lowerFreq, upperFreq); | ||||
|             center = (start + end) / 2.0; | ||||
|             aPos = widgetPos.x + 50 + ((start - lowerFreq) * horizScale); | ||||
|             bPos = widgetPos.x + 50 + ((end - lowerFreq) * horizScale); | ||||
|             cPos = widgetPos.x + 50 + ((center - lowerFreq) * horizScale); | ||||
|             width = bPos - aPos; | ||||
|             txtSz = ImGui::CalcTextSize(bandplan->bands[i].name.c_str()); | ||||
|             float height = txtSz.y * 2.5f; | ||||
|             if (bandplan::colorTable.find(bandplan->bands[i].type.c_str()) != bandplan::colorTable.end()) { | ||||
|                 color = bandplan::colorTable[bandplan->bands[i].type].colorValue; | ||||
|                 colorTrans = bandplan::colorTable[bandplan->bands[i].type].transColorValue; | ||||
|             } | ||||
|             else { | ||||
|                 color = IM_COL32(255, 255, 255, 255); | ||||
|                 colorTrans = IM_COL32(255, 255, 255, 100); | ||||
|             } | ||||
|             if (aPos <= widgetPos.x + 50) { | ||||
|                 aPos = widgetPos.x + 51; | ||||
|             } | ||||
|             if (bPos <= widgetPos.x + 50) { | ||||
|                 bPos = widgetPos.x + 51; | ||||
|             } | ||||
|             if (width >= 1.0) { | ||||
|                 window->DrawList->AddRectFilled(ImVec2(roundf(aPos), widgetPos.y + fftHeight + 10 - height),  | ||||
|                                         ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10), colorTrans); | ||||
|                 if (startVis) { | ||||
|                     window->DrawList->AddLine(ImVec2(roundf(aPos), widgetPos.y + fftHeight + 10 - height - 1),  | ||||
|                                         ImVec2(roundf(aPos), widgetPos.y + fftHeight + 9), color); | ||||
|                 } | ||||
|                 if (endVis) { | ||||
|                     window->DrawList->AddLine(ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10 - height - 1),  | ||||
|                                         ImVec2(roundf(bPos), widgetPos.y + fftHeight + 9), color); | ||||
|                 } | ||||
|             } | ||||
|             if (txtSz.x <= width) { | ||||
|                 window->DrawList->AddText(ImVec2(cPos - (txtSz.x / 2.0), widgetPos.y + fftHeight + 10 - (height / 2.0f) - (txtSz.y / 2.0f)),  | ||||
|                                     IM_COL32(255, 255, 255, 255), bandplan->bands[i].name.c_str()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WaterFall::updateWaterfallTexture() { | ||||
|         glBindTexture(GL_TEXTURE_2D, textureId); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||
|         glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||||
|         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dataWidth, waterfallHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)waterfallFb); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::onPositionChange() { | ||||
|         // Nothing to see here... | ||||
|     } | ||||
|  | ||||
|     void WaterFall::onResize() { | ||||
|         // return if widget is too small | ||||
|         if (widgetSize.x < 100 || widgetSize.y < 100) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         int lastWaterfallHeight = waterfallHeight; | ||||
|  | ||||
|         if (waterfallVisible) { | ||||
|             FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - 50); | ||||
|             fftHeight = FFTAreaHeight - 50; | ||||
|             waterfallHeight = widgetSize.y - fftHeight - 52; | ||||
|         } | ||||
|         else { | ||||
|             fftHeight =  widgetSize.y - 50; | ||||
|         } | ||||
|         dataWidth = widgetSize.x - 60.0; | ||||
|  | ||||
|         if (waterfallVisible) { | ||||
|             // Raw FFT resize | ||||
|             fftLines = std::min<int>(fftLines, waterfallHeight) - 1; | ||||
|             if (rawFFTs != NULL) { | ||||
|                 if (currentFFTLine != 0) { | ||||
|                     float* tempWF = new float[currentFFTLine * rawFFTSize]; | ||||
|                     int moveCount = lastWaterfallHeight - currentFFTLine; | ||||
|                     memcpy(tempWF, rawFFTs, currentFFTLine * rawFFTSize * sizeof(float)); | ||||
|                     memmove(rawFFTs, &rawFFTs[currentFFTLine * rawFFTSize], moveCount * rawFFTSize * sizeof(float)); | ||||
|                     memcpy(&rawFFTs[moveCount * rawFFTSize], tempWF, currentFFTLine * rawFFTSize * sizeof(float)); | ||||
|                     delete[] tempWF; | ||||
|                 } | ||||
|                 currentFFTLine = 0; | ||||
|                 rawFFTs = (float*)realloc(rawFFTs, waterfallHeight * rawFFTSize * sizeof(float)); | ||||
|             } | ||||
|             else { | ||||
|                 rawFFTs = (float*)malloc(waterfallHeight * rawFFTSize * sizeof(float)); | ||||
|             } | ||||
|             // ============== | ||||
|         } | ||||
|  | ||||
|         if (latestFFT != NULL) { | ||||
|             delete[] latestFFT; | ||||
|         } | ||||
|         latestFFT = new float[dataWidth]; | ||||
|  | ||||
|         if (waterfallVisible) { | ||||
|             delete[] waterfallFb; | ||||
|             waterfallFb = new uint32_t[dataWidth * waterfallHeight]; | ||||
|             memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t)); | ||||
|         } | ||||
|         for (int i = 0; i < dataWidth; i++) { | ||||
|             latestFFT[i] = -1000.0; // Hide everything | ||||
|         } | ||||
|  | ||||
|         fftAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + 9); | ||||
|         fftAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10); | ||||
|         freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11); | ||||
|         freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50); | ||||
|  | ||||
|         maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10); | ||||
|         maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y); | ||||
|  | ||||
|         range = findBestRange(viewBandwidth, maxHSteps); | ||||
|         vRange = findBestRange(fftMax - fftMin, maxVSteps); | ||||
|         vRange = 10.0; | ||||
|  | ||||
|         updateWaterfallFb(); | ||||
|         updateAllVFOs(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::draw() { | ||||
|         buf_mtx.lock(); | ||||
|         window = GetCurrentWindow(); | ||||
|  | ||||
|         widgetPos = ImGui::GetWindowContentRegionMin(); | ||||
|         widgetEndPos = ImGui::GetWindowContentRegionMax(); | ||||
|         widgetPos.x += window->Pos.x; | ||||
|         widgetPos.y += window->Pos.y; | ||||
|         widgetEndPos.x += window->Pos.x - 4; // Padding | ||||
|         widgetEndPos.y += window->Pos.y; | ||||
|         widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y); | ||||
|  | ||||
|         if (selectedVFO == "" && vfos.size() > 0) { | ||||
|             selectFirstVFO(); | ||||
|         } | ||||
|  | ||||
|         if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) { | ||||
|             lastWidgetPos = widgetPos; | ||||
|             onPositionChange(); | ||||
|         } | ||||
|         if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) { | ||||
|             lastWidgetSize = widgetSize; | ||||
|             onResize(); | ||||
|         } | ||||
|  | ||||
|         window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 )); | ||||
|         window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 )); | ||||
|         window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0); | ||||
|  | ||||
|         processInputs(); | ||||
|          | ||||
|         drawFFT(); | ||||
|         if (waterfallVisible) { | ||||
|             drawWaterfall(); | ||||
|         } | ||||
|         drawVFOs(); | ||||
|         if (bandplan != NULL && bandplanVisible) { | ||||
|             drawBandPlan(); | ||||
|         } | ||||
|  | ||||
|         if (!waterfallVisible) { | ||||
|             buf_mtx.unlock(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Handle fft resize | ||||
|         ImVec2 winSize = ImGui::GetWindowSize(); | ||||
|         ImVec2 mousePos = ImGui::GetMousePos(); | ||||
|         mousePos.x -= widgetPos.x; | ||||
|         mousePos.y -= widgetPos.y; | ||||
|         bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left); | ||||
|         bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left); | ||||
|         if (draggingFW) { | ||||
|             newFFTAreaHeight = mousePos.y; | ||||
|             newFFTAreaHeight = std::clamp<float>(newFFTAreaHeight, 150, widgetSize.y - 50); | ||||
|             ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y),  | ||||
|                                                     ImGui::GetColorU32(ImGuiCol_SeparatorActive)); | ||||
|         } | ||||
|         if (mousePos.y >= newFFTAreaHeight - 2 && mousePos.y <= newFFTAreaHeight + 2 && mousePos.x > 0 && mousePos.x < widgetSize.x) { | ||||
|             ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); | ||||
|             if (click) { | ||||
|                 draggingFW = true; | ||||
|             } | ||||
|         } | ||||
|         if(!down && draggingFW) { | ||||
|             draggingFW = false; | ||||
|             FFTAreaHeight = newFFTAreaHeight; | ||||
|             onResize(); | ||||
|         } | ||||
|  | ||||
|         buf_mtx.unlock(); | ||||
|     } | ||||
|  | ||||
|     float* WaterFall::getFFTBuffer() { | ||||
|         if (rawFFTs == NULL) { return NULL; } | ||||
|         buf_mtx.lock(); | ||||
|         if (waterfallVisible) { | ||||
|             currentFFTLine--; | ||||
|             fftLines++; | ||||
|             currentFFTLine = ((currentFFTLine + waterfallHeight) % waterfallHeight); | ||||
|             fftLines = std::min<float>(fftLines, waterfallHeight); | ||||
|             return &rawFFTs[currentFFTLine * rawFFTSize]; | ||||
|         } | ||||
|         return rawFFTs; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::pushFFT() { | ||||
|         if (rawFFTs == NULL) { return; } | ||||
|         double offsetRatio = viewOffset / (wholeBandwidth / 2.0); | ||||
|         int drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize; | ||||
|         int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2); | ||||
|          | ||||
|          | ||||
|  | ||||
|         if (waterfallVisible) { | ||||
|             doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT); | ||||
|             memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t)); | ||||
|             float pixel; | ||||
|             float dataRange = waterfallMax - waterfallMin; | ||||
|             for (int j = 0; j < dataWidth; j++) { | ||||
|                 pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange; | ||||
|                 int id = (int)(pixel * (WATERFALL_RESOLUTION - 1)); | ||||
|                 waterfallFb[j] = waterfallPallet[id]; | ||||
|             } | ||||
|             waterfallUpdate = true; | ||||
|         } | ||||
|         else { | ||||
|             doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT); | ||||
|             fftLines = 1; | ||||
|         } | ||||
|          | ||||
|         buf_mtx.unlock(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::updatePallette(float colors[][3], int colorCount) { | ||||
|         std::lock_guard<std::mutex> lck(buf_mtx); | ||||
|         for (int i = 0; i < WATERFALL_RESOLUTION; i++) { | ||||
|             int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount); | ||||
|             int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount); | ||||
|             lowerId = std::clamp<int>(lowerId, 0, colorCount - 1); | ||||
|             upperId = std::clamp<int>(upperId, 0, colorCount - 1); | ||||
|             float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId; | ||||
|             float r = (colors[lowerId][0] * (1.0 - ratio)) + (colors[upperId][0] * (ratio)); | ||||
|             float g = (colors[lowerId][1] * (1.0 - ratio)) + (colors[upperId][1] * (ratio)); | ||||
|             float b = (colors[lowerId][2] * (1.0 - ratio)) + (colors[upperId][2] * (ratio)); | ||||
|             waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r; | ||||
|         } | ||||
|         updateWaterfallFb(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::updatePalletteFromArray(float* colors, int colorCount) { | ||||
|         std::lock_guard<std::mutex> lck(buf_mtx); | ||||
|         for (int i = 0; i < WATERFALL_RESOLUTION; i++) { | ||||
|             int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount); | ||||
|             int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount); | ||||
|             lowerId = std::clamp<int>(lowerId, 0, colorCount - 1); | ||||
|             upperId = std::clamp<int>(upperId, 0, colorCount - 1); | ||||
|             float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId; | ||||
|             float r = (colors[(lowerId * 3) + 0] * (1.0 - ratio)) + (colors[(upperId * 3) + 0] * (ratio)); | ||||
|             float g = (colors[(lowerId * 3) + 1] * (1.0 - ratio)) + (colors[(upperId * 3) + 1] * (ratio)); | ||||
|             float b = (colors[(lowerId * 3) + 2] * (1.0 - ratio)) + (colors[(upperId * 3) + 2] * (ratio)); | ||||
|             waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r; | ||||
|         } | ||||
|         updateWaterfallFb(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::autoRange() { | ||||
|         float min = INFINITY; | ||||
|         float max = -INFINITY; | ||||
|         for (int i = 0; i < dataWidth; i++) { | ||||
|             if (latestFFT[i] < min) { | ||||
|                 min = latestFFT[i]; | ||||
|             } | ||||
|             if (latestFFT[i] > max) { | ||||
|                 max = latestFFT[i]; | ||||
|             } | ||||
|         } | ||||
|         fftMin = min - 5; | ||||
|         fftMax = max + 5; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setCenterFrequency(double freq) { | ||||
|         centerFreq = freq; | ||||
|         lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0); | ||||
|         upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0); | ||||
|         updateAllVFOs(); | ||||
|     } | ||||
|  | ||||
|     double WaterFall::getCenterFrequency() { | ||||
|         return centerFreq; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setBandwidth(double bandWidth) { | ||||
|         double currentRatio = viewBandwidth / wholeBandwidth; | ||||
|         wholeBandwidth = bandWidth; | ||||
|         setViewBandwidth(bandWidth * currentRatio); | ||||
|         for (auto const& [name, vfo] : vfos) { | ||||
|             if (vfo->lowerOffset < -(bandWidth / 2)) { | ||||
|                 vfo->setCenterOffset(-(bandWidth / 2)); | ||||
|             } | ||||
|             if (vfo->upperOffset > (bandWidth / 2)) { | ||||
|                 vfo->setCenterOffset(bandWidth / 2); | ||||
|             } | ||||
|         } | ||||
|         updateAllVFOs(); | ||||
|     } | ||||
|  | ||||
|     double WaterFall::getBandwidth() { | ||||
|         return wholeBandwidth; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setViewBandwidth(double bandWidth) { | ||||
|         std::lock_guard<std::mutex> lck(buf_mtx); | ||||
|         if (bandWidth == viewBandwidth) { | ||||
|             return; | ||||
|         } | ||||
|         if (abs(viewOffset) + (bandWidth / 2.0) > wholeBandwidth / 2.0) { | ||||
|             if (viewOffset < 0) { | ||||
|                 viewOffset = (bandWidth / 2.0) - (wholeBandwidth / 2.0); | ||||
|             } | ||||
|             else { | ||||
|                 viewOffset = (wholeBandwidth / 2.0) - (bandWidth / 2.0); | ||||
|             } | ||||
|         } | ||||
|         viewBandwidth = bandWidth; | ||||
|         lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0); | ||||
|         upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0); | ||||
|         range = findBestRange(bandWidth, maxHSteps); | ||||
|         updateWaterfallFb(); | ||||
|         updateAllVFOs(); | ||||
|     } | ||||
|  | ||||
|     double WaterFall::getViewBandwidth() { | ||||
|         return viewBandwidth; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setViewOffset(double offset) { | ||||
|         std::lock_guard<std::mutex> lck(buf_mtx); | ||||
|         if (offset == viewOffset) { | ||||
|             return; | ||||
|         } | ||||
|         if (offset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) { | ||||
|             offset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0); | ||||
|         } | ||||
|         if (offset + (viewBandwidth / 2.0) > (wholeBandwidth / 2.0)) { | ||||
|             offset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0); | ||||
|         } | ||||
|         viewOffset = offset; | ||||
|         lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0); | ||||
|         upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0); | ||||
|         updateWaterfallFb(); | ||||
|         updateAllVFOs(); | ||||
|     } | ||||
|  | ||||
|     double WaterFall::getViewOffset() { | ||||
|         return viewOffset; | ||||
|     } | ||||
|      | ||||
|     void WaterFall::setFFTMin(float min) { | ||||
|         fftMin = min; | ||||
|         vRange = findBestRange(fftMax - fftMin, maxVSteps); | ||||
|     } | ||||
|  | ||||
|     float WaterFall::getFFTMin() { | ||||
|         return fftMin; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setFFTMax(float max) { | ||||
|         fftMax = max; | ||||
|         vRange = findBestRange(fftMax - fftMin, maxVSteps); | ||||
|     } | ||||
|  | ||||
|     float WaterFall::getFFTMax() { | ||||
|         return fftMax; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setWaterfallMin(float min) { | ||||
|         std::lock_guard<std::mutex> lck(buf_mtx); | ||||
|         if (min == waterfallMin) { | ||||
|             return; | ||||
|         } | ||||
|         waterfallMin = min; | ||||
|         updateWaterfallFb(); | ||||
|     } | ||||
|  | ||||
|     float WaterFall::getWaterfallMin() { | ||||
|         return waterfallMin; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setWaterfallMax(float max) { | ||||
|         std::lock_guard<std::mutex> lck(buf_mtx); | ||||
|         if (max == waterfallMax) { | ||||
|             return; | ||||
|         } | ||||
|         waterfallMax = max; | ||||
|         updateWaterfallFb(); | ||||
|     } | ||||
|  | ||||
|     float WaterFall::getWaterfallMax() { | ||||
|         return waterfallMax; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::updateAllVFOs() { | ||||
|         for (auto const& [name, vfo] : vfos) { | ||||
|             vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setRawFFTSize(int size, bool lock) { | ||||
|         std::lock_guard<std::mutex> lck(buf_mtx); | ||||
|         rawFFTSize = size; | ||||
|         if (rawFFTs != NULL) { | ||||
|             int wfSize = std::max<int>(1, waterfallHeight); | ||||
|             rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * wfSize * sizeof(float)); | ||||
|         } | ||||
|         else { | ||||
|             int wfSize = std::max<int>(1, waterfallHeight); | ||||
|             rawFFTs = (float*)malloc(rawFFTSize * wfSize * sizeof(float)); | ||||
|         } | ||||
|         memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float)); | ||||
|     } | ||||
|  | ||||
|     void WaterfallVFO::setOffset(double offset) { | ||||
|         generalOffset = offset; | ||||
|         if (reference == REF_CENTER) { | ||||
|             centerOffset = offset; | ||||
|             lowerOffset = offset - (bandwidth / 2.0); | ||||
|             upperOffset = offset + (bandwidth / 2.0); | ||||
|         } | ||||
|         else if (reference == REF_LOWER) { | ||||
|             lowerOffset = offset; | ||||
|             centerOffset = offset + (bandwidth / 2.0); | ||||
|             upperOffset = offset + bandwidth; | ||||
|         } | ||||
|         else if (reference == REF_UPPER) { | ||||
|             upperOffset = offset; | ||||
|             centerOffset = offset - (bandwidth / 2.0); | ||||
|             lowerOffset = offset - bandwidth; | ||||
|         } | ||||
|         centerOffsetChanged = true; | ||||
|         upperOffsetChanged = true; | ||||
|         lowerOffsetChanged = true; | ||||
|         redrawRequired = true; | ||||
|     } | ||||
|  | ||||
|     void WaterfallVFO::setCenterOffset(double offset) { | ||||
|         if (reference == REF_CENTER) { | ||||
|             generalOffset = offset; | ||||
|         } | ||||
|         else if (reference == REF_LOWER) { | ||||
|             generalOffset = offset - (bandwidth / 2.0); | ||||
|         } | ||||
|         else if (reference == REF_UPPER) { | ||||
|             generalOffset = offset + (bandwidth / 2.0); | ||||
|         } | ||||
|         centerOffset = offset; | ||||
|         lowerOffset = offset - (bandwidth / 2.0); | ||||
|         upperOffset = offset + (bandwidth / 2.0); | ||||
|         centerOffsetChanged = true; | ||||
|         upperOffsetChanged = true; | ||||
|         lowerOffsetChanged = true; | ||||
|         redrawRequired = true; | ||||
|     } | ||||
|  | ||||
|     void WaterfallVFO::setBandwidth(double bw) { | ||||
|         if (bandwidth == bw || bw < 0) { | ||||
|             return; | ||||
|         } | ||||
|         bandwidth = bw; | ||||
|         if (reference == REF_CENTER) { | ||||
|             lowerOffset = centerOffset - (bandwidth / 2.0); | ||||
|             upperOffset = centerOffset + (bandwidth / 2.0); | ||||
|         } | ||||
|         else if (reference == REF_LOWER) { | ||||
|             centerOffset = lowerOffset + (bandwidth / 2.0); | ||||
|             upperOffset = lowerOffset + bandwidth; | ||||
|             centerOffsetChanged = true; | ||||
|         } | ||||
|         else if (reference == REF_UPPER) { | ||||
|             centerOffset = upperOffset - (bandwidth / 2.0); | ||||
|             lowerOffset = upperOffset - bandwidth; | ||||
|             centerOffsetChanged = true; | ||||
|         } | ||||
|         redrawRequired = true; | ||||
|     } | ||||
|  | ||||
|     void WaterfallVFO::setReference(int ref) { | ||||
|         if (reference == ref || ref < 0 || ref >= _REF_COUNT) { | ||||
|             return; | ||||
|         } | ||||
|         reference = ref; | ||||
|         setOffset(generalOffset); | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void WaterfallVFO::updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight) { | ||||
|         double width = (bandwidth / viewBandwidth) * (double)dataWidth; | ||||
|         int center = roundf((((centerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0)); | ||||
|         int left = roundf((((lowerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0)); | ||||
|         int right = roundf((((upperOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0)); | ||||
|  | ||||
|         if (left >= 0 && left < dataWidth && reference == REF_LOWER) { | ||||
|             lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9); | ||||
|             lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9); | ||||
|             lineVisible = true; | ||||
|         } | ||||
|         else if (center >= 0 && center < dataWidth && reference == REF_CENTER) { | ||||
|             lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9); | ||||
|             lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9); | ||||
|             lineVisible = true; | ||||
|         } | ||||
|         else if (right >= 0 && right < dataWidth && reference == REF_UPPER) { | ||||
|             lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9); | ||||
|             lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9); | ||||
|             lineVisible = true; | ||||
|         } | ||||
|         else { | ||||
|             lineVisible = false; | ||||
|         } | ||||
|  | ||||
|         left = std::clamp<int>(left, 0, dataWidth - 1); | ||||
|         right = std::clamp<int>(right, 0, dataWidth - 1); | ||||
|  | ||||
|         rectMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10); | ||||
|         rectMax = ImVec2(widgetPos.x + 51 + right, widgetPos.y + fftHeight + 10); | ||||
|     } | ||||
|  | ||||
|     void WaterfallVFO::draw(ImGuiWindow* window, bool selected) { | ||||
|         window->DrawList->AddRectFilled(rectMin, rectMax, IM_COL32(255, 255, 255, 50)); | ||||
|         if (lineVisible) { | ||||
|             window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255)); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     void WaterFall::showWaterfall() { | ||||
|         buf_mtx.lock(); | ||||
|         waterfallVisible = true; | ||||
|         onResize(); | ||||
|         memset(rawFFTs, 0, waterfallHeight * rawFFTSize * sizeof(float)); | ||||
|         updateWaterfallFb(); | ||||
|         buf_mtx.unlock(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::hideWaterfall() { | ||||
|         buf_mtx.lock(); | ||||
|         waterfallVisible = false; | ||||
|         onResize(); | ||||
|         buf_mtx.unlock(); | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setFFTHeight(int height) { | ||||
|         FFTAreaHeight = height; | ||||
|         newFFTAreaHeight = height; | ||||
|         buf_mtx.lock(); | ||||
|         onResize(); | ||||
|         buf_mtx.unlock(); | ||||
|     } | ||||
|      | ||||
|     int WaterFall::getFFTHeight() { | ||||
|         return FFTAreaHeight; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::showBandplan() { | ||||
|         bandplanVisible = true; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::hideBandplan() { | ||||
|         bandplanVisible = false; | ||||
|     } | ||||
|  | ||||
|     void WaterfallVFO::setSnapInterval(double interval) { | ||||
|         snapInterval = interval; | ||||
|     } | ||||
| }; | ||||
|  | ||||
							
								
								
									
										203
									
								
								core/src/gui/widgets/waterfall.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								core/src/gui/widgets/waterfall.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | ||||
| #pragma once | ||||
| #include <vector> | ||||
| #include <mutex> | ||||
| #include <gui/widgets/bandplan.h> | ||||
| #include <imgui/imgui.h> | ||||
| #include <imgui/imgui_internal.h> | ||||
| #include <GL/glew.h> | ||||
|  | ||||
| #define WATERFALL_RESOLUTION    1000000 | ||||
|  | ||||
| namespace ImGui { | ||||
|     class WaterfallVFO { | ||||
|     public: | ||||
|         void setOffset(double offset); | ||||
|         void setCenterOffset(double offset); | ||||
|         void setBandwidth(double bw); | ||||
|         void setReference(int ref); | ||||
|         void setSnapInterval(double interval); | ||||
|         void updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight); // NOTE: Datawidth double??? | ||||
|         void draw(ImGuiWindow* window, bool selected); | ||||
|  | ||||
|         enum { | ||||
|             REF_LOWER, | ||||
|             REF_CENTER, | ||||
|             REF_UPPER, | ||||
|             _REF_COUNT | ||||
|         }; | ||||
|  | ||||
|         double generalOffset; | ||||
|         double centerOffset; | ||||
|         double lowerOffset; | ||||
|         double upperOffset; | ||||
|         double bandwidth; | ||||
|         double snapInterval = 5000; | ||||
|         int reference = REF_CENTER; | ||||
|  | ||||
|         ImVec2 rectMin; | ||||
|         ImVec2 rectMax; | ||||
|         ImVec2 lineMin; | ||||
|         ImVec2 lineMax; | ||||
|  | ||||
|         bool centerOffsetChanged = false; | ||||
|         bool lowerOffsetChanged = false; | ||||
|         bool upperOffsetChanged = false; | ||||
|         bool redrawRequired = true; | ||||
|         bool lineVisible = true; | ||||
|     }; | ||||
|  | ||||
|     class WaterFall { | ||||
|     public: | ||||
|         WaterFall(); | ||||
|  | ||||
|         void init(); | ||||
|  | ||||
|         void draw(); | ||||
|         float* getFFTBuffer(); | ||||
|         void pushFFT(); | ||||
|  | ||||
|         void updatePallette(float colors[][3], int colorCount); | ||||
|         void updatePalletteFromArray(float* colors, int colorCount); | ||||
|  | ||||
|         void setCenterFrequency(double freq); | ||||
|         double getCenterFrequency(); | ||||
|  | ||||
|         void setBandwidth(double bandWidth); | ||||
|         double getBandwidth(); | ||||
|  | ||||
|         void setViewBandwidth(double bandWidth); | ||||
|         double getViewBandwidth(); | ||||
|  | ||||
|         void setViewOffset(double offset); | ||||
|         double getViewOffset(); | ||||
|  | ||||
|         void setFFTMin(float min); | ||||
|         float getFFTMin(); | ||||
|  | ||||
|         void setFFTMax(float max); | ||||
|         float getFFTMax(); | ||||
|  | ||||
|         void setWaterfallMin(float min); | ||||
|         float getWaterfallMin(); | ||||
|  | ||||
|         void setWaterfallMax(float max); | ||||
|         float getWaterfallMax(); | ||||
|  | ||||
|         void setZoom(double zoomLevel); | ||||
|         void setOffset(double zoomOffset); | ||||
|  | ||||
|         void autoRange(); | ||||
|  | ||||
|         void selectFirstVFO(); | ||||
|  | ||||
|         void showWaterfall(); | ||||
|         void hideWaterfall(); | ||||
|  | ||||
|         void showBandplan(); | ||||
|         void hideBandplan(); | ||||
|  | ||||
|         void setFFTHeight(int height); | ||||
|         int getFFTHeight(); | ||||
|  | ||||
|         void setRawFFTSize(int size, bool lock = true); | ||||
|  | ||||
|         bool centerFreqMoved = false; | ||||
|         bool vfoFreqChanged = false; | ||||
|         bool bandplanEnabled = false; | ||||
|         bandplan::BandPlan_t* bandplan = NULL; | ||||
|  | ||||
|         std::map<std::string, WaterfallVFO*> vfos; | ||||
|         std::string selectedVFO = ""; | ||||
|         bool selectedVFOChanged = false; | ||||
|  | ||||
|         enum { | ||||
|             REF_LOWER, | ||||
|             REF_CENTER, | ||||
|             REF_UPPER, | ||||
|             _REF_COUNT | ||||
|         }; | ||||
|  | ||||
|  | ||||
|     private: | ||||
|         void drawWaterfall(); | ||||
|         void drawFFT(); | ||||
|         void drawVFOs(); | ||||
|         void drawBandPlan(); | ||||
|         void processInputs(); | ||||
|         void onPositionChange(); | ||||
|         void onResize(); | ||||
|         void updateWaterfallFb(); | ||||
|         void updateWaterfallTexture(); | ||||
|         void updateAllVFOs(); | ||||
|  | ||||
|         bool waterfallUpdate = false; | ||||
|  | ||||
|         uint32_t waterfallPallet[WATERFALL_RESOLUTION]; | ||||
|          | ||||
|         ImVec2 widgetPos; | ||||
|         ImVec2 widgetEndPos; | ||||
|         ImVec2 widgetSize; | ||||
|  | ||||
|         ImVec2 lastWidgetPos; | ||||
|         ImVec2 lastWidgetSize; | ||||
|  | ||||
|         ImVec2 fftAreaMin; | ||||
|         ImVec2 fftAreaMax; | ||||
|         ImVec2 freqAreaMin; | ||||
|         ImVec2 freqAreaMax; | ||||
|         ImVec2 waterfallAreaMin; | ||||
|         ImVec2 waterfallAreaMax; | ||||
|  | ||||
|         ImGuiWindow* window; | ||||
|  | ||||
|         GLuint textureId; | ||||
|  | ||||
|         std::mutex buf_mtx; | ||||
|  | ||||
|         float vRange; | ||||
|  | ||||
|         int maxVSteps; | ||||
|         int maxHSteps; | ||||
|  | ||||
|         int dataWidth;              // Width of the FFT and waterfall | ||||
|         int fftHeight;              // Height of the fft graph | ||||
|         int waterfallHeight = 0;    // Height of the waterfall | ||||
|  | ||||
|         double viewBandwidth; | ||||
|         double viewOffset; | ||||
|  | ||||
|         double lowerFreq; | ||||
|         double upperFreq; | ||||
|         double range; | ||||
|  | ||||
|         float lastDrag; | ||||
|  | ||||
|         int vfoRef = REF_CENTER; | ||||
|  | ||||
|         // Absolute values | ||||
|         double centerFreq; | ||||
|         double wholeBandwidth; | ||||
|  | ||||
|         // Ranges | ||||
|         float fftMin; | ||||
|         float fftMax; | ||||
|         float waterfallMin; | ||||
|         float waterfallMax; | ||||
|  | ||||
|         //std::vector<std::vector<float>> rawFFTs; | ||||
|         int rawFFTSize; | ||||
|         float* rawFFTs = NULL; | ||||
|         float* latestFFT; | ||||
|         int currentFFTLine = 0; | ||||
|         int fftLines = 0; | ||||
|  | ||||
|         uint32_t* waterfallFb; | ||||
|  | ||||
|         bool draggingFW = false; | ||||
|         int FFTAreaHeight; | ||||
|         int newFFTAreaHeight; | ||||
|  | ||||
|         bool waterfallVisible = true; | ||||
|         bool bandplanVisible = false; | ||||
|     }; | ||||
| }; | ||||
| @@ -2640,6 +2640,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat | ||||
| 
 | ||||
|     // Slider behavior
 | ||||
|     ImRect grab_bb; | ||||
| 
 | ||||
|     const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb); | ||||
|     if (value_changed) | ||||
|         MarkItemEdited(id); | ||||
							
								
								
									
										2631
									
								
								core/src/imgui/stb_image_resize.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2631
									
								
								core/src/imgui/stb_image_resize.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										25347
									
								
								core/src/json.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25347
									
								
								core/src/json.hpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										145
									
								
								core/src/module.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								core/src/module.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| #include <module.h> | ||||
| #include <filesystem> | ||||
| #include <spdlog/spdlog.h> | ||||
|  | ||||
| ModuleManager::Module_t ModuleManager::loadModule(std::string path) { | ||||
|     Module_t mod; | ||||
|     if (!std::filesystem::exists(path)) { | ||||
|         spdlog::error("{0} does not exist", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     if (!std::filesystem::is_regular_file(path)) { | ||||
|         spdlog::error("{0} isn't a loadable module", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
| #ifdef _WIN32 | ||||
|     mod.handle = LoadLibraryA(path.c_str()); | ||||
|     if (mod.handle == NULL) { | ||||
|         spdlog::error("Couldn't load {0}.", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     mod.info = (ModuleInfo_t*)GetProcAddress(mod.handle, "_INFO_"); | ||||
|     mod.init = (void(*)())GetProcAddress(mod.handle, "_INIT_"); | ||||
|     mod.createInstance = (Instance*(*)(std::string))GetProcAddress(mod.handle, "_CREATE_INSTANCE_"); | ||||
|     mod.deleteInstance = (void(*)(Instance*))GetProcAddress(mod.handle, "_DELETE_INSTANCE_"); | ||||
|     mod.end = (void(*)())GetProcAddress(mod.handle, "_END_"); | ||||
| #else | ||||
|     mod.handle = dlopen(path.c_str(), RTLD_LAZY); | ||||
|     if (mod.handle == NULL) { | ||||
|         spdlog::error("Couldn't load {0}.", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     mod.info = (ModuleInfo_t*)dlsym(mod.handle, "_INFO_"); | ||||
|     mod.init = (void(*)())dlsym(mod.handle, "_INIT_"); | ||||
|     mod.createInstance = (Instance*(*)(std::string))dlsym(mod.handle, "_CREATE_INSTANCE_"); | ||||
|     mod.deleteInstance = (void(*)(Instance*))dlsym(mod.handle, "_DELETE_INSTANCE_"); | ||||
|     mod.end = (void(*)())dlsym(mod.handle, "_END_"); | ||||
| #endif | ||||
|     if (mod.info == NULL) { | ||||
|         spdlog::error("{0} is missing _INFO_ symbol", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     if (mod.init == NULL) { | ||||
|         spdlog::error("{0} is missing _INIT_ symbol", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     if (mod.createInstance == NULL) { | ||||
|         spdlog::error("{0} is missing _CREATE_INSTANCE_ symbol", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     if (mod.deleteInstance == NULL) { | ||||
|         spdlog::error("{0} is missing _DELETE_INSTANCE_ symbol", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     if (mod.end == NULL) { | ||||
|         spdlog::error("{0} is missing _END_ symbol", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     if (modules.find(mod.info->name) != modules.end()) { | ||||
|         spdlog::error("{0} has the same name as an already loaded module", path); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
|     for (auto const& [name, _mod] : modules) { | ||||
|         if (mod.handle == _mod.handle) { | ||||
|             return _mod; | ||||
|         } | ||||
|     } | ||||
|     mod.init(); | ||||
|     modules[mod.info->name] = mod; | ||||
|     return mod; | ||||
| } | ||||
|  | ||||
| void ModuleManager::createInstance(std::string name, std::string module) { | ||||
|     if (modules.find(module) == modules.end()) { | ||||
|         spdlog::error("Module '{0}' doesn't exist", module); | ||||
|         return; | ||||
|     } | ||||
|     if (instances.find(name) != instances.end()) { | ||||
|         spdlog::error("A module instance with the name '{0}' already exists", name); | ||||
|         return; | ||||
|     } | ||||
|     int maxCount = modules[module].info->maxInstances; | ||||
|     if (countModuleInstances(module) >= maxCount && maxCount > 0) { | ||||
|         spdlog::error("Maximum number of instances reached for '{0}'", module); | ||||
|         return; | ||||
|     } | ||||
|     Instance_t inst; | ||||
|     inst.module = modules[module]; | ||||
|     inst.instance = inst.module.createInstance(name); | ||||
|     instances[name] = inst; | ||||
| } | ||||
|  | ||||
| void ModuleManager::deleteInstance(std::string name) { | ||||
|     spdlog::error("DELETE INSTANCE NOT IMPLEMENTED"); | ||||
| } | ||||
|  | ||||
| void ModuleManager::deleteInstance(ModuleManager::Instance* instance) { | ||||
|     spdlog::error("DELETE INSTANCE NOT IMPLEMENTED"); | ||||
| } | ||||
|  | ||||
| void ModuleManager::enableInstance(std::string name) { | ||||
|     if (instances.find(name) == instances.end()) { | ||||
|         spdlog::error("Cannot enable '{0}', instance doesn't exist", name); | ||||
|         return; | ||||
|     } | ||||
|     instances[name].instance->enable(); | ||||
| } | ||||
|  | ||||
| void ModuleManager::disableInstance(std::string name) { | ||||
|     if (instances.find(name) == instances.end()) { | ||||
|         spdlog::error("Cannot disable '{0}', instance doesn't exist", name); | ||||
|         return; | ||||
|     } | ||||
|     instances[name].instance->disable(); | ||||
| } | ||||
|  | ||||
| bool ModuleManager::instanceEnabled(std::string name) { | ||||
|     if (instances.find(name) == instances.end()) { | ||||
|         spdlog::error("Cannot check if '{0}' is enabled, instance doesn't exist", name); | ||||
|         return false; | ||||
|     } | ||||
|     return instances[name].instance->isEnabled(); | ||||
| } | ||||
|  | ||||
| int ModuleManager::countModuleInstances(std::string module) { | ||||
|     if (modules.find(module) == modules.end()) { | ||||
|         spdlog::error("Cannot count instances of '{0}', Module doesn't exist", module); | ||||
|         return -1; | ||||
|     } | ||||
|     ModuleManager::Module_t mod = modules[module]; | ||||
|     int count = 0; | ||||
|     for (auto const& [name, instance] : instances) { | ||||
|         if (instance.module == mod) { count++; } | ||||
|     } | ||||
|     return count; | ||||
| } | ||||
							
								
								
									
										91
									
								
								core/src/module.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								core/src/module.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <map> | ||||
| #include <json.hpp> | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #ifdef SDRPP_IS_CORE | ||||
| #define SDRPP_EXPORT extern "C" __declspec(dllexport) | ||||
| #else | ||||
| #define SDRPP_EXPORT extern "C" __declspec(dllimport) | ||||
| #endif | ||||
| #else | ||||
| #define SDRPP_EXPORT extern | ||||
| #endif | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <Windows.h> | ||||
| #define MOD_EXPORT extern "C" __declspec(dllexport) | ||||
| #define SDRPP_MOD_EXTENTSION    ".dll" | ||||
| #else | ||||
| #include <dlfcn.h> | ||||
| #define MOD_EXPORT extern "C" | ||||
| #define SDRPP_MOD_EXTENTSION    ".so" | ||||
| #endif | ||||
|  | ||||
| class ModuleManager { | ||||
| public: | ||||
|     struct ModuleInfo_t { | ||||
|         const char* name; | ||||
|         const char* description; | ||||
|         const char* author; | ||||
|         const int versionMajor; | ||||
|         const int versionMinor; | ||||
|         const int versionBuild; | ||||
|         const int maxInstances; | ||||
|     }; | ||||
|  | ||||
|     class Instance { | ||||
|     public: | ||||
|         virtual void enable() = 0; | ||||
|         virtual void disable() = 0; | ||||
|         virtual bool isEnabled() = 0; | ||||
|     }; | ||||
|  | ||||
|     struct Module_t { | ||||
| #ifdef _WIN32 | ||||
|         HMODULE handle; | ||||
| #else | ||||
|         void* handle; | ||||
| #endif | ||||
|         ModuleManager::ModuleInfo_t* info; | ||||
|         void (*init)(); | ||||
|         ModuleManager::Instance* (*createInstance)(std::string name); | ||||
|         void (*deleteInstance)(ModuleManager::Instance* instance); | ||||
|         void (*end)(); | ||||
|  | ||||
|         friend bool operator==(const Module_t& a, const Module_t& b) { | ||||
|             if (a.handle != b.handle) { return false; } | ||||
|             if (a.info != b.info) { return false; } | ||||
|             if (a.init != b.init) { return false; } | ||||
|             if (a.createInstance != b.createInstance) { return false; } | ||||
|             if (a.deleteInstance != b.deleteInstance) { return false; } | ||||
|             if (a.end != b.end) { return false; } | ||||
|             return true; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     struct Instance_t { | ||||
|         ModuleManager::Module_t module; | ||||
|         ModuleManager::Instance* instance; | ||||
|     }; | ||||
|  | ||||
|     ModuleManager::Module_t loadModule(std::string path); | ||||
|  | ||||
|     void createInstance(std::string name, std::string module); | ||||
|     void deleteInstance(std::string name); | ||||
|     void deleteInstance(ModuleManager::Instance* instance); | ||||
|  | ||||
|     void enableInstance(std::string name); | ||||
|     void disableInstance(std::string name); | ||||
|     bool instanceEnabled(std::string name); | ||||
|  | ||||
|     int countModuleInstances(std::string module); | ||||
|  | ||||
| private: | ||||
|     std::map<std::string, ModuleManager::Module_t> modules; | ||||
|     std::map<std::string, ModuleManager::Instance_t> instances; | ||||
|  | ||||
| }; | ||||
|  | ||||
| #define SDRPP_MOD_INFO    MOD_EXPORT const ModuleManager::ModuleInfo_t _INFO_ | ||||
							
								
								
									
										31
									
								
								core/src/options.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								core/src/options.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| #include <options.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <stdlib.h> | ||||
|  | ||||
| namespace options { | ||||
|     CMDLineOptions opts; | ||||
|  | ||||
|     void loadDefaults() { | ||||
| #ifdef _WIN32 | ||||
|         opts.root = "."; | ||||
| #else | ||||
|         std::string homedir = getenv("HOME"); | ||||
|         opts.root = homedir + "/.config/sdrpp"; | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     bool parse(int argc, char *argv[]) { | ||||
|         for (int i = 1; i < argc; i++) { | ||||
|             char* arg = argv[i]; | ||||
|             if (!strcmp(arg, "-r") || !strcmp(arg, "--root")) { | ||||
|                 if (i == argc - 1) { return false; } | ||||
|                 opts.root = argv[++i]; | ||||
|             } | ||||
|             else { | ||||
|                 spdlog::error("Invalid command line option: {0}", arg); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								core/src/options.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								core/src/options.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <module.h> | ||||
|  | ||||
| namespace options { | ||||
|     struct CMDLineOptions { | ||||
|         std::string root; | ||||
|     }; | ||||
|  | ||||
|     SDRPP_EXPORT CMDLineOptions opts; | ||||
|  | ||||
|     void loadDefaults(); | ||||
|     bool parse(int argc, char *argv[]); | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user