mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 08:58:13 +01:00 
			
		
		
		
	Compare commits
	
		
			40 Commits
		
	
	
		
			better_ins
			...
			new_source
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 6891d0bb0f | ||
|  | b835d07573 | ||
|  | 69161253e8 | ||
|  | 5ab3428b90 | ||
|  | 7f002f6276 | ||
|  | a728403a3f | ||
|  | 0f1d2da3b7 | ||
|  | 6d0b65c27f | ||
|  | f640cdcb6a | ||
|  | 80a90e13d9 | ||
|  | 3982db73d3 | ||
|  | bd64f07a20 | ||
|  | c9950d9331 | ||
|  | 9bc609f4e4 | ||
|  | bcc8e20e66 | ||
|  | b07e828fed | ||
|  | bd24a4a5eb | ||
|  | fe4a7b32a7 | ||
|  | 0e1ab29b5d | ||
|  | fbbafddd3d | ||
|  | 1cbc8ec6f5 | ||
|  | 9f65e3ec71 | ||
|  | 08f3a7d201 | ||
|  | 9ce62f8885 | ||
|  | caeaa2d46c | ||
|  | 7ae030a3a6 | ||
|  | 1b27379a3d | ||
|  | e52123038e | ||
|  | ec8c60111d | ||
|  | f61799cf5f | ||
|  | 17eccf5156 | ||
|  | e835c8dd9a | ||
|  | acb1be121c | ||
|  | 0fa89614bb | ||
|  | 256affd918 | ||
|  | e80cdbf248 | ||
|  | 75e66226c3 | ||
|  | c2f0e756a5 | ||
|  | 79dd5bdcbb | ||
|  | 6dce28345c | 
							
								
								
									
										19
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							| @@ -71,14 +71,17 @@ jobs: | ||||
|           run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install . | ||||
|  | ||||
|         - name: Install libperseus-sdr | ||||
|           run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release  ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr" | ||||
|           run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release  ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr" | ||||
|  | ||||
|         - name: Install librfnm | ||||
|           run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install . | ||||
|           run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install . | ||||
|  | ||||
|         - name: Install libfobos | ||||
|           run: git clone https://github.com/AlexandreRouma/libfobos ; cd libfobos ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install . | ||||
|  | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake -DCOPY_MSVC_REDISTRIBUTABLES=ON "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
|           run: cmake -DCOPY_MSVC_REDISTRIBUTABLES=ON "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
| @@ -127,12 +130,15 @@ jobs: | ||||
|         - name: Install librfnm | ||||
|           run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install libfobos | ||||
|           run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install more recent librtlsdr | ||||
|           run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
| @@ -181,12 +187,15 @@ jobs: | ||||
|         - name: Install librfnm | ||||
|           run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install libfobos | ||||
|           run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install more recent librtlsdr | ||||
|           run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|   | ||||
| @@ -15,9 +15,11 @@ option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio) | ||||
| option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF) | ||||
| option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF) | ||||
| option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) | ||||
| option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF) | ||||
| option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON) | ||||
| option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF) | ||||
| option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF) | ||||
| option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF) | ||||
| option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF) | ||||
| @@ -43,6 +45,7 @@ option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: port | ||||
|  | ||||
| # Decoders | ||||
| option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF) | ||||
| option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF) | ||||
| option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF) | ||||
| option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF) | ||||
| option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF) | ||||
| @@ -97,56 +100,13 @@ set(SDRPP_MODULE_COMPILER_FLAGS ${SDRPP_COMPILER_FLAGS}) | ||||
|  | ||||
| # Set a default install prefix | ||||
| if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) | ||||
|     if (MSVC) | ||||
|         set(CMAKE_INSTALL_PREFIX "C:/Program Files/SDR++/" CACHE PATH "..." FORCE) | ||||
|     elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") | ||||
|     if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") | ||||
|         set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "..." FORCE) | ||||
|     else() | ||||
|         set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "..." FORCE) | ||||
|     endif() | ||||
| endif() | ||||
|  | ||||
| # Include standard install directory definitions | ||||
| include(GNUInstallDirs) | ||||
|  | ||||
| # Set up SDR++ directory load paths | ||||
| if (MSVC) | ||||
|     set(SDRPP_MODULES_LOAD_DIR "./modules") | ||||
|     set(SDRPP_RES_LOAD_DIR "./res") | ||||
| elseif (USE_BUNDLE_DEFAULTS) | ||||
|     set(SDRPP_MODULES_LOAD_DIR "../Plugins") | ||||
|     set(SDRPP_RES_LOAD_DIR "../Resources") | ||||
| elseif (ANDROID) | ||||
|     set(SDRPP_MODULES_LOAD_DIR "/modules") | ||||
|     set(SDRPP_RES_LOAD_DIR "/res") | ||||
| else() | ||||
|     set(SDRPP_MODULES_LOAD_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/sdrpp/plugins") | ||||
|     set(SDRPP_RES_LOAD_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/sdrpp") | ||||
| endif() | ||||
|  | ||||
| # Set up SDR++ directory install paths | ||||
| if (MSVC) | ||||
|     set(SDRPP_BIN_INSTALL_DIR "/") | ||||
|     set(SDRPP_LIB_INSTALL_DIR "/") | ||||
|     set(SDRPP_MODULES_INSTALL_DIR "modules") | ||||
|     set(SDRPP_RES_INSTALL_DIR "res") | ||||
| elseif (USE_BUNDLE_DEFAULTS) | ||||
|     set(SDRPP_BIN_INSTALL_DIR "Contents/MacOS") | ||||
|     set(SDRPP_LIB_INSTALL_DIR "Contents/Frameworks") | ||||
|     set(SDRPP_MODULES_INSTALL_DIR "Contents/Plugins") | ||||
|     set(SDRPP_RES_INSTALL_DIR "Contents/Resources") | ||||
| elseif (ANDROID) | ||||
|     set(SDRPP_BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") | ||||
|     set(SDRPP_LIB_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}") | ||||
|     set(SDRPP_MODULES_INSTALL_DIR "/modules") | ||||
|     set(SDRPP_RES_INSTALL_DIR "/res") | ||||
| else() | ||||
|     set(SDRPP_BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") | ||||
|     set(SDRPP_LIB_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}") | ||||
|     set(SDRPP_MODULES_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/sdrpp/plugins") | ||||
|     set(SDRPP_RES_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/sdrpp") | ||||
| endif() | ||||
|  | ||||
| # Configure toolchain for android | ||||
| if (ANDROID) | ||||
|     set(CMAKE_SHARED_LINKER_FLAGS | ||||
| @@ -185,6 +145,10 @@ if (OPT_BUILD_FILE_SOURCE) | ||||
| add_subdirectory("source_modules/file_source") | ||||
| endif (OPT_BUILD_FILE_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_FOBOSSDR_SOURCE) | ||||
| add_subdirectory("source_modules/fobossdr_source") | ||||
| endif (OPT_BUILD_FOBOSSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_HACKRF_SOURCE) | ||||
| add_subdirectory("source_modules/hackrf_source") | ||||
| endif (OPT_BUILD_HACKRF_SOURCE) | ||||
| @@ -197,6 +161,10 @@ if (OPT_BUILD_HERMES_SOURCE) | ||||
| add_subdirectory("source_modules/hermes_source") | ||||
| endif (OPT_BUILD_HERMES_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_KCSDR_SOURCE) | ||||
| add_subdirectory("source_modules/kcsdr_source") | ||||
| endif (OPT_BUILD_KCSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_LIMESDR_SOURCE) | ||||
| add_subdirectory("source_modules/limesdr_source") | ||||
| endif (OPT_BUILD_LIMESDR_SOURCE) | ||||
| @@ -285,6 +253,10 @@ if (OPT_BUILD_ATV_DECODER) | ||||
| add_subdirectory("decoder_modules/atv_decoder") | ||||
| endif (OPT_BUILD_ATV_DECODER) | ||||
|  | ||||
| if (OPT_BUILD_DAB_DECODER) | ||||
| add_subdirectory("decoder_modules/dab_decoder") | ||||
| endif (OPT_BUILD_DAB_DECODER) | ||||
|  | ||||
| if (OPT_BUILD_FALCON9_DECODER) | ||||
| add_subdirectory("decoder_modules/falcon9_decoder") | ||||
| endif (OPT_BUILD_FALCON9_DECODER) | ||||
| @@ -406,18 +378,17 @@ endif () | ||||
| # Create module cmake file | ||||
| configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY) | ||||
|  | ||||
| # Create desktop entry file | ||||
| # Install directives | ||||
| install(TARGETS sdrpp DESTINATION bin) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/bandplans DESTINATION share/sdrpp) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/colormaps DESTINATION share/sdrpp) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/fonts DESTINATION share/sdrpp) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/icons DESTINATION share/sdrpp) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/themes DESTINATION share/sdrpp) | ||||
| configure_file(${CMAKE_SOURCE_DIR}/sdrpp.desktop ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop @ONLY) | ||||
|  | ||||
| # Install directives | ||||
| install(TARGETS sdrpp DESTINATION ${SDRPP_BIN_INSTALL_DIR}) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/bandplans DESTINATION ${SDRPP_RES_INSTALL_DIR}) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/colormaps DESTINATION ${SDRPP_RES_INSTALL_DIR}) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/fonts DESTINATION ${SDRPP_RES_INSTALL_DIR}) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/icons DESTINATION ${SDRPP_RES_INSTALL_DIR}) | ||||
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/themes DESTINATION ${SDRPP_RES_INSTALL_DIR}) | ||||
| if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") | ||||
|     install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) | ||||
|     install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop DESTINATION share/applications) | ||||
| endif () | ||||
|  | ||||
| # Create uninstall target | ||||
|   | ||||
| @@ -18,10 +18,6 @@ if (MSVC) | ||||
|     set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) | ||||
| endif () | ||||
|  | ||||
| # Add compiler definitions for the directories | ||||
| add_definitions(-DSDRPP_MODULES_LOAD_DIR="${SDRPP_MODULES_LOAD_DIR}") | ||||
| add_definitions(-DSDRPP_RES_LOAD_DIR="${SDRPP_RES_LOAD_DIR}") | ||||
|  | ||||
| # Configure backend sources | ||||
| if (OPT_BACKEND_GLFW) | ||||
|     file(GLOB_RECURSE BACKEND_SRC "backends/glfw/*.cpp" "backends/glfw/*.c") | ||||
| @@ -37,6 +33,9 @@ add_library(sdrpp_core SHARED ${SRC} ${BACKEND_SRC}) | ||||
| # Set compiler options | ||||
| target_compile_options(sdrpp_core PRIVATE ${SDRPP_COMPILER_FLAGS}) | ||||
|  | ||||
| # Set the install prefix | ||||
| target_compile_definitions(sdrpp_core PUBLIC INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}") | ||||
|  | ||||
| # Include core headers | ||||
| target_include_directories(sdrpp_core PUBLIC "src/") | ||||
| target_include_directories(sdrpp_core PUBLIC "src/imgui") | ||||
| @@ -168,4 +167,4 @@ set(CORE_FILES ${RUNTIME_OUTPUT_DIRECTORY} PARENT_SCOPE) | ||||
| # cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" | ||||
|  | ||||
| # Install directives | ||||
| install(TARGETS sdrpp_core DESTINATION ${SDRPP_LIB_INSTALL_DIR}) | ||||
| install(TARGETS sdrpp_core DESTINATION lib) | ||||
| @@ -24,16 +24,14 @@ | ||||
| #include <Windows.h> | ||||
| #endif | ||||
|  | ||||
|  | ||||
| // Default install dirs to make the IDE happy | ||||
| #ifndef SDRPP_MODULES_LOAD_DIR | ||||
| #define SDRPP_MODULES_LOAD_DIR "" | ||||
| #ifndef INSTALL_PREFIX | ||||
| #ifdef __APPLE__ | ||||
| #define INSTALL_PREFIX "/usr/local" | ||||
| #else | ||||
| #define INSTALL_PREFIX "/usr" | ||||
| #endif | ||||
| #ifndef SDRPP_RES_LOAD_DIR | ||||
| #define SDRPP_RES_LOAD_DIR "" | ||||
| #endif | ||||
|  | ||||
|  | ||||
| namespace core { | ||||
|     ConfigManager configManager; | ||||
|     ModuleManager moduleManager; | ||||
| @@ -175,16 +173,22 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["File Source"]["module"] = "file_source"; | ||||
|     defConfig["moduleInstances"]["File Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source"; | ||||
|     defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source"; | ||||
|     defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source"; | ||||
|     defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source"; | ||||
|     defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source"; | ||||
|     defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source"; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Network Source"]["module"] = "network_source"; | ||||
|     defConfig["moduleInstances"]["Network Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source"; | ||||
|     defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source"; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source"; | ||||
|     defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source"; | ||||
| @@ -197,8 +201,12 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source"; | ||||
|     defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source"; | ||||
|     defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source"; | ||||
|     defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["USRP Source"]["module"] = "usrp_source"; | ||||
|     defConfig["moduleInstances"]["USRP Source"]["enabled"] = true; | ||||
|  | ||||
|     defConfig["moduleInstances"]["Audio Sink"] = "audio_sink"; | ||||
|     defConfig["moduleInstances"]["Network Sink"] = "network_sink"; | ||||
| @@ -224,12 +232,19 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|  | ||||
|     defConfig["modules"] = json::array(); | ||||
|  | ||||
|     defConfig["offsetMode"] = (int)0; // Off | ||||
|     defConfig["offset"] = 0.0; | ||||
|     defConfig["offsets"]["SpyVerter"] = 120000000.0; | ||||
|     defConfig["offsets"]["Ham-It-Up"] = 125000000.0; | ||||
|     defConfig["offsets"]["MMDS S-band (1998MHz)"] = -1998000000.0; | ||||
|     defConfig["offsets"]["DK5AV X-Band"] = -6800000000.0; | ||||
|     defConfig["offsets"]["Ku LNB (9750MHz)"] = -9750000000.0; | ||||
|     defConfig["offsets"]["Ku LNB (10700MHz)"] = -10700000000.0; | ||||
|  | ||||
|     defConfig["selectedOffset"] = "None"; | ||||
|     defConfig["manualOffset"] = 0.0; | ||||
|     defConfig["showMenu"] = true; | ||||
|     defConfig["showWaterfall"] = true; | ||||
|     defConfig["source"] = ""; | ||||
|     defConfig["decimationPower"] = 0; | ||||
|     defConfig["decimation"] = 1; | ||||
|     defConfig["iqCorrection"] = false; | ||||
|     defConfig["invertIQ"] = false; | ||||
|  | ||||
| @@ -250,14 +265,19 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["lockMenuOrder"] = false; | ||||
| #endif | ||||
|  | ||||
| #ifdef __ANDROID__ | ||||
|     defConfig["modulesDirectory"] = root + SDRPP_MODULES_LOAD_DIR; | ||||
|     defConfig["resourcesDirectory"] = root + SDRPP_RES_LOAD_DIR; | ||||
| #if defined(_WIN32) | ||||
|     defConfig["modulesDirectory"] = "./modules"; | ||||
|     defConfig["resourcesDirectory"] = "./res"; | ||||
| #elif defined(IS_MACOS_BUNDLE) | ||||
|     defConfig["modulesDirectory"] = "../Plugins"; | ||||
|     defConfig["resourcesDirectory"] = "../Resources"; | ||||
| #elif defined(__ANDROID__) | ||||
|     defConfig["modulesDirectory"] = root + "/modules"; | ||||
|     defConfig["resourcesDirectory"] = root + "/res"; | ||||
| #else | ||||
|     defConfig["modulesDirectory"] = SDRPP_MODULES_LOAD_DIR; | ||||
|     defConfig["resourcesDirectory"] = SDRPP_RES_LOAD_DIR; | ||||
|     defConfig["modulesDirectory"] = INSTALL_PREFIX "/lib/sdrpp/plugins"; | ||||
|     defConfig["resourcesDirectory"] = INSTALL_PREFIX "/share/sdrpp"; | ||||
| #endif | ||||
|      | ||||
|  | ||||
|     // Load config | ||||
|     flog::info("Loading config"); | ||||
| @@ -305,12 +325,18 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|  | ||||
|     // Remove unused elements | ||||
|     auto items = core::configManager.conf.items(); | ||||
|     auto newConf = core::configManager.conf; | ||||
|     bool configCorrected = false; | ||||
|     for (auto const& item : items) { | ||||
|         if (!defConfig.contains(item.key())) { | ||||
|             flog::info("Unused key in config {0}, repairing", item.key()); | ||||
|             core::configManager.conf.erase(item.key()); | ||||
|             newConf.erase(item.key()); | ||||
|             configCorrected = true; | ||||
|         } | ||||
|     } | ||||
|     if (configCorrected) { | ||||
|         core::configManager.conf = newConf; | ||||
|     } | ||||
|  | ||||
|     // Update to new module representation in config if needed | ||||
|     for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) { | ||||
|   | ||||
| @@ -37,9 +37,12 @@ namespace sdrpp_credits { | ||||
|     const char* hardwareDonators[] = { | ||||
|         "Aaronia AG", | ||||
|         "Airspy", | ||||
|         "Alex 4Z5LV", | ||||
|         "Analog Devices", | ||||
|         "CaribouLabs", | ||||
|         "Deepace", | ||||
|         "Ettus Research", | ||||
|         "Harogic", | ||||
|         "Howard Su", | ||||
|         "MicroPhase", | ||||
|         "Microtelecom", | ||||
| @@ -47,6 +50,7 @@ namespace sdrpp_credits { | ||||
|         "Nuand", | ||||
|         "RFNM", | ||||
|         "RFspace", | ||||
|         "RigExpert", | ||||
|         "RTL-SDRblog", | ||||
|         "SDRplay" | ||||
|     }; | ||||
|   | ||||
| @@ -5,113 +5,120 @@ | ||||
| #include <gui/main_window.h> | ||||
| #include <gui/style.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <gui/dialogs/dialog_box.h> | ||||
|  | ||||
| namespace sourcemenu { | ||||
|     int offsetMode = 0; | ||||
|     int sourceId = 0; | ||||
|     double customOffset = 0.0; | ||||
|     double effectiveOffset = 0.0; | ||||
|     int decimationPower = 0; | ||||
|     EventHandler<std::string> sourcesChangedHandler; | ||||
|     EventHandler<std::string> sourceUnregisterHandler; | ||||
|     OptionList<std::string, std::string> sources; | ||||
|     std::string selectedSource; | ||||
|  | ||||
|     int decimId = 0; | ||||
|     OptionList<int, int> decimations; | ||||
|  | ||||
|     bool iqCorrection = false; | ||||
|     bool invertIQ = false; | ||||
|  | ||||
|     EventHandler<std::string> sourceRegisteredHandler; | ||||
|     EventHandler<std::string> sourceUnregisterHandler; | ||||
|     EventHandler<std::string> sourceUnregisteredHandler; | ||||
|     int offsetId = 0; | ||||
|     double manualOffset = 0.0; | ||||
|     std::string selectedOffset; | ||||
|     double effectiveOffset = 0.0; | ||||
|     OptionList<std::string, double> offsets; | ||||
|     std::map<std::string, double> namedOffsets; | ||||
|  | ||||
|     std::vector<std::string> sourceNames; | ||||
|     std::string sourceNamesTxt; | ||||
|     std::string selectedSource; | ||||
|     bool showAddOffsetDialog = false; | ||||
|     char newOffsetName[1024]; | ||||
|     double newOffset = 0.0; | ||||
|  | ||||
|     bool showDelOffsetDialog = false; | ||||
|     std::string delOffsetName = ""; | ||||
|  | ||||
|     // Offset IDs | ||||
|     enum { | ||||
|         OFFSET_MODE_NONE, | ||||
|         OFFSET_MODE_CUSTOM, | ||||
|         OFFSET_MODE_SPYVERTER, | ||||
|         OFFSET_MODE_HAM_IT_UP, | ||||
|         OFFSET_MODE_MMDS_SB_1998, | ||||
|         OFFSET_MODE_DK5AV_XB, | ||||
|         OFFSET_MODE_KU_LNB_9750, | ||||
|         OFFSET_MODE_KU_LNB_10700, | ||||
|         _OFFSET_MODE_COUNT | ||||
|         OFFSET_ID_NONE, | ||||
|         OFFSET_ID_MANUAL, | ||||
|         OFFSET_ID_CUSTOM_BASE | ||||
|     }; | ||||
|  | ||||
|     const char* offsetModesTxt = "None\0" | ||||
|                                  "Custom\0" | ||||
|                                  "SpyVerter\0" | ||||
|                                  "Ham-It-Up\0" | ||||
|                                  "MMDS S-band (1998MHz)\0" | ||||
|                                  "DK5AV X-Band\0" | ||||
|                                  "Ku LNB (9750MHz)\0" | ||||
|                                  "Ku LNB (10700MHz)\0"; | ||||
|  | ||||
|     const char* decimationStages = "None\0" | ||||
|                                    "2\0" | ||||
|                                    "4\0" | ||||
|                                    "8\0" | ||||
|                                    "16\0" | ||||
|                                    "32\0" | ||||
|                                    "64\0"; | ||||
|  | ||||
|     void updateOffset() { | ||||
|         if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; } | ||||
|         else if (offsetMode == OFFSET_MODE_SPYVERTER) { | ||||
|             effectiveOffset = 120000000; | ||||
|         } // 120MHz Up-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_HAM_IT_UP) { | ||||
|             effectiveOffset = 125000000; | ||||
|         } // 125MHz Up-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_MMDS_SB_1998) { | ||||
|             effectiveOffset = -1998000000; | ||||
|         } // 1.998GHz Down-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_DK5AV_XB) { | ||||
|             effectiveOffset = -6800000000; | ||||
|         } // 6.8GHz Down-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_KU_LNB_9750) { | ||||
|             effectiveOffset = -9750000000; | ||||
|         } // 9.750GHz Down-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_KU_LNB_10700) { | ||||
|             effectiveOffset = -10700000000; | ||||
|         } // 10.7GHz Down-conversion | ||||
|         else { | ||||
|         // Compute the effective offset | ||||
|         switch (offsetId) { | ||||
|         case OFFSET_ID_NONE: | ||||
|             effectiveOffset = 0; | ||||
|             break; | ||||
|         case OFFSET_ID_MANUAL: | ||||
|             effectiveOffset = manualOffset; | ||||
|             break; | ||||
|         default: | ||||
|             effectiveOffset = namedOffsets[offsets.name(offsetId)]; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         // Apply it | ||||
|         sigpath::sourceManager.setTuningOffset(effectiveOffset); | ||||
|     } | ||||
|  | ||||
|     void selectOffsetById(int id) { | ||||
|         // Update the offset mode | ||||
|         offsetId = id; | ||||
|         selectedOffset = offsets.name(id); | ||||
|  | ||||
|         // Update the offset | ||||
|         updateOffset(); | ||||
|     } | ||||
|  | ||||
|     void selectOffsetByName(const std::string& name) { | ||||
|         // If the name doesn't exist, select 'None' | ||||
|         if (!offsets.nameExists(name)) { | ||||
|             selectOffsetById(OFFSET_ID_NONE); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Select using the ID associated with the name | ||||
|         selectOffsetById(offsets.nameId(name)); | ||||
|     } | ||||
|  | ||||
|     void refreshSources() { | ||||
|         sourceNames = sigpath::sourceManager.getSourceNames(); | ||||
|         sourceNamesTxt.clear(); | ||||
|         // Get sources | ||||
|         auto sourceNames = sigpath::sourceManager.getSourceNames(); | ||||
|  | ||||
|         // Define source options | ||||
|         sources.clear(); | ||||
|         for (auto name : sourceNames) { | ||||
|             sourceNamesTxt += name; | ||||
|             sourceNamesTxt += '\0'; | ||||
|             sources.define(name, name, name); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void selectSource(std::string name) { | ||||
|         if (sourceNames.empty()) { | ||||
|         // If there is no source, give up | ||||
|         if (sources.empty()) { | ||||
|             sourceId = 0; | ||||
|             selectedSource.clear(); | ||||
|             return; | ||||
|         } | ||||
|         auto it = std::find(sourceNames.begin(), sourceNames.end(), name); | ||||
|         if (it == sourceNames.end()) { | ||||
|             selectSource(sourceNames[0]); | ||||
|  | ||||
|         // If a source with the given name doesn't exist, select the first source instead | ||||
|         if (!sources.valueExists(name)) { | ||||
|             selectSource(sources.value(0)); | ||||
|             return; | ||||
|         } | ||||
|         sourceId = std::distance(sourceNames.begin(), it); | ||||
|         selectedSource = sourceNames[sourceId]; | ||||
|         sigpath::sourceManager.selectSource(sourceNames[sourceId]); | ||||
|  | ||||
|         // Update the GUI variables | ||||
|         sourceId = sources.valueId(name); | ||||
|         selectedSource = name; | ||||
|  | ||||
|         // Select the source module | ||||
|         sigpath::sourceManager.selectSource(name); | ||||
|     } | ||||
|  | ||||
|     void onSourceRegistered(std::string name, void* ctx) { | ||||
|     void onSourcesChanged(std::string name, void* ctx) { | ||||
|         // Update the source list | ||||
|         refreshSources(); | ||||
|  | ||||
|         if (selectedSource.empty()) { | ||||
|             sourceId = 0; | ||||
|             selectSource(sourceNames[0]); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); | ||||
|         // Reselect the current source | ||||
|         selectSource(selectedSource); | ||||
|     } | ||||
|  | ||||
|     void onSourceUnregister(std::string name, void* ctx) { | ||||
| @@ -120,60 +127,173 @@ namespace sourcemenu { | ||||
|         // TODO: Stop everything | ||||
|     } | ||||
|  | ||||
|     void onSourceUnregistered(std::string name, void* ctx) { | ||||
|         refreshSources(); | ||||
|     void reloadOffsets() { | ||||
|         // Clear list | ||||
|         offsets.clear(); | ||||
|         namedOffsets.clear(); | ||||
|  | ||||
|         if (sourceNames.empty()) { | ||||
|             selectedSource = ""; | ||||
|             return; | ||||
|         // Define special offset modes | ||||
|         offsets.define("None", OFFSET_ID_NONE); | ||||
|         offsets.define("Manual", OFFSET_ID_MANUAL); | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|  | ||||
|         // Load custom offsets | ||||
|         auto ofs = core::configManager.conf["offsets"].items(); | ||||
|         for (auto& o : ofs) { | ||||
|             namedOffsets[o.key()] = (double)o.value(); | ||||
|         } | ||||
|  | ||||
|         if (name == selectedSource) { | ||||
|             sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1); | ||||
|             selectSource(sourceNames[sourceId]); | ||||
|             return; | ||||
|         // Define custom offsets | ||||
|         for (auto& [name, offset] : namedOffsets) { | ||||
|             offsets.define(name, offsets.size()); | ||||
|         } | ||||
|  | ||||
|         sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); | ||||
|         // Release the config file | ||||
|         core::configManager.release(); | ||||
|     } | ||||
|  | ||||
|     void init() { | ||||
|         // Load offset modes | ||||
|         reloadOffsets(); | ||||
|  | ||||
|         // Define decimation values | ||||
|         decimations.define(1, "None", 1); | ||||
|         decimations.define(2, "2x", 2); | ||||
|         decimations.define(4, "4x", 4); | ||||
|         decimations.define(8, "8x", 8); | ||||
|         decimations.define(16, "16x", 16); | ||||
|         decimations.define(32, "32x", 32); | ||||
|         decimations.define(64, "64x", 64); | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|         std::string selected = core::configManager.conf["source"]; | ||||
|         customOffset = core::configManager.conf["offset"]; | ||||
|         offsetMode = core::configManager.conf["offsetMode"]; | ||||
|         decimationPower = core::configManager.conf["decimationPower"]; | ||||
|  | ||||
|         // Load other settings | ||||
|         std::string selectedSource = core::configManager.conf["source"]; | ||||
|         manualOffset = core::configManager.conf["manualOffset"]; | ||||
|         std::string selectedOffset = core::configManager.conf["selectedOffset"]; | ||||
|         iqCorrection = core::configManager.conf["iqCorrection"]; | ||||
|         invertIQ = core::configManager.conf["invertIQ"]; | ||||
|         int decimation = core::configManager.conf["decimation"]; | ||||
|         if (decimations.keyExists(decimation)) { | ||||
|             decimId = decimations.keyId(decimation); | ||||
|         } | ||||
|  | ||||
|         // Release the config file | ||||
|         core::configManager.release(); | ||||
|  | ||||
|         // Select the source module | ||||
|         refreshSources(); | ||||
|         selectSource(selectedSource); | ||||
|  | ||||
|         // Update frontend settings | ||||
|         sigpath::iqFrontEnd.setDCBlocking(iqCorrection); | ||||
|         sigpath::iqFrontEnd.setInvertIQ(invertIQ); | ||||
|         updateOffset(); | ||||
|         sigpath::iqFrontEnd.setDecimation(decimations.value(decimId)); | ||||
|         selectOffsetByName(selectedOffset); | ||||
|  | ||||
|         refreshSources(); | ||||
|         selectSource(selected); | ||||
|         sigpath::iqFrontEnd.setDecimation(1 << decimationPower); | ||||
|  | ||||
|         sourceRegisteredHandler.handler = onSourceRegistered; | ||||
|         // Register handlers | ||||
|         sourcesChangedHandler.handler = onSourcesChanged; | ||||
|         sourceUnregisterHandler.handler = onSourceUnregister; | ||||
|         sourceUnregisteredHandler.handler = onSourceUnregistered; | ||||
|         sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler); | ||||
|         sigpath::sourceManager.onSourceRegistered.bindHandler(&sourcesChangedHandler); | ||||
|         sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler); | ||||
|         sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler); | ||||
|         sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourcesChangedHandler); | ||||
|     } | ||||
|  | ||||
|         core::configManager.release(); | ||||
|     void addOffset(const std::string& name, double offset) { | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|  | ||||
|         // Define a new offset | ||||
|         core::configManager.conf["offsets"][name] = offset; | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.release(true); | ||||
|  | ||||
|         // Reload the offsets | ||||
|         reloadOffsets(); | ||||
|  | ||||
|         // Attempt to re-select the same one | ||||
|         selectOffsetByName(selectedOffset); | ||||
|     } | ||||
|  | ||||
|     void delOffset(const std::string& name) { | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|  | ||||
|         // Define a new offset | ||||
|         core::configManager.conf["offsets"].erase(name); | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.release(true); | ||||
|  | ||||
|         // Reload the offsets | ||||
|         reloadOffsets(); | ||||
|  | ||||
|         // Attempt to re-select the same one | ||||
|         selectOffsetByName(selectedOffset); | ||||
|     } | ||||
|  | ||||
|     bool addOffsetDialog() { | ||||
|         bool open = true; | ||||
|         gui::mainWindow.lockWaterfallControls = true; | ||||
|  | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         const char* id = "Add offset##sdrpp_add_offset_dialog_"; | ||||
|         ImGui::OpenPopup(id); | ||||
|  | ||||
|         if (ImGui::BeginPopup(id, ImGuiWindowFlags_NoResize)) { | ||||
|             ImGui::LeftLabel("Name"); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             ImGui::InputText("##sdrpp_add_offset_name", newOffsetName, 1023); | ||||
|  | ||||
|             ImGui::LeftLabel("Offset"); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             ImGui::InputDouble("##sdrpp_add_offset_offset", &newOffset); | ||||
|  | ||||
|             bool nameExists = offsets.nameExists(newOffsetName); | ||||
|             bool reservedName = !strcmp(newOffsetName, "None") || !strcmp(newOffsetName, "Manual"); | ||||
|             bool denyApply = !newOffsetName[0] || nameExists || reservedName; | ||||
|  | ||||
|             if (nameExists) { | ||||
|                 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "An offset with the given name already exists."); | ||||
|             } | ||||
|             else if (reservedName) { | ||||
|                 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "The given name is reserved."); | ||||
|             } | ||||
|  | ||||
|             if (denyApply) { style::beginDisabled(); } | ||||
|             if (ImGui::Button("Apply")) { | ||||
|                 addOffset(newOffsetName, newOffset); | ||||
|                 open = false; | ||||
|             } | ||||
|             if (denyApply) { style::endDisabled(); } | ||||
|             ImGui::SameLine(); | ||||
|             if (ImGui::Button("Cancel")) { | ||||
|                 open = false; | ||||
|             } | ||||
|             ImGui::EndPopup(); | ||||
|         } | ||||
|         return open; | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         float itemWidth = ImGui::GetContentRegionAvail().x; | ||||
|         float lineHeight = ImGui::GetTextLineHeightWithSpacing(); | ||||
|         float spacing = lineHeight - ImGui::GetTextLineHeight(); | ||||
|         bool running = gui::mainWindow.sdrIsRunning(); | ||||
|  | ||||
|         if (running) { style::beginDisabled(); } | ||||
|  | ||||
|         ImGui::SetNextItemWidth(itemWidth); | ||||
|         if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) { | ||||
|             selectSource(sourceNames[sourceId]); | ||||
|         if (ImGui::Combo("##source", &sourceId, sources.txt)) { | ||||
|             std::string newSource = sources.value(sourceId); | ||||
|             selectSource(newSource); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["source"] = sourceNames[sourceId]; | ||||
|             core::configManager.conf["source"] = newSource; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
| @@ -196,21 +316,45 @@ namespace sourcemenu { | ||||
|         } | ||||
|  | ||||
|         ImGui::LeftLabel("Offset mode"); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) { | ||||
|             updateOffset(); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX() - 2.0f*(lineHeight + 1.5f*spacing)); | ||||
|         if (ImGui::Combo("##_sdrpp_offset", &offsetId, offsets.txt)) { | ||||
|             selectOffsetById(offsetId); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["offsetMode"] = offsetMode; | ||||
|             core::configManager.conf["selectedOffset"] = offsets.key(offsetId); | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing); | ||||
|         if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::BeginDisabled(); } | ||||
|         if (ImGui::Button("-##_sdrpp_offset_del_", ImVec2(lineHeight + 0.5f*spacing, 0))) { | ||||
|             delOffsetName = selectedOffset; | ||||
|             showDelOffsetDialog = true; | ||||
|         } | ||||
|         if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::EndDisabled(); } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing); | ||||
|         if (ImGui::Button("+##_sdrpp_offset_add_", ImVec2(lineHeight + 0.5f*spacing, 0))) { | ||||
|             strcpy(newOffsetName, "New Offset"); | ||||
|             showAddOffsetDialog = true; | ||||
|         } | ||||
|  | ||||
|         // Offset delete confirmation | ||||
|         if (ImGui::GenericDialog("sdrpp_del_offset_confirm", showDelOffsetDialog, GENERIC_DIALOG_BUTTONS_YES_NO, []() { | ||||
|             ImGui::Text("Deleting offset named \"%s\". Are you sure?", delOffsetName.c_str()); | ||||
|         }) == GENERIC_DIALOG_BUTTON_YES) { | ||||
|             delOffset(delOffsetName); | ||||
|         } | ||||
|  | ||||
|         // Offset add diaglog | ||||
|         if (showAddOffsetDialog) { showAddOffsetDialog = addOffsetDialog(); } | ||||
|  | ||||
|         ImGui::LeftLabel("Offset"); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (offsetMode == OFFSET_MODE_CUSTOM) { | ||||
|             if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) { | ||||
|         ImGui::FillWidth(); | ||||
|         if (offsetId == OFFSET_ID_MANUAL) { | ||||
|             if (ImGui::InputDouble("##freq_offset", &manualOffset, 1.0, 100.0)) { | ||||
|                 updateOffset(); | ||||
|                 core::configManager.acquire(); | ||||
|                 core::configManager.conf["offset"] = customOffset; | ||||
|                 core::configManager.conf["manualOffset"] = manualOffset; | ||||
|                 core::configManager.release(true); | ||||
|             } | ||||
|         } | ||||
| @@ -222,11 +366,11 @@ namespace sourcemenu { | ||||
|  | ||||
|         if (running) { style::beginDisabled(); } | ||||
|         ImGui::LeftLabel("Decimation"); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) { | ||||
|             sigpath::iqFrontEnd.setDecimation(1 << decimationPower); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo("##source_decim", &decimId, decimations.txt)) { | ||||
|             sigpath::iqFrontEnd.setDecimation(decimations.value(decimId)); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["decimationPower"] = decimationPower; | ||||
|             core::configManager.conf["decimation"] = decimations.key(decimId); | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         if (running) { style::endDisabled(); } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include <gui/style.h> | ||||
| #include <gui/gui.h> | ||||
| #include <backend.h> | ||||
| #include <utils/hrfreq.h> | ||||
|  | ||||
| #ifndef IMGUI_DEFINE_MATH_OPERATORS | ||||
| #define IMGUI_DEFINE_MATH_OPERATORS | ||||
| @@ -90,6 +91,7 @@ void FrequencySelect::moveCursorToDigit(int i) { | ||||
|  | ||||
| void FrequencySelect::draw() { | ||||
|     auto window = ImGui::GetCurrentWindow(); | ||||
|     auto io = ImGui::GetIO(); | ||||
|     widgetPos = ImGui::GetWindowContentRegionMin(); | ||||
|     ImVec2 cursorPos = ImGui::GetCursorPos(); | ||||
|     widgetPos.x += window->Pos.x + cursorPos.x; | ||||
| @@ -132,7 +134,7 @@ void FrequencySelect::draw() { | ||||
|         ImVec2 mousePos = ImGui::GetMousePos(); | ||||
|         bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); | ||||
|         bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right); | ||||
|         int mw = ImGui::GetIO().MouseWheel; | ||||
|         int mw = io.MouseWheel; | ||||
|         bool onDigit = false; | ||||
|         bool hovered = false; | ||||
|  | ||||
| @@ -174,7 +176,7 @@ void FrequencySelect::draw() { | ||||
|                     moveCursorToDigit(i + 1); | ||||
|                 } | ||||
|  | ||||
|                 auto chars = ImGui::GetIO().InputQueueCharacters; | ||||
|                 auto chars = io.InputQueueCharacters; | ||||
|  | ||||
|                 // For each keyboard characters, type it | ||||
|                 for (int j = 0; j < chars.Size; j++) { | ||||
| @@ -194,6 +196,34 @@ void FrequencySelect::draw() { | ||||
|             } | ||||
|         } | ||||
|         digitHovered = hovered; | ||||
|  | ||||
|         if (isInArea(mousePos, digitTopMins[0], digitBottomMaxs[11])) { | ||||
|             bool shortcutKey = io.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl); | ||||
|             bool ctrlOnly = (io.KeyMods == ImGuiKeyModFlags_Ctrl); | ||||
|             bool shiftOnly = (io.KeyMods == ImGuiKeyModFlags_Shift); | ||||
|             bool copy  = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_C)) || (ctrlOnly  && ImGui::IsKeyPressed(ImGuiKey_Insert))); | ||||
|             bool paste = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_V)) || (shiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert))); | ||||
|             if (copy) { | ||||
|                 // Convert the freqency to a string | ||||
|                 std::string freqStr = hrfreq::toString(frequency); | ||||
|  | ||||
|                 // Write it to the clipboard | ||||
|                 ImGui::SetClipboardText(freqStr.c_str()); | ||||
|             } | ||||
|             if (paste) { | ||||
|                 // Attempt to parse the clipboard as a number | ||||
|                 const char* clip = ImGui::GetClipboardText(); | ||||
|  | ||||
|                 // If the clipboard is not empty, attempt to parse it | ||||
|                 if (clip) { | ||||
|                     double newFreq; | ||||
|                     if (hrfreq::fromString(clip, newFreq)) { | ||||
|                         setFrequency(abs(newFreq)); | ||||
|                         frequencyChanged = true; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     uint64_t freq = 0; | ||||
|   | ||||
							
								
								
									
										120
									
								
								core/src/utils/hrfreq.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								core/src/utils/hrfreq.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| #include "hrfreq.h" | ||||
| #include <utils/flog.h> | ||||
|  | ||||
| namespace hrfreq { | ||||
|  | ||||
|  | ||||
|     std::string toString(double freq) { | ||||
|         // Determine the scale | ||||
|         int maxDecimals = 0; | ||||
|         const char* suffix = "Hz"; | ||||
|         if (freq >= 1e9) { | ||||
|             freq /= 1e9; | ||||
|             maxDecimals = 9; | ||||
|             suffix = "GHz"; | ||||
|         } | ||||
|         else if (freq >= 1e6) { | ||||
|             freq /= 1e6; | ||||
|             maxDecimals = 6; | ||||
|             suffix = "MHz"; | ||||
|         } | ||||
|         else if (freq >= 1e3) { | ||||
|             freq /= 1e3; | ||||
|             maxDecimals = 3; | ||||
|             suffix = "KHz"; | ||||
|         } | ||||
|  | ||||
|         // Convert to string (TODO: Not sure if limiting the decimals rounds) | ||||
|         char numBuf[128]; | ||||
|         int numLen = sprintf(numBuf, "%0.*lf", maxDecimals, freq); | ||||
|  | ||||
|         // If there is a decimal point, remove the useless zeros | ||||
|         if (maxDecimals) { | ||||
|             for (int i = numLen-1; i >= 0; i--) { | ||||
|                 bool dot = (numBuf[i] == '.'); | ||||
|                 if (numBuf[i] != '0' && !dot) { break; } | ||||
|                 numBuf[i] = 0; | ||||
|                 if (dot) { break; } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Concat the suffix | ||||
|         char finalBuf[128]; | ||||
|         sprintf(finalBuf, "%s%s", numBuf, suffix); | ||||
|  | ||||
|         // Return the final string | ||||
|         return finalBuf; | ||||
|     } | ||||
|  | ||||
|     bool isNumeric(char c) { | ||||
|         return std::isdigit(c) || c == '+' || c == '-' || c == '.' || c == ','; | ||||
|     } | ||||
|  | ||||
|     bool fromString(const std::string& str, double& freq) { | ||||
|         // Skip non-numeric characters | ||||
|         int i = 0; | ||||
|         char c; | ||||
|         for (; i < str.size(); i++) { | ||||
|             if (isNumeric(str[i])) { break; } | ||||
|         } | ||||
|  | ||||
|         // Extract the numeric part | ||||
|         std::string numeric; | ||||
|         for (; i < str.size(); i++) { | ||||
|             // Get the character | ||||
|             c = str[i]; | ||||
|  | ||||
|             // If it's a letter, stop | ||||
|             if (std::isalpha(c)) { break; } | ||||
|  | ||||
|             // If isn't numeric, skip it | ||||
|             if (!isNumeric(c)) { continue; } | ||||
|  | ||||
|             // If it's a comma, skip it for now. This enforces a dot as a decimal point | ||||
|             if (c == ',') { continue; } | ||||
|  | ||||
|             // Add the character to the numeric string | ||||
|             numeric += c; | ||||
|         } | ||||
|  | ||||
|         // Attempt to parse the numeric part | ||||
|         double num; | ||||
|         try { | ||||
|             num = std::stod(numeric); | ||||
|         } | ||||
|         catch (const std::exception& e) { | ||||
|             flog::error("Failed to parse numeric part: '{}'", numeric); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // If no more text is available, assume the numeric part gives a frequency in Hz | ||||
|         if (i == str.size()) { | ||||
|             flog::warn("No unit given, assuming it's Hz"); | ||||
|             freq = num; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // Scale the numeric value depending on the first scale character | ||||
|         char scale = std::toupper(str[i]); | ||||
|         switch (scale) { | ||||
|         case 'G': | ||||
|             num *= 1e9; | ||||
|             break; | ||||
|         case 'M': | ||||
|             num *= 1e6; | ||||
|             break; | ||||
|         case 'K': | ||||
|             num *= 1e3; | ||||
|             break; | ||||
|         case 'H': | ||||
|             break; | ||||
|         default: | ||||
|             flog::warn("Unknown frequency scale: '{}'", scale); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         // Return the frequency | ||||
|         freq = num; | ||||
|         return true; // TODO | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								core/src/utils/hrfreq.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								core/src/utils/hrfreq.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
|  | ||||
| namespace hrfreq { | ||||
|     /** | ||||
|      * Convert a frequency to a human-readable string. | ||||
|      * @param freq Frequency in Hz. | ||||
|      * @return Human-readable representation of the frequency. | ||||
|     */ | ||||
|     std::string toString(double freq); | ||||
|  | ||||
|     /** | ||||
|      * Convert a human-readable representation of a frequency to a frequency value. | ||||
|      * @param str String containing the human-readable frequency. | ||||
|      * @param freq Value to write the decoded frequency to. | ||||
|      * @return True on success, false otherwise. | ||||
|     */ | ||||
|     bool fromString(const std::string& str, double& freq); | ||||
| } | ||||
| @@ -1,3 +1,3 @@ | ||||
| #pragma once | ||||
|  | ||||
| #define VERSION_STR "1.2.0" | ||||
| #define VERSION_STR "1.2.1" | ||||
							
								
								
									
										37
									
								
								decoder_modules/dab_decoder/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								decoder_modules/dab_decoder/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(dab_decoder) | ||||
|  | ||||
| file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| target_include_directories(dab_decoder PRIVATE "src/") | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_include_directories(dab_decoder PRIVATE "C:/Program Files/codec2/include/") | ||||
|     target_link_directories(dab_decoder PRIVATE "C:/Program Files/codec2/lib") | ||||
|  | ||||
|     target_link_libraries(dab_decoder PRIVATE libcodec2) | ||||
| elseif (ANDROID) | ||||
|     target_include_directories(dab_decoder PUBLIC | ||||
|         /sdr-kit/${ANDROID_ABI}/include/codec2 | ||||
|     ) | ||||
|  | ||||
|     target_link_libraries(dab_decoder PUBLIC | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/libcodec2.so | ||||
|     ) | ||||
| else () | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBCODEC2 REQUIRED codec2) | ||||
|  | ||||
|     target_include_directories(dab_decoder PRIVATE ${LIBCODEC2_INCLUDE_DIRS}) | ||||
|     target_link_directories(dab_decoder PRIVATE ${LIBCODEC2_LIBRARY_DIRS}) | ||||
|     target_link_libraries(dab_decoder PRIVATE ${LIBCODEC2_LIBRARIES}) | ||||
|  | ||||
|     # Include it because for some reason pkgconfig doesn't look here? | ||||
|     if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") | ||||
|         target_include_directories(dab_decoder PRIVATE "/usr/local/include") | ||||
|     endif() | ||||
| endif () | ||||
							
								
								
									
										280
									
								
								decoder_modules/dab_decoder/src/dab_dsp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								decoder_modules/dab_decoder/src/dab_dsp.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| #pragma once | ||||
| #include <dsp/processor.h> | ||||
| #include <utils/flog.h> | ||||
| #include <fftw3.h> | ||||
| #include "dab_phase_sym.h" | ||||
|  | ||||
| namespace dab { | ||||
|     class CyclicSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> { | ||||
|         using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>; | ||||
|     public: | ||||
|         CyclicSync() {} | ||||
|  | ||||
|         // TODO: The default AGC rate is probably way too fast, plot out the avgCorr to see how much it moves | ||||
|         CyclicSync(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { init(in, symbolLength, cyclicPrefixLength, samplerate, agcRate); } | ||||
|  | ||||
|         void init(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { | ||||
|             // Computer the number of samples for the symbol and its cyclic prefix | ||||
|             symbolSamps = round(samplerate * symbolLength); | ||||
|             prefixSamps = round(samplerate * cyclicPrefixLength); | ||||
|  | ||||
|             // Allocate and clear the delay buffer | ||||
|             delayBuf = dsp::buffer::alloc<dsp::complex_t>(STREAM_BUFFER_SIZE + 64000); | ||||
|             dsp::buffer::clear(delayBuf, symbolSamps); | ||||
|  | ||||
|             // Allocate and clear the history buffer | ||||
|             histBuf = dsp::buffer::alloc<dsp::complex_t>(prefixSamps); | ||||
|             dsp::buffer::clear(histBuf, prefixSamps); | ||||
|  | ||||
|             // Compute the delay input addresses | ||||
|             delayBufInput = &delayBuf[symbolSamps]; | ||||
|  | ||||
|             // Compute the correlation AGC configuration | ||||
|             this->agcRate = agcRate; | ||||
|             agcRateInv = 1.0f - agcRate; | ||||
|              | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|              | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             // Copy the data into the normal delay buffer | ||||
|             memcpy(delayBufInput, base_type::_in->readBuf, count * sizeof(dsp::complex_t)); | ||||
|  | ||||
|             // Flush the input stream | ||||
|             base_type::_in->flush(); | ||||
|  | ||||
|             // Do cross-correlation | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 // Get the current history slot | ||||
|                 dsp::complex_t* slot = &histBuf[histId++]; | ||||
|  | ||||
|                 // Wrap around the history slot index (TODO: Check that the history buffer's length is correct) | ||||
|                 histId %= prefixSamps; | ||||
|  | ||||
|                 // Kick out last value from the correlation | ||||
|                 corr -= *slot; | ||||
|  | ||||
|                 // Save input value and compute the new prodct | ||||
|                 dsp::complex_t val = delayBuf[i]; | ||||
|                 dsp::complex_t prod = val.conj()*delayBuf[i+symbolSamps]; | ||||
|  | ||||
|                 // Add the new value to the correlation | ||||
|                 *slot = prod; | ||||
|  | ||||
|                 // Add the new value to the history buffer | ||||
|                 corr += prod; | ||||
|  | ||||
|                 // Compute sample amplitude | ||||
|                 float rcorr = corr.amplitude(); | ||||
|  | ||||
|                 // If a high enough peak is reached, reset the symbol counter | ||||
|                 if (rcorr > avgCorr && rcorr > peakCorr) { // Note keeping an average level might not be needed | ||||
|                     peakCorr = rcorr; | ||||
|                     peakLCorr = lastCorr; | ||||
|                     samplesSincePeak = 0; | ||||
|                 } | ||||
|  | ||||
|                 // If this is the sample right after the peak, save it | ||||
|                 if (samplesSincePeak == 1) { | ||||
|                     peakRCorr = rcorr; | ||||
|                 } | ||||
|  | ||||
|                 // Write the sample to the output | ||||
|                 out.writeBuf[samplesSincePeak++] = val; | ||||
|  | ||||
|                 // If the end of the symbol is reached, send it off | ||||
|                 if (samplesSincePeak >= symbolSamps) { | ||||
|                     if (!out.swap(symbolSamps)) { | ||||
|                         return -1; | ||||
|                     } | ||||
|                     samplesSincePeak = 0; | ||||
|                     peakCorr = 0; | ||||
|                 } | ||||
|  | ||||
|                 // Update the average correlation | ||||
|                 lastCorr = rcorr; | ||||
|  | ||||
|                 // Update the average correlation value | ||||
|                 avgCorr = agcRate*rcorr + agcRateInv*avgCorr; | ||||
|             } | ||||
|  | ||||
|             // Move unused data | ||||
|             memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t)); | ||||
|  | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         int symbolSamps; | ||||
|         int prefixSamps; | ||||
|  | ||||
|         int histId = 0; | ||||
|         dsp::complex_t* histBuf; | ||||
|  | ||||
|         dsp::complex_t* delayBuf; | ||||
|         dsp::complex_t* delayBufInput; | ||||
|  | ||||
|         dsp::complex_t corr = { 0.0f, 0.0f }; | ||||
|  | ||||
|         int samplesSincePeak = 0; | ||||
|         float lastCorr = 0.0f; | ||||
|         float peakCorr = 0.0f; | ||||
|         float peakLCorr = 0.0f; | ||||
|         float peakRCorr = 0.0f; | ||||
|  | ||||
|         // Note only required for DAB | ||||
|         float avgCorr = 0.0f; | ||||
|         float agcRate; | ||||
|         float agcRateInv; | ||||
|     }; | ||||
|  | ||||
|     class FrameFreqSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> { | ||||
|         using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>; | ||||
|     public: | ||||
|         FrameFreqSync() {} | ||||
|  | ||||
|         FrameFreqSync(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { init(in, agcRate); } | ||||
|  | ||||
|         void init(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { | ||||
|             // Allocate buffers | ||||
|             amps = dsp::buffer::alloc<float>(2048); | ||||
|             conjRef = dsp::buffer::alloc<dsp::complex_t>(2048); | ||||
|             corrIn = (dsp::complex_t*)fftwf_alloc_complex(2048); | ||||
|             corrOut = (dsp::complex_t*)fftwf_alloc_complex(2048); | ||||
|  | ||||
|             // Copy the phase reference | ||||
|             memcpy(conjRef, DAB_PHASE_SYM_CONJ, 2048 * sizeof(dsp::complex_t)); | ||||
|  | ||||
|             // Plan the FFT computation | ||||
|             plan = fftwf_plan_dft_1d(2048, (fftwf_complex*)corrIn, (fftwf_complex*)corrOut, FFTW_FORWARD, FFTW_ESTIMATE); | ||||
|  | ||||
|             // Compute the correlation AGC configuration | ||||
|             this->agcRate = agcRate; | ||||
|             agcRateInv = 1.0f - agcRate; | ||||
|              | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|              | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             // Apply frequency shift | ||||
|             lv_32fc_t phase = lv_cmake(1.0f, 0.0f); | ||||
|             lv_32fc_t phaseDelta = lv_cmake(cos(offset), sin(offset)); | ||||
| #if VOLK_VERSION >= 030100 | ||||
|             volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count); | ||||
| #else | ||||
|             volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count); | ||||
| #endif | ||||
|  | ||||
|             // Compute the amplitude amplitude of all samples | ||||
|             volk_32fc_magnitude_32f(amps, (lv_32fc_t*)_in->readBuf, 2048); | ||||
|  | ||||
|             // Compute the average signal level by adding up all values | ||||
|             float level = 0.0f; | ||||
|             volk_32f_accumulator_s32f(&level, amps, 2048); | ||||
|  | ||||
|             // Detect a frame sync condition | ||||
|             if (level < avgLvl * 0.5f) { | ||||
|                 // Reset symbol counter | ||||
|                 sym = 1; | ||||
|  | ||||
|                 // Update the average level | ||||
|                 avgLvl = agcRate*level + agcRateInv*avgLvl; | ||||
|  | ||||
|                 // Flush the input stream and return | ||||
|                 base_type::_in->flush(); | ||||
|                 return count; | ||||
|             } | ||||
|  | ||||
|             // Update the average level | ||||
|             avgLvl = agcRate*level + agcRateInv*avgLvl; | ||||
|  | ||||
|             // Handle phase reference | ||||
|             if (sym == 1) { | ||||
|                 // Output the symbols (DEBUG ONLY) | ||||
|                 memcpy(corrIn, _in->readBuf, 2048 * sizeof(dsp::complex_t)); | ||||
|                 fftwf_execute(plan); | ||||
|                 volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048); | ||||
|                 int outCount = 0; | ||||
|                 dsp::complex_t pi4 = { cos(3.1415926535*0.25), sin(3.1415926535*0.25) }; | ||||
|                 for (int i = -767; i < 768; i++) { | ||||
|                     if (!i) { continue; } | ||||
|                     int cid0 = ((i-1) >= 0) ? (i-1) : 2048+(i-1); | ||||
|                     int cid1 = (i >= 0) ? i : 2048+i;; | ||||
|                     out.writeBuf[outCount++] = pi4 * (corrOut[cid1] * corrOut[cid0].conj()) * (1.0f/(amps[cid0]*amps[cid0])); | ||||
|                 } | ||||
|                 out.swap(outCount); | ||||
|  | ||||
|                 // Multiply the samples with the conjugated phase reference signal | ||||
|                 volk_32fc_x2_multiply_32fc((lv_32fc_t*)corrIn, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)conjRef, 2048); | ||||
|              | ||||
|                 // Compute the FFT of the product | ||||
|                 fftwf_execute(plan); | ||||
|  | ||||
|                 // Compute the amplitude of the bins | ||||
|                 volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048); | ||||
|  | ||||
|                 // Locate highest power bin | ||||
|                 uint32_t peakId; | ||||
|                 volk_32f_index_max_32u(&peakId, amps, 2048); | ||||
|  | ||||
|                 // Obtain the value of the bins next to the peak | ||||
|                 float peakL = amps[(peakId + 2047) % 2048]; | ||||
|                 float peakR = amps[(peakId + 1) % 2048]; | ||||
|  | ||||
|                 // Compute the integer frequency offset | ||||
|                 float offInt = (peakId < 1024) ? (float)peakId : ((float)peakId - 2048.0f); | ||||
|  | ||||
|                 // Compute the frequency offset in rad/samp | ||||
|                 float off = 3.1415926535f * (offInt + ((peakR - peakL) / (peakR + peakL))) * (1.0f / 1024.0f); | ||||
|  | ||||
|                 // Run control loop | ||||
|                 offset -= 0.1f*off; | ||||
|                 flog::debug("Offset: {} Hz, Error: {} Hz, Avg Level: {}", offset * (0.5f/3.1415926535f)*2.048e6, off * (0.5f/3.1415926535f)*2.048e6, avgLvl); | ||||
|             } | ||||
|  | ||||
|             // Increment the symbol counter | ||||
|             sym++; | ||||
|  | ||||
|             // Flush the input stream and return | ||||
|             base_type::_in->flush(); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         fftwf_plan plan; | ||||
|  | ||||
|         float* amps; | ||||
|         dsp::complex_t* conjRef; | ||||
|         dsp::complex_t* corrIn; | ||||
|         dsp::complex_t* corrOut; | ||||
|  | ||||
|         int sym; | ||||
|         float offset = 0.0f; | ||||
|  | ||||
|         float avgLvl = 0.0f; | ||||
|         float agcRate; | ||||
|         float agcRateInv; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										2053
									
								
								decoder_modules/dab_decoder/src/dab_phase_sym.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2053
									
								
								decoder_modules/dab_decoder/src/dab_phase_sym.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										163
									
								
								decoder_modules/dab_decoder/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								decoder_modules/dab_decoder/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| #include <imgui.h> | ||||
| #include <config.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <module.h> | ||||
| #include <filesystem> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/buffer/reshaper.h> | ||||
| #include <dsp/multirate/rational_resampler.h> | ||||
| #include <dsp/sink/handler_sink.h> | ||||
| #include <fstream> | ||||
| #include <chrono> | ||||
| #include "dab_dsp.h" | ||||
| #include <gui/widgets/constellation_diagram.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "dab_decoder", | ||||
|     /* Description:     */ "DAB/DAB+ Decoder for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| #define INPUT_SAMPLE_RATE   2.048e6 | ||||
| #define VFO_BANDWIDTH       1.6e6 | ||||
|  | ||||
| class M17DecoderModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     M17DecoderModule(std::string name)  { | ||||
|         this->name = name; | ||||
|  | ||||
|         file = std::ofstream("sync4.f32", std::ios::out | std::ios::binary); | ||||
|  | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|          | ||||
|         config.release(true); | ||||
|  | ||||
|         // Initialize VFO | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true); | ||||
|         vfo->setSnapInterval(250); | ||||
|  | ||||
|         // Initialize DSP here | ||||
|         csync.init(vfo->output, 1e-3, 246e-6, INPUT_SAMPLE_RATE); | ||||
|         ffsync.init(&csync.out); | ||||
|         ns.init(&ffsync.out, handler, this); | ||||
|  | ||||
|         // Start DSO Here | ||||
|         csync.start(); | ||||
|         ffsync.start(); | ||||
|         ns.start(); | ||||
|  | ||||
|         gui::menu.registerEntry(name, menuHandler, this, this); | ||||
|     } | ||||
|  | ||||
|     ~M17DecoderModule() { | ||||
|         gui::menu.removeEntry(name); | ||||
|         // Stop DSP Here | ||||
|         if (enabled) { | ||||
|             csync.stop(); | ||||
|             ffsync.stop(); | ||||
|             ns.stop(); | ||||
|             sigpath::vfoManager.deleteVFO(vfo); | ||||
|         } | ||||
|  | ||||
|         sigpath::sinkManager.unregisterStream(name); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         double bw = gui::waterfall.getBandwidth(); | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true); | ||||
|         vfo->setSnapInterval(250); | ||||
|  | ||||
|         // Set Input of demod here | ||||
|         csync.setInput(vfo->output); | ||||
|  | ||||
|         // Start DSP here | ||||
|         csync.start(); | ||||
|         ffsync.start(); | ||||
|         ns.start(); | ||||
|  | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         // Stop DSP here | ||||
|         csync.stop(); | ||||
|         ffsync.stop(); | ||||
|         ns.stop(); | ||||
|  | ||||
|         sigpath::vfoManager.deleteVFO(vfo); | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static void menuHandler(void* ctx) { | ||||
|         M17DecoderModule* _this = (M17DecoderModule*)ctx; | ||||
|  | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         if (!_this->enabled) { style::beginDisabled(); } | ||||
|  | ||||
|         _this->constDiagram.draw(); | ||||
|  | ||||
|         if (!_this->enabled) { style::endDisabled(); } | ||||
|     } | ||||
|  | ||||
|     std::ofstream file; | ||||
|  | ||||
|     static void handler(dsp::complex_t* data, int count, void* ctx) { | ||||
|         M17DecoderModule* _this = (M17DecoderModule*)ctx; | ||||
|         //_this->file.write((char*)data, count * sizeof(dsp::complex_t)); | ||||
|  | ||||
|         dsp::complex_t* buf = _this->constDiagram.acquireBuffer(); | ||||
|         memcpy(buf, data, 1024 * sizeof(dsp::complex_t)); | ||||
|         _this->constDiagram.releaseBuffer(); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|  | ||||
|     dab::CyclicSync csync; | ||||
|     dab::FrameFreqSync ffsync; | ||||
|     dsp::sink::Handler<dsp::complex_t> ns; | ||||
|  | ||||
|     ImGui::ConstellationDiagram constDiagram; | ||||
|  | ||||
|     // DSP Chain | ||||
|     VFOManager::VFO* vfo; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Create default recording directory | ||||
|     json def = json({}); | ||||
|     config.setPath(core::args["root"].s() + "/dab_decoder_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new M17DecoderModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (M17DecoderModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
							
								
								
									
										34
									
								
								decoder_modules/dab_decoder/src/optimized_algo.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								decoder_modules/dab_decoder/src/optimized_algo.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| 0123456789 | ||||
| ---  --- | ||||
|  | ||||
| 0*4 | ||||
| 1*5 | ||||
| 2*6 | ||||
|  | ||||
| 1*5 | ||||
| 2*6 = L + 3*7 - 0*4 | ||||
| 3*7 | ||||
|  | ||||
| 2*6 | ||||
| 3*7 = L + 4*8 - 1*5 | ||||
| 4*8 | ||||
|  | ||||
| 3*7 | ||||
| 4*8 = L + 5*9 - 2*6 | ||||
| 5*9 | ||||
|  | ||||
|  | ||||
|  | ||||
| 0*5 | ||||
| 1*6 | ||||
| 2*7 | ||||
|  | ||||
| 1*6 | ||||
| 2*7 | ||||
| 3*8 | ||||
|  | ||||
| 2*7 | ||||
| 3*8 | ||||
| 4*9 | ||||
|  | ||||
| => Use same technique to cache the interpolation results | ||||
| @@ -23,9 +23,9 @@ SDRPP_MOD_INFO{ | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| #define INPUT_BANDWIDTH     138e3 | ||||
| #define INPUT_SAMPLE_RATE   250e3 | ||||
| #define INPUT_BAUDRATE      125e3 | ||||
| #define INPUT_BANDWIDTH     600e3 | ||||
| #define INPUT_SAMPLE_RATE   1000e3 | ||||
| #define INPUT_BAUDRATE      500e3 | ||||
|  | ||||
| #define SYMBOL_DIAG_RATE    30 | ||||
| #define SYMBOL_DIAG_COUNT   1024 | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
| #include "dsp/processor.h" | ||||
|  | ||||
| extern "C" { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
| #include "dsp/processor.h" | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| namespace ryfi { | ||||
|     // Synchronization word. | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| namespace ryfi { | ||||
|     /** | ||||
|   | ||||
| @@ -72,6 +72,7 @@ namespace ryfi { | ||||
|         uint8_t* pktBuffer = new uint8_t[Packet::MAX_CONTENT_SIZE]; | ||||
|         int pktExpected = 0; | ||||
|         int pktRead = 0; | ||||
|         int valid = 0; | ||||
|  | ||||
|         while (true) { | ||||
|             // Read a frame | ||||
| @@ -80,6 +81,7 @@ namespace ryfi { | ||||
|  | ||||
|             // Deserialize the frame | ||||
|             Frame::deserialize(rs.out.readBuf, frame); | ||||
|             valid++; | ||||
|  | ||||
|             // Flush the stream | ||||
|             rs.out.flush(); | ||||
| @@ -93,11 +95,12 @@ namespace ryfi { | ||||
|             // If the frames aren't consecutive | ||||
|             int frameRead = 0; | ||||
|             if (frame.counter != expectedCounter) { | ||||
|                 flog::warn("Lost at least {} frames", ((int)frame.counter - (int)expectedCounter + 0x10000) % 0x10000); | ||||
|                 flog::warn("Lost at least {} frames after {} valid frames", ((int)frame.counter - (int)expectedCounter + 0x10000) % 0x10000, valid); | ||||
|  | ||||
|                 // Cancel the partial packet if there was one | ||||
|                 pktExpected = 0; | ||||
|                 pktRead = 0; | ||||
|                 valid = 1; | ||||
|  | ||||
|                 // If this frame is not an idle frame or continuation frame | ||||
|                 if (frame.firstPacket != PKT_OFFS_NONE) { | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
| #include "dsp/processor.h" | ||||
|  | ||||
| extern "C" { | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| FROM fedora:40 | ||||
| ENV DEBIAN_FRONTEND=noninteractive  | ||||
| COPY do_build.sh /root | ||||
| RUN chmod +x /root/do_build.sh | ||||
| @@ -1,31 +0,0 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
| cd /root | ||||
|  | ||||
| # Install dependencies and tools | ||||
| dnf update -y | ||||
| dnf install -y make gcc g++ cmake git wget p7zip fftw-devel glfw-devel volk-devel libzstd-devel codec2-devel airspyone_host-devel rtaudio-devel \ | ||||
|             hackrf-devel rtl-sdr-devel portaudio-devel spdlog-devel libusbg-devel | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7za x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7za x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_AIRSPYHF_SOURCE=OFF -DOPT_BUILD_PLUTOSDR_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON | ||||
| make VERBOSE=1 -j2 | ||||
| @@ -61,6 +61,16 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Fix missing .pc file for codec2 | ||||
| echo 'prefix=/usr/' >> /usr/share/pkgconfig/codec2.pc | ||||
| echo 'libdir=/usr/include/x86_64-linux-gnu/' >> /usr/share/pkgconfig/codec2.pc | ||||
| @@ -76,7 +86,7 @@ echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| # Generate package | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -35,10 +35,20 @@ make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -35,9 +35,11 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/airspyhf_source/airspyhf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/bladerf_source/bladerf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/file_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/fobossdr_source/fobossdr_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/network_source/network_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfnm_source/rfnm_source.dylib | ||||
|   | ||||
| @@ -24,6 +24,9 @@ cp 'C:/Program Files/PothosSDR/bin/bladeRF.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/file_source/Release/file_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/fobossdr_source/Release/fobossdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/RigExpert/Fobos/bin/fobos.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/hackrf_source/Release/hackrf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/hackrf.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| @@ -32,6 +35,8 @@ cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windo | ||||
| cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/network_source/Release/network_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/ | ||||
|  | ||||
|   | ||||
| @@ -168,10 +168,9 @@ public: | ||||
|         writer.setSamplerate(samplerate); | ||||
|  | ||||
|         // Open file | ||||
|         std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband"; | ||||
|         std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : ""; | ||||
|         std::string extension = ".wav"; | ||||
|         std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension); | ||||
|         std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, recMode, vfoName) + extension); | ||||
|         if (!writer.open(expandedPath)) { | ||||
|             flog::error("Failed to open file for recording: {0}", expandedPath); | ||||
|             return; | ||||
| @@ -452,7 +451,7 @@ private: | ||||
|         { RADIO_IFACE_MODE_RAW, "RAW" } | ||||
|     }; | ||||
|  | ||||
|     std::string genFileName(std::string templ, std::string type, std::string name) { | ||||
|     std::string genFileName(std::string templ, int mode, std::string name) { | ||||
|         // Get data | ||||
|         time_t now = time(0); | ||||
|         tm* ltm = localtime(&now); | ||||
| @@ -462,6 +461,9 @@ private: | ||||
|             freq += gui::waterfall.vfos[name]->generalOffset; | ||||
|         } | ||||
|  | ||||
|         // Select the recording type string | ||||
|         std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband"; | ||||
|  | ||||
|         // Format to string | ||||
|         char freqStr[128]; | ||||
|         char hourStr[128]; | ||||
| @@ -470,7 +472,7 @@ private: | ||||
|         char dayStr[128]; | ||||
|         char monStr[128]; | ||||
|         char yearStr[128]; | ||||
|         const char* modeStr = "Unknown"; | ||||
|         const char* modeStr = (recMode == RECORDER_MODE_AUDIO) ? "Unknown" : "IQ"; | ||||
|         sprintf(freqStr, "%.0lfHz", freq); | ||||
|         sprintf(hourStr, "%02d", ltm->tm_hour); | ||||
|         sprintf(minStr, "%02d", ltm->tm_min); | ||||
|   | ||||
							
								
								
									
										19
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								readme.md
									
									
									
									
									
								
							| @@ -324,11 +324,13 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | audio_source         | Working    | rtaudio           | OPT_BUILD_AUDIO_SOURCE         | ✅              | ✅                     | ✅                         | | ||||
| | bladerf_source       | Working    | libbladeRF        | OPT_BUILD_BLADERF_SOURCE       | ⛔              | ✅ (not Debian Buster) | ✅                         | | ||||
| | file_source          | Working    | -                 | OPT_BUILD_FILE_SOURCE          | ✅              | ✅                     | ✅                         | | ||||
| | fobossdr_source      | Working    | libfobos          | OPT_BUILD_FOBOSSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | hackrf_source        | Working    | libhackrf         | OPT_BUILD_HACKRF_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | harogic_source       | Unfinished | htra_api          | OPT_BUILD_HAROGIC_SOURCE       | ⛔              | ⛔                     | ⛔                         | | ||||
| | harogic_source       | Beta       | htra_api          | OPT_BUILD_HAROGIC_SOURCE       | ⛔              | ⛔                     | ✅                         | | ||||
| | hermes_source        | Beta       | -                 | OPT_BUILD_HERMES_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | kcsdr_source         | Unfinished | libkcsdr          | OPT_BUILD_KCSDR_SOURCE         | ⛔              | ⛔                     | ⛔                         | | ||||
| | limesdr_source       | Working    | liblimesuite      | OPT_BUILD_LIMESDR_SOURCE       | ⛔              | ✅                     | ✅                         | | ||||
| | network_source       | Unfinished | -                 | OPT_BUILD_NETWORK_SOURCE       | ✅              | ✅                     | ⛔                         | | ||||
| | network_source       | Beta       | -                 | OPT_BUILD_NETWORK_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | perseus_source       | Beta       | libperseus-sdr    | OPT_BUILD_PERSEUS_SOURCE       | ⛔              | ✅                     | ✅                         | | ||||
| | plutosdr_source      | Working    | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | rfnm_source          | Beta       | librfnm           | OPT_BUILD_RFNM_SOURCE          | ⛔              | ✅                     | ✅                         | | ||||
| @@ -339,9 +341,9 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | sdrpp_server_source  | Working    | -                 | OPT_BUILD_SDRPP_SERVER_SOURCE  | ✅              | ✅                     | ✅                         | | ||||
| | soapy_source         | Deprecated | soapysdr          | OPT_BUILD_SOAPY_SOURCE         | ⛔              | ⛔                     | ⛔                         | | ||||
| | spectran_source      | Unfinished | RTSA Suite        | OPT_BUILD_SPECTRAN_SOURCE      | ⛔              | ⛔                     | ⛔                         | | ||||
| | spectran_http_source | Beta       | -                 | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅              | ✅                     | ⛔                         | | ||||
| | spectran_http_source | Beta       | -                 | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅              | ✅                     | ✅                         | | ||||
| | spyserver_source     | Working    | -                 | OPT_BUILD_SPYSERVER_SOURCE     | ✅              | ✅                     | ✅                         | | ||||
| | usrp_source          | Beta       | libuhd            | OPT_BUILD_USRP_SOURCE          | ⛔              | ⛔                     | ⛔                         | | ||||
| | usrp_source          | Beta       | libuhd            | OPT_BUILD_USRP_SOURCE          | ⛔              | ⛔                     | ✅                         | | ||||
|  | ||||
| ## Sinks | ||||
|  | ||||
| @@ -350,14 +352,15 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | android_audio_sink | Working    | -            | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔              | ✅              | ✅ (Android only)          | | ||||
| | audio_sink         | Working    | rtaudio      | OPT_BUILD_AUDIO_SINK         | ✅              | ✅              | ✅                         | | ||||
| | network_sink       | Working    | -            | OPT_BUILD_NETWORK_SINK       | ✅              | ✅              | ✅                         | | ||||
| | new_portaudio_sink | Beta       | portaudio    | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | portaudio_sink     | Beta       | portaudio    | OPT_BUILD_PORTAUDIO_SINK     | ⛔              | ✅              | ⛔                         | | ||||
| | new_portaudio_sink | Working    | portaudio    | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | portaudio_sink     | Working    | portaudio    | OPT_BUILD_PORTAUDIO_SINK     | ⛔              | ✅              | ⛔                         | | ||||
|  | ||||
| ## Decoders | ||||
|  | ||||
| | Name                | Stage      | Dependencies | Option                        | Built by default| Built in Release | Enabled in SDR++ by default | | ||||
| |---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:| | ||||
| | atv_decoder         | Unfinished | -            | OPT_BUILD_ATV_DECODER         | ⛔              | ⛔              | ⛔                         | | ||||
| | dab_decoder         | Unfinished | -            | OPT_BUILD_DAB_DECODER         | ⛔              | ⛔              | ⛔                         | | ||||
| | falcon9_decoder     | Unfinished | ffplay       | OPT_BUILD_FALCON9_DECODER     | ⛔              | ⛔              | ⛔                         | | ||||
| | kgsstv_decoder      | Unfinished | -            | OPT_BUILD_KGSSTV_DECODER      | ⛔              | ⛔              | ⛔                         | | ||||
| | m17_decoder         | Working    | -            | OPT_BUILD_M17_DECODER         | ⛔              | ✅              | ⛔                         | | ||||
| @@ -416,8 +419,8 @@ If you still have an issue, please open an issue about it or ask on the discord. | ||||
|  | ||||
| # Contributing | ||||
|  | ||||
| Feel free to submit pull requests and report bugs via the GitHub issue tracker. | ||||
| I will soon publish a contributing.md listing the code style to use. | ||||
| Feel free to submit band plans via the GitHub issue tracker. | ||||
| For code changes, please create a feature request instead. | ||||
|  | ||||
| # Credits | ||||
|  | ||||
|   | ||||
							
								
								
									
										117
									
								
								root/res/bandplans/ireland.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								root/res/bandplans/ireland.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| { | ||||
|     "name": "Ireland", | ||||
|     "country_name": "Republic Of Ireland", | ||||
|     "country_code": "IE", | ||||
|     "author_name": "Oskar Dudek", | ||||
|     "author_url": "", | ||||
|     "bands": [ | ||||
|         { | ||||
|             "name": "2200m Ham Band", | ||||
|             "type": "amateur", | ||||
|             "start": 135700, | ||||
|             "end": 137800 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Long wave", | ||||
|             "type": "broadcast", | ||||
|             "start": 148500, | ||||
|             "end": 282500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "AM broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 531000, | ||||
|             "end": 1602000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "120m SW broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 2300000, | ||||
|             "end": 2495000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "90m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 3200000, | ||||
|             "end": 3400000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "75m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 3900000, | ||||
|             "end": 4000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "60m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 4750000, | ||||
|             "end": 5060000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "49m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 5900000, | ||||
|             "end": 6200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "40m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 7200000, | ||||
|             "end": 7450000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "31m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 9400000, | ||||
|             "end": 9900000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "25m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 11600000, | ||||
|             "end": 12100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "22m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13570000, | ||||
|             "end": 13870000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "19m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15100000, | ||||
|             "end": 15800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "16m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 17480000, | ||||
|             "end": 17900000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "15m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 18900000, | ||||
|             "end": 19020000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "13m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 21450000, | ||||
|             "end": 21850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "11m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 25670000, | ||||
|             "end": 26100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "FM Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 87500000, | ||||
|             "end": 108000000 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										549
									
								
								root/res/bandplans/republic-of-korea.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										549
									
								
								root/res/bandplans/republic-of-korea.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,549 @@ | ||||
| { | ||||
|     "name": "Republic of Korea", | ||||
|     "country_name": "Republic of Korea", | ||||
|     "country_code": "KR", | ||||
|     "author_name": "SeoyeonBae", | ||||
|     "author_url": "https://github.com/bsy0317", | ||||
|     "bands": [ | ||||
|         { | ||||
|             "name": "Radio Navigation", | ||||
|             "type": "aviation", | ||||
|             "start": 8300, | ||||
|             "end": 14000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Coastal Telegraph", | ||||
|             "type": "marine", | ||||
|             "start": 14000, | ||||
|             "end": 19950 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 19950, | ||||
|             "end": 20250 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Coastal Telegraph", | ||||
|             "type": "marine", | ||||
|             "start": 20250, | ||||
|             "end": 70000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radio Navigation", | ||||
|             "type": "navigation", | ||||
|             "start": 70000, | ||||
|             "end": 160000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Radio Navigation", | ||||
|             "type": "aviation", | ||||
|             "start": 160000, | ||||
|             "end": 285000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Maritime Radiobeacon", | ||||
|             "type": "aviation", | ||||
|             "start": 285000, | ||||
|             "end": 325000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Radio Navigation", | ||||
|             "type": "aviation", | ||||
|             "start": 325000, | ||||
|             "end": 472000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur", | ||||
|             "type": "amateur", | ||||
|             "start": 472000, | ||||
|             "end": 479000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "International Distress Safety Call", | ||||
|             "type": "marine", | ||||
|             "start": 479000, | ||||
|             "end": 505000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Maritime Telegraph", | ||||
|             "type": "marine", | ||||
|             "start": 505000, | ||||
|             "end": 526500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 526500, | ||||
|             "end": 1606500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiobuoy", | ||||
|             "type": "navigation", | ||||
|             "start": 1606500, | ||||
|             "end": 1800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 1800000, | ||||
|             "end": 1825000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiobuoy Control LORAN", | ||||
|             "type": "radiolocation", | ||||
|             "start": 1825000, | ||||
|             "end": 2000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiobuoy", | ||||
|             "type": "fixed", | ||||
|             "start": 2000000, | ||||
|             "end": 2065000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Distress Call", | ||||
|             "type": "marine", | ||||
|             "start": 2065000, | ||||
|             "end": 2107000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "International Distress Search and Rescue", | ||||
|             "type": "mobile", | ||||
|             "start": 2173500, | ||||
|             "end": 2190500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Road Management", | ||||
|             "type": "fixed", | ||||
|             "start": 2194000, | ||||
|             "end": 2495000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 2495000, | ||||
|             "end": 2505000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Telephone", | ||||
|             "type": "fixed", | ||||
|             "start": 2505000, | ||||
|             "end": 2850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile R", | ||||
|             "type": "aviation", | ||||
|             "start": 2850000, | ||||
|             "end": 3025000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile OR", | ||||
|             "type": "aviation", | ||||
|             "start": 3025000, | ||||
|             "end": 3155000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile R", | ||||
|             "type": "aviation", | ||||
|             "start": 3400000, | ||||
|             "end": 3500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 3500000, | ||||
|             "end": 3550000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Experimental Station", | ||||
|             "type": "fixed", | ||||
|             "start": 3550000, | ||||
|             "end": 3790000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 3790000, | ||||
|             "end": 3800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 3900000, | ||||
|             "end": 3950000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 3995000, | ||||
|             "end": 4005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Telephone", | ||||
|             "type": "marine", | ||||
|             "start": 4005000, | ||||
|             "end": 4063000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Oceanographic Data", | ||||
|             "type": "marine", | ||||
|             "start": 4063000, | ||||
|             "end": 4065000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Duplex Telephone", | ||||
|             "type": "marine", | ||||
|             "start": 4065000, | ||||
|             "end": 4146000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Simplex Telephone", | ||||
|             "type": "marine", | ||||
|             "start": 4146000, | ||||
|             "end": 4152000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Wideband Telegraph Fax", | ||||
|             "type": "marine", | ||||
|             "start": 4152000, | ||||
|             "end": 4172000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Narrowband", | ||||
|             "type": "marine", | ||||
|             "start": 4172000, | ||||
|             "end": 4181750 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station A1A Morse Code Communication", | ||||
|             "type": "marine", | ||||
|             "start": 4186750, | ||||
|             "end": 4202250 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiolocation", | ||||
|             "type": "radiolocation", | ||||
|             "start": 4438000, | ||||
|             "end": 4488000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Calling Response", | ||||
|             "type": "fixed", | ||||
|             "start": 4488000, | ||||
|             "end": 4650000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile R", | ||||
|             "type": "aviation", | ||||
|             "start": 4650000, | ||||
|             "end": 4850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 4995000, | ||||
|             "end": 5005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Search Rescue", | ||||
|             "type": "aviation", | ||||
|             "start": 5480000, | ||||
|             "end": 5730000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 5900000, | ||||
|             "end": 5950000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 5950000, | ||||
|             "end": 6200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 7000000, | ||||
|             "end": 7100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 7100000, | ||||
|             "end": 7200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 7200000, | ||||
|             "end": 7450000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 7995000, | ||||
|             "end": 8005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 9400000, | ||||
|             "end": 9500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 9500000, | ||||
|             "end": 9900000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 9995000, | ||||
|             "end": 10005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 10100000, | ||||
|             "end": 10150000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile", | ||||
|             "type": "aviation", | ||||
|             "start": 10150000, | ||||
|             "end": 11600000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 11600000, | ||||
|             "end": 11650000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 11650000, | ||||
|             "end": 12050000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 12050000, | ||||
|             "end": 12100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile", | ||||
|             "type": "aviation", | ||||
|             "start": 13260000, | ||||
|             "end": 13360000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radio Astronomy", | ||||
|             "type": "astronomy", | ||||
|             "start": 13360000, | ||||
|             "end": 13410000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13570000, | ||||
|             "end": 13600000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13600000, | ||||
|             "end": 13800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13800000, | ||||
|             "end": 13870000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 14000000, | ||||
|             "end": 14350000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile", | ||||
|             "type": "aviation", | ||||
|             "start": 15010000, | ||||
|             "end": 15100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15100000, | ||||
|             "end": 15600000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15600000, | ||||
|             "end": 15800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15800000, | ||||
|             "end": 15995000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 15995000, | ||||
|             "end": 16005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 18900000, | ||||
|             "end": 19020000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 21000000, | ||||
|             "end": 21450000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 21450000, | ||||
|             "end": 21850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 24890000, | ||||
|             "end": 24990000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 25670000, | ||||
|             "end": 26100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 28000000, | ||||
|             "end": 29700000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 50000000, | ||||
|             "end": 54000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "TV Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 54000000, | ||||
|             "end": 72000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Flood Warning", | ||||
|             "type": "broadcast", | ||||
|             "start": 72000000, | ||||
|             "end": 74800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "TV Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 76000000, | ||||
|             "end": 88000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "FM Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 88000000, | ||||
|             "end": 100000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "FM Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 100000000, | ||||
|             "end": 108000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "ILS Localizer VOR", | ||||
|             "type": "fixed", | ||||
|             "start": 108000000, | ||||
|             "end": 117975000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 144000000, | ||||
|             "end": 146000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "General Communication", | ||||
|             "type": "fixed", | ||||
|             "start": 146000000, | ||||
|             "end": 148000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 162037500, | ||||
|             "end": 174000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "TV Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 174000000, | ||||
|             "end": 216000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 216000000, | ||||
|             "end": 230000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 273000000, | ||||
|             "end": 322000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Personal Radio", | ||||
|             "type": "fixed", | ||||
|             "start": 420000000, | ||||
|             "end": 470000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Public Network", | ||||
|             "type": "broadcast", | ||||
|             "start": 698000000, | ||||
|             "end": 806000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 942000000, | ||||
|             "end": 960000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Satellite Mobile Communication", | ||||
|             "type": "fixed", | ||||
|             "start": 15250000000, | ||||
|             "end": 16605000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Mobile Communication", | ||||
|             "type": "mobile", | ||||
|             "start": 25000000000, | ||||
|             "end": 37000000000 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| @@ -3,7 +3,7 @@ Encoding=UTF-8 | ||||
| Version=1.0 | ||||
| Type=Application | ||||
| Terminal=false | ||||
| Exec=@SDRPP_BIN_INSTALL_DIR@/sdrpp | ||||
| Exec=@CMAKE_INSTALL_PREFIX@/bin/sdrpp | ||||
| Name=SDR++ | ||||
| Icon=@SDRPP_RES_INSTALL_DIR@/icons/sdrpp.png | ||||
| Icon=@CMAKE_INSTALL_PREFIX@/share/sdrpp/icons/sdrpp.png | ||||
| Categories=HamRadio | ||||
| @@ -6,10 +6,6 @@ if (NOT SDRPP_MODULE_COMPILER_FLAGS) | ||||
|     set(SDRPP_MODULE_COMPILER_FLAGS @SDRPP_MODULE_COMPILER_FLAGS@) | ||||
| endif () | ||||
|  | ||||
| # Add compiler definitions for the directories | ||||
| add_definitions(-DSDRPP_MODULES_LOAD_DIR="${SDRPP_MODULES_LOAD_DIR}") | ||||
| add_definitions(-DSDRPP_RES_LOAD_DIR="${SDRPP_RES_LOAD_DIR}") | ||||
|  | ||||
| # Created shared lib and link to core | ||||
| add_library(${PROJECT_NAME} SHARED ${SRC}) | ||||
| target_link_libraries(${PROJECT_NAME} PRIVATE sdrpp_core) | ||||
| @@ -20,4 +16,4 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") | ||||
| target_compile_options(${PROJECT_NAME} PRIVATE ${SDRPP_MODULE_COMPILER_FLAGS}) | ||||
|  | ||||
| # Install directives | ||||
| install(TARGETS ${PROJECT_NAME} DESTINATION ${SDRPP_MODULES_INSTALL_DIR}) | ||||
| install(TARGETS ${PROJECT_NAME} DESTINATION lib/sdrpp/plugins) | ||||
| @@ -217,14 +217,19 @@ private: | ||||
|     } | ||||
|  | ||||
|     void startServer() { | ||||
|         if (modeId == SINK_MODE_TCP) { | ||||
|             listener = net::listen(hostname, port); | ||||
|             if (listener) { | ||||
|                 listener->acceptAsync(clientHandler, this); | ||||
|         try { | ||||
|             if (modeId == SINK_MODE_TCP) { | ||||
|                 listener = net::listen(hostname, port); | ||||
|                 if (listener) { | ||||
|                     listener->acceptAsync(clientHandler, this); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 conn = net::openUDP("0.0.0.0", port, hostname, port, false); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             conn = net::openUDP("0.0.0.0", port, hostname, port, false); | ||||
|         catch (const std::exception& e) { | ||||
|             flog::error("Failed to open socket: {}", e.what()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| #include <libbladeRF.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <algorithm> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| @@ -37,6 +38,10 @@ public: | ||||
|     BladeRFSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         // Define clocks | ||||
|         clocks.define("onboard", "On-Board", CLOCK_SELECT_ONBOARD); | ||||
|         clocks.define("external", "External", CLOCK_SELECT_EXTERNAL); | ||||
|  | ||||
|         sampleRate = 1000000.0; | ||||
|  | ||||
|         handler.ctx = this; | ||||
| @@ -267,6 +272,15 @@ public: | ||||
|         } | ||||
|         config.release(true); | ||||
|  | ||||
|         // Load clock source | ||||
|         clkId = clocks.keyId("onboard"); | ||||
|         if (config.conf["devices"][selectedSerial].contains("clock")) { | ||||
|             std::string clkStr = config.conf["devices"][selectedSerial]["clock"]; | ||||
|             if (clocks.keyExists(clkStr)) { | ||||
|                 clkId = clocks.keyId(clkStr); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Load gain mode | ||||
|         if (config.conf["devices"][selectedSerial].contains("gainMode")) { | ||||
|             std::string gm = config.conf["devices"][selectedSerial]["gainMode"]; | ||||
| @@ -364,6 +378,7 @@ private: | ||||
|         if (_this->bufferSize < 1024) { _this->bufferSize = 1024; } | ||||
|  | ||||
|         // Setup device parameters | ||||
|         _this->setClockSource(_this->clocks[_this->clkId]); | ||||
|         bladerf_set_sample_rate(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->sampleRate, NULL); | ||||
|         bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq); | ||||
|         bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ? std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL); | ||||
| @@ -486,6 +501,19 @@ private: | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Clock Source"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_balderf_clk_sel_", _this->name), &_this->clkId, _this->clocks.txt)) { | ||||
|             if (_this->running) { | ||||
|                 _this->setClockSource(_this->clocks[_this->clkId]); | ||||
|             } | ||||
|             if (_this->selectedSerial != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["clock"] = _this->clocks.key(_this->clkId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // General config BS | ||||
|         SmGui::LeftLabel("Gain control mode"); | ||||
|         SmGui::FillWidth(); | ||||
| @@ -537,6 +565,15 @@ private: | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void setClockSource(bladerf_clock_select clk) { | ||||
|         if (selectedBladeType == BLADERF_TYPE_V1) { | ||||
|             bladerf_set_smb_mode(openDev, (clk == CLOCK_SELECT_EXTERNAL) ? BLADERF_SMB_MODE_INPUT : BLADERF_SMB_MODE_DISABLED); | ||||
|         } | ||||
|         else { | ||||
|             bladerf_set_clock_select(openDev, clk); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         int16_t* buffer = new int16_t[bufferSize * 2]; | ||||
|         bladerf_metadata meta; | ||||
| @@ -565,6 +602,7 @@ private: | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int bwId = 0; | ||||
|     int clkId = 0; | ||||
|     int chanId = 0; | ||||
|     int gainMode = 0; | ||||
|     bool streamingEnabled = false; | ||||
| @@ -580,8 +618,8 @@ private: | ||||
|     std::string sampleRatesTxt; | ||||
|     std::vector<uint64_t> bandwidths; | ||||
|     std::string bandwidthsTxt; | ||||
|  | ||||
|     std::string channelNamesTxt; | ||||
|     OptionList<std::string, bladerf_clock_select> clocks; | ||||
|  | ||||
|     int bufferSize; | ||||
|     struct bladerf_stream* rxStream; | ||||
|   | ||||
							
								
								
									
										21
									
								
								source_modules/fobossdr_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								source_modules/fobossdr_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(fobossdr_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/lib/") | ||||
|     target_include_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/include/") | ||||
|     target_link_libraries(fobossdr_source PRIVATE fobos) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBFOBOS REQUIRED libfobos) | ||||
|  | ||||
|     target_include_directories(fobossdr_source PRIVATE ${LIBFOBOS_INCLUDE_DIRS}) | ||||
|     target_link_directories(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARY_DIRS}) | ||||
|     target_link_libraries(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARIES}) | ||||
| endif () | ||||
							
								
								
									
										560
									
								
								source_modules/fobossdr_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										560
									
								
								source_modules/fobossdr_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,560 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <atomic> | ||||
| #include <fobos.h> | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "fobossdr_source", | ||||
|     /* Description:     */ "FobosSDR Source Module", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| // Work around for the fobos API not including | ||||
| #define FOBOS_LNA_GAIN_MIN  1 | ||||
| #define FOBOS_LNA_GAIN_MAX  3 | ||||
| #define FOBOS_VGA_GAIN_MIN  0 | ||||
| #define FOBOS_VGA_GAIN_MAX  31 | ||||
|  | ||||
| class FobosSDRSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     FobosSDRSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 50000000.0; | ||||
|  | ||||
|         // Initialize the DDC | ||||
|         ddc.init(&ddcIn, 50e6, 50e6, 50e6, 0.0); | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &ddc.out; | ||||
|  | ||||
|         // Refresh devices | ||||
|         refresh(); | ||||
|  | ||||
|         // Select device from config | ||||
|         config.acquire(); | ||||
|         std::string devSerial = config.conf["device"]; | ||||
|         config.release(); | ||||
|         select(devSerial); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("FobosSDR", &handler); | ||||
|     } | ||||
|  | ||||
|     ~FobosSDRSourceModule() { | ||||
|         // Nothing to do | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
|     enum Port { | ||||
|         PORT_RF, | ||||
|         PORT_HF1, | ||||
|         PORT_HF2 | ||||
|     }; | ||||
|  | ||||
| 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); | ||||
|     } | ||||
|  | ||||
|     void refresh() { | ||||
|         devices.clear(); | ||||
|          | ||||
|         // Get device list | ||||
|         char serials[1024]; | ||||
|         memset(serials, 0, sizeof(serials)); | ||||
|         int devCount = fobos_rx_list_devices(serials); | ||||
|         if (devCount < 0) { | ||||
|             flog::error("Failed to get device list: {}", devCount); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If no device, give up | ||||
|         if (!devCount) { return; } | ||||
|  | ||||
|         // Generate device entries | ||||
|         const char* _serials = serials; | ||||
|         int index = 0; | ||||
|         while (*_serials) { | ||||
|             // Read serial until space | ||||
|             std::string serial = ""; | ||||
|             while (*_serials) { | ||||
|                 // Get a character | ||||
|                 char c = *(_serials++); | ||||
|  | ||||
|                 // If it's a space, we're done | ||||
|                 if (c == ' ') { break; } | ||||
|  | ||||
|                 // Otherwise, add it to the string | ||||
|                 serial += c; | ||||
|             } | ||||
|  | ||||
|             // Create entry | ||||
|             devices.define(serial, serial, index++); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devices.empty()) { | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial was not found, select the first available serial | ||||
|         if (!devices.keyExists(serial)) { | ||||
|             select(devices.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the ID in the list | ||||
|         int id = devices.keyId(serial); | ||||
|         selectedDevId = devices[id]; | ||||
|  | ||||
|         // Open the device | ||||
|         fobos_dev_t* dev; | ||||
|         int err = fobos_rx_open(&dev, selectedDevId); | ||||
|         if (err) { | ||||
|             flog::error("Failed to open device: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get a list of supported samplerates | ||||
|         double srList[128]; | ||||
|         unsigned int srCount; | ||||
|         err = fobos_rx_get_samplerates(dev, srList, &srCount); | ||||
|         if (err) { | ||||
|             flog::error("Failed to get samplerate list: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Generate samplerate list | ||||
|         samplerates.clear(); | ||||
|         for (int i = 0; i < srCount; i++) { | ||||
|             std::string str = getBandwdithScaled(srList[i]); | ||||
|             samplerates.define(srList[i], str, srList[i]); | ||||
|         } | ||||
|  | ||||
|         // Add some custom samplerates | ||||
|         samplerates.define(5e6, "5.0MHz", 5e6); | ||||
|         samplerates.define(2.5e6, "2.5MHz", 2.5e6); | ||||
|         samplerates.define(1.25e6, "1.25MHz", 1.25e6); | ||||
|  | ||||
|         // Define the ports | ||||
|         ports.clear(); | ||||
|         ports.define("rf", "RF", PORT_RF); | ||||
|         ports.define("hf1", "HF1", PORT_HF1); | ||||
|         ports.define("hf2", "HF2", PORT_HF2); | ||||
|  | ||||
|         // Define clock sources | ||||
|         clockSources.clear(); | ||||
|         clockSources.define("internal", "Internal", 0); | ||||
|         clockSources.define("external", "External", 1); | ||||
|  | ||||
|         // Close the device | ||||
|         fobos_rx_close(dev); | ||||
|  | ||||
|         // Save serial number | ||||
|         selectedSerial = serial; | ||||
|         devId = id; | ||||
|  | ||||
|         // Load default options | ||||
|         sampleRate = 50e6; | ||||
|         srId = samplerates.valueId(sampleRate); | ||||
|         port = PORT_RF; | ||||
|         portId = ports.valueId(port); | ||||
|         clkSrcId = clockSources.nameId("Internal"); | ||||
|         lnaGain = 0; | ||||
|         vgaGain = 0; | ||||
|  | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         if (config.conf["devices"][selectedSerial].contains("samplerate")) { | ||||
|             int desiredSr = config.conf["devices"][selectedSerial]["samplerate"]; | ||||
|             if (samplerates.keyExists(desiredSr)) { | ||||
|                 srId = samplerates.keyId(desiredSr); | ||||
|                 sampleRate = samplerates[srId]; | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("port")) { | ||||
|             std::string desiredPort = config.conf["devices"][selectedSerial]["port"]; | ||||
|             if (ports.keyExists(desiredPort)) { | ||||
|                 portId = ports.keyId(desiredPort); | ||||
|                 port = ports[portId]; | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("clkSrc")) { | ||||
|             std::string desiredClkSrc = config.conf["devices"][selectedSerial]["clkSrc"]; | ||||
|             if (clockSources.keyExists(desiredClkSrc)) { | ||||
|                 clkSrcId = clockSources.keyId(desiredClkSrc); | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("lnaGain")) { | ||||
|             lnaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["lnaGain"], FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX); | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("vgaGain")) { | ||||
|             vgaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["vgaGain"], FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX); | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         // Update the samplerate | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("FobosSDRSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         flog::info("FobosSDRSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|  | ||||
|         // Open the device | ||||
|         int err = fobos_rx_open(&_this->openDev, _this->selectedDevId); | ||||
|         if (err) { | ||||
|             flog::error("Failed to open device: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the selected port | ||||
|         _this->port = _this->ports[_this->portId]; | ||||
|  | ||||
|         // Configure the device | ||||
|         double actualSr, actualFreq; | ||||
|         fobos_rx_set_samplerate(_this->openDev, (_this->sampleRate >= 50e6) ? _this->sampleRate : 50e6, &actualSr); | ||||
|         fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq); | ||||
|         fobos_rx_set_direct_sampling(_this->openDev, _this->port != PORT_RF); | ||||
|         fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]); | ||||
|         fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|         fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|  | ||||
|         // Configure the DDC | ||||
|         if (_this->port == PORT_RF && _this->sampleRate >= 50e6) { | ||||
|             // Set the frequency | ||||
|             fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq); | ||||
|         } | ||||
|         else if (_this->port == PORT_RF) { | ||||
|             // Set the frequency | ||||
|             fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq); | ||||
|  | ||||
|             // Configure and start the DDC for decimation only | ||||
|             _this->ddc.setInSamplerate(actualSr); | ||||
|             _this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate); | ||||
|             _this->ddc.setOffset(0.0); | ||||
|             _this->ddc.start(); | ||||
|         } | ||||
|         else { | ||||
|             // Configure and start the DDC | ||||
|             _this->ddc.setInSamplerate(actualSr); | ||||
|             _this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate); | ||||
|             _this->ddc.setOffset(_this->freq); | ||||
|             _this->ddc.start(); | ||||
|         } | ||||
|  | ||||
|         // Compute buffer size (Lower than usual, but it's a workaround for their API having broken streaming) | ||||
|         _this->bufferSize = _this->sampleRate / 400.0; | ||||
|  | ||||
|         // Start streaming | ||||
|         err = fobos_rx_start_sync(_this->openDev, _this->bufferSize); | ||||
|         if (err) { | ||||
|             flog::error("Failed to start stream: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Start worker | ||||
|         _this->run = true; | ||||
|         _this->workerThread = std::thread(&FobosSDRSourceModule::worker, _this); | ||||
|          | ||||
|         _this->running = true; | ||||
|         flog::info("FobosSDRSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|  | ||||
|         // Stop worker | ||||
|         _this->run = false; | ||||
|         if (_this->port == PORT_RF && _this->sampleRate >= 50e6) { | ||||
|             _this->ddc.out.stopWriter(); | ||||
|             if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|             _this->ddc.out.clearWriteStop(); | ||||
|         } | ||||
|         else { | ||||
|             _this->ddcIn.stopWriter(); | ||||
|             if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|             _this->ddcIn.clearWriteStop(); | ||||
|         } | ||||
|  | ||||
|         // Stop streaming | ||||
|         fobos_rx_stop_sync(_this->openDev); | ||||
|  | ||||
|         // Stop the DDC | ||||
|         _this->ddc.stop(); | ||||
|  | ||||
|         // Close the device | ||||
|         fobos_rx_close(_this->openDev); | ||||
|  | ||||
|         flog::info("FobosSDRSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             if (_this->port == PORT_RF) { | ||||
|                 double actual; // Dummy, don't care | ||||
|                 fobos_rx_set_frequency(_this->openDev, freq, &actual); | ||||
|             } | ||||
|             else { | ||||
|                 _this->ddc.setOffset(freq); | ||||
|             } | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("FobosSDRSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|          | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = _this->selectedSerial; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { | ||||
|             _this->sampleRate = _this->samplerates.value(_this->srId); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["samplerate"] = _this->samplerates.key(_this->srId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_fobossdr_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Antenna Port"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_port_", _this->name), &_this->portId, _this->ports.txt)) { | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["port"] = _this->ports.key(_this->portId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::LeftLabel("Clock Source"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_clk_", _this->name), &_this->clkSrcId, _this->clockSources.txt)) { | ||||
|             if (_this->running) { | ||||
|                 fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["clkSrc"] = _this->clockSources.key(_this->clkSrcId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_this->port == PORT_RF) { | ||||
|             SmGui::LeftLabel("LNA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_fobossdr_lna_gain_", _this->name), &_this->lnaGain, FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX)) { | ||||
|                 if (_this->running) { | ||||
|                     fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                 } | ||||
|                 if (!_this->selectedSerial.empty()) { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerial]["lnaGain"] = _this->lnaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             SmGui::LeftLabel("VGA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_fobossdr_vga_gain_", _this->name), &_this->vgaGain, FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX)) { | ||||
|                 if (_this->running) { | ||||
|                     fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|                 } | ||||
|                 if (!_this->selectedSerial.empty()) { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerial]["vgaGain"] = _this->vgaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         // Select different processing depending on the mode | ||||
|         if (port == PORT_RF && sampleRate >= 50e6) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddc.out.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|                  | ||||
|                 // Send out samples to the core | ||||
|                 if (!ddc.out.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (port == PORT_RF) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|                  | ||||
|                 // Send samples to the DDC | ||||
|                 if (!ddcIn.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (port == PORT_HF1) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|  | ||||
|                 // Null out the HF2 samples | ||||
|                 for (int i = 0; i < sampCount; i++) { | ||||
|                     ddcIn.writeBuf[i].im = 0.0f; | ||||
|                 } | ||||
|                  | ||||
|                 // Send samples to the DDC | ||||
|                 if (!ddcIn.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (port == PORT_HF2) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|  | ||||
|                 // Null out the HF2 samples | ||||
|                 for (int i = 0; i < sampCount; i++) { | ||||
|                     ddcIn.writeBuf[i].re = 0.0f; | ||||
|                 } | ||||
|                  | ||||
|                 // Send samples to the DDC | ||||
|                 if (!ddcIn.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|  | ||||
|     OptionList<std::string, int> devices; | ||||
|     OptionList<int, double> samplerates; | ||||
|     OptionList<std::string, Port> ports; | ||||
|     OptionList<std::string, int> clockSources; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int portId = 0; | ||||
|     int clkSrcId = 0; | ||||
|     Port port; | ||||
|     int lnaGain = 0; | ||||
|     int vgaGain = 0; | ||||
|     std::string selectedSerial; | ||||
|     int selectedDevId; | ||||
|  | ||||
|     fobos_dev_t* openDev; | ||||
|  | ||||
|     int bufferSize; | ||||
|     std::thread workerThread; | ||||
|     std::atomic<bool> run = false; | ||||
|  | ||||
|     dsp::stream<dsp::complex_t> ddcIn; | ||||
|     dsp::channel::RxVFO ddc; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     json def = json({}); | ||||
|     def["devices"] = json({}); | ||||
|     def["device"] = ""; | ||||
|     config.setPath(core::args["root"].s() + "/fobossdr_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new FobosSDRSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (FobosSDRSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
							
								
								
									
										1
									
								
								source_modules/kcsdr_source/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								source_modules/kcsdr_source/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| vendor/* | ||||
							
								
								
									
										10
									
								
								source_modules/kcsdr_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								source_modules/kcsdr_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(kcsdr_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp" "src/*.c") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| target_link_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10/x64/DLL") | ||||
| target_include_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10") | ||||
| target_link_libraries(kcsdr_source PRIVATE FTD3XX) | ||||
							
								
								
									
										209
									
								
								source_modules/kcsdr_source/src/kcsdr.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								source_modules/kcsdr_source/src/kcsdr.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| #include "kcsdr.h" | ||||
| #include <string.h> | ||||
| #include "../vendor/FTD3XXLibrary_1.3.0.10/FTD3XX.h" | ||||
| #include <stdio.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| #define KCSDR_PKT_EMPTY_LEN 0x0C | ||||
|  | ||||
| #define KCSDR_COMMAND_PIPE  0x02 | ||||
| #define KCSDR_RX_DATA_PIPE  0x83 | ||||
| #define KCSDR_TX_DATA_PIPE  0x03 | ||||
|  | ||||
| struct kcsdr { | ||||
|     FT_HANDLE ft; | ||||
| }; | ||||
|  | ||||
| #pragma pack(push, 1) | ||||
| struct kcsdr_packet { | ||||
|     uint8_t zeros0[4]; | ||||
|     uint8_t length; | ||||
|     uint8_t zeros1[2]; | ||||
|     uint8_t hex_eighty; | ||||
|     uint32_t command; | ||||
|     uint8_t data[188]; | ||||
| }; | ||||
| typedef struct kcsdr_packet kcsdr_packet_t; | ||||
| #pragma pack(pop) | ||||
|  | ||||
| enum kcsdr_command { | ||||
|     CMD_NOT_USED_0x00   = 0x00, | ||||
|     CMD_SET_PORT        = 0x01, | ||||
|     CMD_SET_FREQUENCY   = 0x02, | ||||
|     CMD_SET_ATTENUATION = 0x03, | ||||
|     CMD_SET_AMPLIFIER   = 0x04, | ||||
|     CMD_SET_BANDWIDTH   = 0x05, | ||||
|     CMD_START           = 0x06, | ||||
|     CMD_STOP            = 0x07, | ||||
|     CMD_SET_EXT_AMP     = 0x08, | ||||
|     CMD_START_REMOTE    = 0x09, | ||||
|     CMD_STOP_REMOTE     = 0x0A | ||||
| }; | ||||
| typedef enum kcsdr_command kcsdr_command_t; | ||||
|  | ||||
| int kcsdr_send_command(kcsdr_t* dev, kcsdr_direction_t dir, kcsdr_command_t cmd, const uint8_t* data, int len) { | ||||
|     Sleep(50); | ||||
|      | ||||
|     // Create an empty packet | ||||
|     kcsdr_packet_t pkt; | ||||
|     memset(&pkt, 0, sizeof(kcsdr_packet_t)); | ||||
|  | ||||
|     // Fill out the packet info | ||||
|     pkt.length = len + KCSDR_PKT_EMPTY_LEN; | ||||
|     pkt.hex_eighty = 0x80; // Whatever the fuck that is | ||||
|     pkt.command = (uint32_t)cmd | (uint32_t)dir; | ||||
|  | ||||
|     // Copy the data if there is some | ||||
|     if (len) { memcpy(pkt.data, data, len); } | ||||
|  | ||||
|     // Dump the bytes | ||||
|     uint8_t* dump = (uint8_t*)&pkt; | ||||
|     printf("Sending:"); | ||||
|     for (int i = 0; i < pkt.length; i++) { | ||||
|         printf(" %02X", dump[i]); | ||||
|     } | ||||
|     printf("\n"); | ||||
|  | ||||
|     // Send the command to endpoint 0 | ||||
|     int sent; | ||||
|     FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_COMMAND_PIPE, (uint8_t*)&pkt, sizeof(kcsdr_packet_t), &sent, NULL); | ||||
|     if (err != FT_OK) { | ||||
|         return -err; | ||||
|     } | ||||
|     printf("Sent %d bytes (%d)\n", sent, err); | ||||
|  | ||||
|     Sleep(50); | ||||
|  | ||||
|     // Flush existing commands | ||||
|     FT_FlushPipe(dev->ft, KCSDR_COMMAND_PIPE); | ||||
|  | ||||
|     return -(int)err; | ||||
| } | ||||
|  | ||||
| int kcsdr_list_devices(kcsdr_info_t** devices) { | ||||
|     // Generate a list of FTDI devices | ||||
|     int ftdiDevCount = 0; | ||||
|     FT_STATUS err = FT_CreateDeviceInfoList(&ftdiDevCount); | ||||
|     if (err != FT_OK) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // If no device was found, return nothing | ||||
|     if (!ftdiDevCount) { | ||||
|         *devices = NULL; | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     // Get said device list | ||||
|     FT_DEVICE_LIST_INFO_NODE* list = malloc(ftdiDevCount * sizeof(FT_DEVICE_LIST_INFO_NODE)); | ||||
|     err = FT_GetDeviceInfoList(list, &ftdiDevCount); | ||||
|     if (err != FT_OK) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // Allocate the device info list | ||||
|     *devices = malloc(ftdiDevCount * sizeof(kcsdr_info_t)); | ||||
|  | ||||
|     // Find all KC908s | ||||
|     int kcCount = 0; | ||||
|     for (int i = 0; i < ftdiDevCount; i++) { | ||||
|         strcpy((*devices)[kcCount++].serial, list[i].SerialNumber); | ||||
|     } | ||||
|  | ||||
|     // Free the FTDI list | ||||
|     free(list); | ||||
|  | ||||
|     return kcCount; | ||||
| } | ||||
|  | ||||
| void kcsdr_free_device_list(kcsdr_info_t* devices) { | ||||
|     // Free the list | ||||
|     if (devices) { free(devices); } | ||||
| } | ||||
|  | ||||
| int kcsdr_open(kcsdr_t** dev, const char* serial) { | ||||
|     // Attempt to open the device using the serial number | ||||
|     FT_HANDLE ft; | ||||
|     FT_STATUS err = FT_Create(serial, FT_OPEN_BY_SERIAL_NUMBER, &ft); | ||||
|     if (err != FT_OK) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // Set the timeouts for the data pipes | ||||
|     FT_SetPipeTimeout(ft, KCSDR_RX_DATA_PIPE, 1000); | ||||
|     FT_SetPipeTimeout(ft, KCSDR_TX_DATA_PIPE, 1000); | ||||
|  | ||||
|     // Allocate the device object | ||||
|     *dev = malloc(sizeof(kcsdr_t)); | ||||
|  | ||||
|     // Fill out the device object | ||||
|     (*dev)->ft = ft; | ||||
|  | ||||
|     // Put device into remote control mode | ||||
|     return kcsdr_send_command(*dev, KCSDR_DIR_RX, CMD_START_REMOTE, NULL, 0); | ||||
| } | ||||
|  | ||||
| void kcsdr_close(kcsdr_t* dev) { | ||||
|     // Put device back in normal mode | ||||
|     kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_STOP_REMOTE, NULL, 0); | ||||
|  | ||||
|     // Close the device | ||||
|     FT_Close(dev->ft); | ||||
|  | ||||
|     // Free the device object | ||||
|     free(dev); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port) { | ||||
|     // Send SET_PORT command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_PORT, &port, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq) { | ||||
|     // Send SET_FREQUENCY command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_FREQUENCY, (uint8_t*)&freq, 8); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att) { | ||||
|     // Send SET_ATTENUATION command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_ATTENUATION, &att, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain) { | ||||
|     // Send SET_AMPLIFIER command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_AMPLIFIER, &gain, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain) { | ||||
|     // Send CMD_SET_EXT_AMP command | ||||
|     return kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_SET_EXT_AMP, &gain, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate) { | ||||
|     // Set SET_BANDWIDTH command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_BANDWIDTH, (uint8_t*)&samplerate, 4); | ||||
| } | ||||
|  | ||||
| int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir) { | ||||
|     // Send START command | ||||
|     return kcsdr_send_command(dev, dir, CMD_START, NULL, 0); | ||||
| } | ||||
|  | ||||
| int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir) { | ||||
|     // Send STOP command | ||||
|     return kcsdr_send_command(dev, dir, CMD_STOP, NULL, 0); | ||||
| } | ||||
|  | ||||
| int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count) { | ||||
|     // Receive samples (TODO: Endpoint might be 0x81) | ||||
|     int received; | ||||
|     FT_STATUS err = FT_ReadPipeEx(dev->ft, KCSDR_RX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &received, NULL); | ||||
|     return (err == FT_OK) ? received / (2*sizeof(uint16_t)) : -(int)err; | ||||
| } | ||||
|  | ||||
| int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count) { | ||||
|     // Transmit samples | ||||
|     int sent; | ||||
|     FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_TX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &sent, NULL); | ||||
|     return (err == FT_OK) ? sent / (2*sizeof(uint16_t)) : -(int)err; | ||||
| } | ||||
							
								
								
									
										150
									
								
								source_modules/kcsdr_source/src/kcsdr.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								source_modules/kcsdr_source/src/kcsdr.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
|  | ||||
| #define KCSDR_SERIAL_LEN    16 | ||||
| #define KCSDR_MAX_PORTS     6 | ||||
|  | ||||
| // Detect C++ | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| /** | ||||
|  * KCSDR Device. | ||||
| */ | ||||
| struct kcsdr; | ||||
| typedef struct kcsdr kcsdr_t; | ||||
|  | ||||
| /** | ||||
|  * Device Information | ||||
| */ | ||||
| struct kcsdr_info { | ||||
|     char serial[KCSDR_SERIAL_LEN+1]; | ||||
| }; | ||||
| typedef struct kcsdr_info kcsdr_info_t; | ||||
|  | ||||
| /** | ||||
|  * RF Direction. | ||||
| */ | ||||
| enum kcsdr_direction { | ||||
|     KCSDR_DIR_RX    = 0x00, | ||||
|     KCSDR_DIR_TX    = 0x80 | ||||
| }; | ||||
| typedef enum kcsdr_direction kcsdr_direction_t; | ||||
|  | ||||
| /** | ||||
|  * Get a list of KCSDR devices on the system. | ||||
|  * @param devices Pointer to an array of device info. | ||||
|  * @return Number of devices found or error code. | ||||
| */ | ||||
| int kcsdr_list_devices(kcsdr_info_t** devices); | ||||
|  | ||||
| /** | ||||
|  * Free a device list returned by `kcsdr_list_devices()`. | ||||
|  * @param devices Device list to free. | ||||
| */ | ||||
| void kcsdr_free_device_list(kcsdr_info_t* devices); | ||||
|  | ||||
| /** | ||||
|  * Open a KCSDR device. | ||||
|  * @param dev Newly open device. | ||||
|  * @param serial Serial number of the device to open as returned in the device list. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_open(kcsdr_t** dev, const char* serial); | ||||
|  | ||||
| /** | ||||
|  * Close a KCSDR device. | ||||
|  * @param dev Device to be closed. | ||||
| */ | ||||
| void kcsdr_close(kcsdr_t* dev); | ||||
|  | ||||
| /** | ||||
|  * Select the RF port. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX. | ||||
|  * @param port RF port number to select. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port); | ||||
|  | ||||
| /** | ||||
|  * Set the center frequency. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param freq Frequency in Hz. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq); | ||||
|  | ||||
| /** | ||||
|  * Set the attenuation. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param samplerate Attenuation in dB. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att); | ||||
|  | ||||
| /** | ||||
|  * Set the internal amplifier gain. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param gain Gain in dB. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain); | ||||
|  | ||||
| /** | ||||
|  * Set the external amplifier gain. | ||||
|  * @param dev Device to control. | ||||
|  * @param gain Gain in dB. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain); | ||||
|  | ||||
| /** | ||||
|  * Set the samplerate. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param samplerate Samplerate in Hz. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate); | ||||
|  | ||||
| /** | ||||
|  * Start streaming samples. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir); | ||||
|  | ||||
| /** | ||||
|  * Stop streaming samples. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir); | ||||
|  | ||||
| /** | ||||
|  * Receive a buffer of samples. | ||||
|  * @param samples Sample buffer. | ||||
|  * @param count Number of complex samples. | ||||
|  * @return Number of samples received. | ||||
| */ | ||||
| int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count); | ||||
|  | ||||
| /** | ||||
|  * Transmit a buffer of samples. | ||||
|  * @param samples Sample buffer. | ||||
|  * @param count Number of complex samples. | ||||
|  * @return Number of samples transmitted. | ||||
| */ | ||||
| int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count); | ||||
|  | ||||
| // Detect C++ | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										324
									
								
								source_modules/kcsdr_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								source_modules/kcsdr_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include "kcsdr.h" | ||||
| #include <atomic> | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "kcsdr_source", | ||||
|     /* Description:     */ "KCSDR Source Module", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| class KCSDRSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     KCSDRSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 2000000.0; | ||||
|         samplerates.define(40e6, "40MHz", 40e6); | ||||
|         samplerates.define(35e6, "35MHz", 35e6); | ||||
|         samplerates.define(30e6, "30MHz", 30e6); | ||||
|         samplerates.define(25e6, "25MHz", 25e6); | ||||
|         samplerates.define(20e6, "20MHz", 20e6); | ||||
|         samplerates.define(15e6, "15MHz", 15e6); | ||||
|         samplerates.define(10e6, "10MHz", 10e6); | ||||
|         samplerates.define(5e6, "5MHz", 5e6); | ||||
|  | ||||
|         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 devices | ||||
|         refresh(); | ||||
|  | ||||
|         // Select first (TODO: Select from config) | ||||
|         select(""); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("KCSDR", &handler); | ||||
|     } | ||||
|  | ||||
|     ~KCSDRSourceModule() { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void refresh() { | ||||
|         devices.clear(); | ||||
|          | ||||
|         // Get device list | ||||
|         kcsdr_info_t* list; | ||||
|         int count = kcsdr_list_devices(&list); | ||||
|         if (count < 0) { | ||||
|             flog::error("Failed to list devices: {}", count); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Create list | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             devices.define(list[i].serial, list[i].serial, list[i].serial); | ||||
|         } | ||||
|  | ||||
|         // Free the device list | ||||
|         kcsdr_free_device_list(list); | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devices.empty()) { | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial was not found, select the first available serial | ||||
|         if (!devices.keyExists(serial)) { | ||||
|             select(devices.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the menu ID | ||||
|         devId = devices.keyId(serial); | ||||
|  | ||||
|         // TODO | ||||
|  | ||||
|         // Update the samplerate | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         // Save serial number | ||||
|         selectedSerial = serial; | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("KCSDRSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         flog::info("KCSDRSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|  | ||||
|         // If no serial is given, do nothing | ||||
|         if (_this->selectedSerial.empty()) { return; } | ||||
|  | ||||
|         // Open the device | ||||
|         int err = kcsdr_open(&_this->openDev, _this->selectedSerial.c_str()); | ||||
|         if (err) { | ||||
|             flog::error("Failed to open device: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Configure the device | ||||
|         kcsdr_set_port(_this->openDev, KCSDR_DIR_RX, 0); | ||||
|         kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, _this->freq); | ||||
|         kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att); | ||||
|         kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain); | ||||
|         kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain); | ||||
|         kcsdr_set_samplerate(_this->openDev, KCSDR_DIR_RX, _this->sampleRate); | ||||
|  | ||||
|         // Start the stream | ||||
|         kcsdr_start(_this->openDev, KCSDR_DIR_RX); | ||||
|  | ||||
|         // Start worker | ||||
|         _this->run = true; | ||||
|         _this->workerThread = std::thread(&KCSDRSourceModule::worker, _this); | ||||
|          | ||||
|         _this->running = true; | ||||
|         flog::info("KCSDRSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|          | ||||
|         // Stop worker | ||||
|         _this->run = false; | ||||
|         _this->stream.stopWriter(); | ||||
|         if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|         _this->stream.clearWriteStop(); | ||||
|  | ||||
|         // Stop streaming | ||||
|         kcsdr_stop(_this->openDev, KCSDR_DIR_RX); | ||||
|  | ||||
|         // Close the device | ||||
|         kcsdr_close(_this->openDev); | ||||
|  | ||||
|         flog::info("KCSDRSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, freq); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("KCSDRSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|          | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_kcsdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_kcsdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { | ||||
|             _this->sampleRate = _this->samplerates.value(_this->srId); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_kcsdr_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         // SmGui::LeftLabel("RX Port"); | ||||
|         // SmGui::FillWidth(); | ||||
|         // if (SmGui::Combo(CONCAT("##_kcsdr_port_", _this->name), &_this->portId, _this->rxPorts.txt)) { | ||||
|         //     if (_this->running) { | ||||
|         //         // TODO | ||||
|         //     } | ||||
|         //     // TODO: Save | ||||
|         // } | ||||
|  | ||||
|         SmGui::LeftLabel("Attenuation"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_kcsdr_att_", _this->name), &_this->att, 0, 31)) { | ||||
|             if (_this->running) { | ||||
|                 kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_kcsdr_gain_", _this->name), &_this->gain, 0, 31)) { | ||||
|             if (_this->running) { | ||||
|                 kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("External Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_kcsdr_ext_gain_", _this->name), &_this->extGain, 0, 31)) { | ||||
|             if (_this->running) { | ||||
|                 kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         // Compute the buffer size | ||||
|         int bufferSize = 0x4000/4;//sampleRate / 200; | ||||
|  | ||||
|         // Allocate the sample buffer | ||||
|         int16_t* samps = dsp::buffer::alloc<int16_t>(bufferSize*2); | ||||
|  | ||||
|         // Loop | ||||
|         while (run) { | ||||
|             // Read samples | ||||
|             int count = kcsdr_rx(openDev, samps, bufferSize); | ||||
|             if (!count) { continue; } | ||||
|             if (count < 0) { | ||||
|                 flog::debug("Failed to read samples: {}", count); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // Convert the samples to float | ||||
|             volk_16i_s32f_convert_32f((float*)stream.writeBuf, samps, 8192.0f, count*2); | ||||
|  | ||||
|             // Send out the samples | ||||
|             if (!stream.swap(count)) { break; } | ||||
|         } | ||||
|  | ||||
|         // Free the sample buffer | ||||
|         dsp::buffer::free(samps); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|  | ||||
|     OptionList<std::string, std::string> devices; | ||||
|     OptionList<int, double> samplerates; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int att = 0; | ||||
|     int gain = 30; | ||||
|     int extGain = 1; | ||||
|     int portId = 0; | ||||
|     std::string selectedSerial; | ||||
|  | ||||
|     kcsdr_t* openDev; | ||||
|  | ||||
|     std::thread workerThread; | ||||
|     std::atomic<bool> run = false; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Nothing here | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new KCSDRSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (KCSDRSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     // Nothing here | ||||
| } | ||||
| @@ -36,10 +36,10 @@ enum SampleType { | ||||
| }; | ||||
|  | ||||
| const size_t SAMPLE_TYPE_SIZE[] { | ||||
|     sizeof(int8_t)*2, | ||||
|     sizeof(int16_t)*2, | ||||
|     sizeof(int32_t)*2, | ||||
|     sizeof(float)*2, | ||||
|     2*sizeof(int8_t), | ||||
|     2*sizeof(int16_t), | ||||
|     2*sizeof(int32_t), | ||||
|     2*sizeof(float), | ||||
| }; | ||||
|  | ||||
| class NetworkSourceModule : public ModuleManager::Instance { | ||||
| @@ -58,20 +58,6 @@ public: | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &stream; | ||||
|  | ||||
|         // Define samplerates | ||||
|         for (int i = 3000; i <= 192000; i <<= 1) { | ||||
|             samplerates.define(i, getSrScaled(i), i); | ||||
|         } | ||||
|         for (int i = 250000; i < 1000000; i += 250000) { | ||||
|             samplerates.define(i, getSrScaled(i), i); | ||||
|         } | ||||
|         for (int i = 1000000; i < 10000000; i += 500000) { | ||||
|             samplerates.define(i, getSrScaled(i), i); | ||||
|         } | ||||
|         for (int i = 10000000; i <= 100000000; i += 5000000) { | ||||
|             samplerates.define(i, getSrScaled(i), i); | ||||
|         } | ||||
|  | ||||
|         // Define protocols | ||||
|         // protocols.define("TCP (Server)", PROTOCOL_TCP_SERVER); | ||||
|         protocols.define("TCP (Client)", PROTOCOL_TCP_CLIENT); | ||||
| @@ -86,8 +72,8 @@ public: | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         if (config.conf[name].contains("samplerate")) { | ||||
|             int sr = config.conf[name]["samplerate"]; | ||||
|             if (samplerates.keyExists(sr)) { samplerate = samplerates.value(samplerates.keyId(sr)); } | ||||
|             samplerate = config.conf[name]["samplerate"]; | ||||
|             tempSamplerate = samplerate; | ||||
|         } | ||||
|         if (config.conf[name].contains("protocol")) { | ||||
|             std::string protoStr = config.conf[name]["protocol"]; | ||||
| @@ -108,7 +94,6 @@ public: | ||||
|         config.release(); | ||||
|  | ||||
|         // Set menu IDs | ||||
|         srId = samplerates.valueId(samplerate); | ||||
|         protoId = protocols.valueId(proto); | ||||
|         sampTypeId = sampleTypes.valueId(sampType); | ||||
|  | ||||
| @@ -228,35 +213,24 @@ private: | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         // Hostname and port field | ||||
|         if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) { | ||||
|         if (SmGui::InputText(("##network_source_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) { | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["host"] = _this->hostname; | ||||
|             config.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) { | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::InputInt(("##network_source_port_" + _this->name).c_str(), &_this->port, 0, 0)) { | ||||
|             _this->port = std::clamp<int>(_this->port, 1, 65535); | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["port"] = _this->port; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         // Samplerate selector | ||||
|         ImGui::LeftLabel("Samplerate"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo(("##iq_exporter_sr_" + _this->name).c_str(), &_this->srId, _this->samplerates.txt)) { | ||||
|             _this->samplerate = _this->samplerates.value(_this->srId); | ||||
|             core::setInputSampleRate(_this->samplerate); | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["samplerate"] = _this->samplerates.key(_this->srId); | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         // Mode protocol selector | ||||
|         ImGui::LeftLabel("Protocol"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) { | ||||
|         SmGui::LeftLabel("Protocol"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(("##network_source_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) { | ||||
|             _this->proto = _this->protocols.value(_this->protoId); | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["protocol"] = _this->protocols.key(_this->protoId); | ||||
| @@ -264,15 +238,38 @@ private: | ||||
|         } | ||||
|  | ||||
|         // Sample type selector | ||||
|         ImGui::LeftLabel("Sample type"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) { | ||||
|         SmGui::LeftLabel("Sample type"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(("##network_source_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) { | ||||
|             _this->sampType = _this->sampleTypes.value(_this->sampTypeId); | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampTypeId); | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         // Samplerate selector | ||||
|         SmGui::LeftLabel("Samplerate"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::InputInt(("##network_source_sr_" + _this->name).c_str(), &_this->tempSamplerate)) { | ||||
|             // Prevent silly values from silly users | ||||
|             _this->tempSamplerate = std::max<int>(_this->tempSamplerate, 1000); | ||||
|         } | ||||
|         bool applyEn = (!_this->running && _this->tempSamplerate != _this->samplerate); | ||||
|         if (!applyEn) { SmGui::BeginDisabled(); } | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Button(("Apply##network_source_apply_" + _this->name).c_str())) { | ||||
|             _this->samplerate = _this->tempSamplerate; | ||||
|             core::setInputSampleRate(_this->samplerate); | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["samplerate"] = _this->samplerate; | ||||
|             config.release(true); | ||||
|         } | ||||
|         if (!applyEn) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         if (_this->tempSamplerate != _this->samplerate) { | ||||
|             SmGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Warning: Samplerate not applied yet"); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|     } | ||||
|  | ||||
| @@ -280,14 +277,17 @@ private: | ||||
|         // Compute sizes | ||||
|         int blockSize = samplerate / 200; | ||||
|         int sampleSize = SAMPLE_TYPE_SIZE[sampType]; | ||||
|         int frameSize = blockSize*sampleSize; | ||||
|  | ||||
|         // Chose amount of bytes to attempt to read | ||||
|         bool forceSize = (proto != PROTOCOL_UDP); | ||||
|         int frameSize = sampleSize * (forceSize ? blockSize : STREAM_BUFFER_SIZE); | ||||
|  | ||||
|         // Allocate receive buffer | ||||
|         uint8_t* buffer = dsp::buffer::alloc<uint8_t>(frameSize); | ||||
|  | ||||
|         while (true) { | ||||
|             // Read samples from socket | ||||
|             int bytes = sock->recv(buffer, frameSize, true); | ||||
|             int bytes = sock->recv(buffer, frameSize, forceSize); | ||||
|             if (bytes <= 0) { break; } | ||||
|  | ||||
|             // Convert to CF32 (note: problem if partial sample) | ||||
| @@ -325,7 +325,7 @@ private: | ||||
|     double freq; | ||||
|      | ||||
|     int samplerate = 1000000; | ||||
|     int srId; | ||||
|     int tempSamplerate = 1000000; | ||||
|     Protocol proto = PROTOCOL_UDP; | ||||
|     int protoId; | ||||
|     SampleType sampType = SAMPLE_TYPE_INT16; | ||||
| @@ -333,7 +333,6 @@ private: | ||||
|     char hostname[1024] = "localhost"; | ||||
|     int port = 1234; | ||||
|  | ||||
|     OptionList<int, int> samplerates; | ||||
|     OptionList<std::string, Protocol> protocols; | ||||
|     OptionList<std::string, SampleType> sampleTypes; | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,12 @@ SDRPP_MOD_INFO{ | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| const std::vector<const char*> deviceWhiteList = { | ||||
|     "PlutoSDR", | ||||
|     "ANTSDR", | ||||
|     "LibreSDR" | ||||
| }; | ||||
|  | ||||
| class PlutoSDRSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     PlutoSDRSourceModule(std::string name) { | ||||
| @@ -130,7 +136,14 @@ private: | ||||
|             std::string duri = iio_context_info_get_uri(info); | ||||
|  | ||||
|             // If the device is not a plutosdr, don't include it | ||||
|             if (desc.find("PlutoSDR") == std::string::npos) { | ||||
|             bool isPluto = false; | ||||
|             for (const auto type : deviceWhiteList) { | ||||
|                 if (desc.find(type) != std::string::npos) { | ||||
|                     isPluto = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (!isPluto) { | ||||
|                 flog::warn("Ignored IIO device: [{}] {}", duri, desc); | ||||
|                 continue; | ||||
|             } | ||||
|   | ||||
| @@ -523,8 +523,8 @@ private: | ||||
|         RTLSDRSourceModule* _this = (RTLSDRSourceModule*)ctx; | ||||
|         int sampCount = len / 2; | ||||
|         for (int i = 0; i < sampCount; i++) { | ||||
|             _this->stream.writeBuf[i].re = ((float)buf[i * 2] - 127.4f) / 128.0f; | ||||
|             _this->stream.writeBuf[i].im = ((float)buf[(i * 2) + 1] - 127.4f) / 128.0f; | ||||
|             _this->stream.writeBuf[i].re = ((float)buf[i * 2] - 127.4) / 128.0f; | ||||
|             _this->stream.writeBuf[i].im = ((float)buf[(i * 2) + 1] - 127.4) / 128.0f; | ||||
|         } | ||||
|         if (!_this->stream.swap(sampCount)) { return; } | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user