mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			119 Commits
		
	
	
		
			new_source
			...
			nightly
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 052167962d | ||
|  | 6fc41a81a7 | ||
|  | e710b6c6dc | ||
|  | a91434c5fe | ||
|  | ead2ac6128 | ||
|  | 505cbb0ba2 | ||
|  | b1030cbdfb | ||
|  | f3c5b2c31f | ||
|  | 794d6ff5ac | ||
|  | 4803271115 | ||
|  | 78daed7879 | ||
|  | 5f297b1a69 | ||
|  | 7c5d4226eb | ||
|  | ec086ebbdf | ||
|  | d10d420467 | ||
|  | 5bf989f49d | ||
|  | 4b3b6976d6 | ||
|  | 55ddd383d2 | ||
|  | a043ab2dd3 | ||
|  | d270e1c5e8 | ||
|  | e41f24a95e | ||
|  | 766b3db363 | ||
|  | 0632342bb7 | ||
|  | 27b07ed0e8 | ||
|  | a824c83848 | ||
|  | 0e50ee0e67 | ||
|  | a55d1d9c06 | ||
|  | 97187b790f | ||
|  | 9c1361a8a9 | ||
|  | 4d0d14856b | ||
|  | 365ab2325e | ||
|  | 4da7e686f3 | ||
|  | c1d9ab64f8 | ||
|  | ea33135bf1 | ||
|  | 2081384905 | ||
|  | 6b31134af2 | ||
|  | 99d2786c25 | ||
|  | 320f4459ed | ||
|  | de5816f79f | ||
|  | 7b9c01ec73 | ||
|  | f06eccd97c | ||
|  | 88baa8a48e | ||
|  | b436fd0745 | ||
|  | 6fa4299136 | ||
|  | 220dcbcc76 | ||
|  | 15ad065feb | ||
|  | dddf84510e | ||
|  | acd9ad9781 | ||
|  | 168e28cc44 | ||
|  | 3b9867c1d7 | ||
|  | ff7ef78b8f | ||
|  | 87add9ad83 | ||
|  | 6cd09f9b60 | ||
|  | 8d05c1e181 | ||
|  | 1c081cad78 | ||
|  | aa929a1e79 | ||
|  | 5acdab0d22 | ||
|  | 3e3846daa1 | ||
|  | 47617e1acd | ||
|  | 1df51020aa | ||
|  | aa1fd9e573 | ||
|  | 21816fb438 | ||
|  | 3a06612ff5 | ||
|  | a53bf05ed3 | ||
|  | 78c57db116 | ||
|  | c16281f68a | ||
|  | 32c580ba57 | ||
|  | 664b5d85e2 | ||
|  | 6bcb62bfb2 | ||
|  | 0e7c754b8b | ||
|  | 71327cd695 | ||
|  | f296730302 | ||
|  | b89fdba433 | ||
|  | 2c3b522787 | ||
|  | 528763d10e | ||
|  | 582aeed640 | ||
|  | 9c0b57a036 | ||
|  | 604f95fd96 | ||
|  | c892e51000 | ||
|  | 3336ae4aa5 | ||
|  | 190cea8e4e | ||
|  | 13a268a3e1 | ||
|  | 800a8b22c7 | ||
|  | 2eb030dd83 | ||
|  | 365fe1930c | ||
|  | 342a677c3f | ||
|  | b5c41bcb3a | ||
|  | f578adceef | ||
|  | a9f882e5b1 | ||
|  | 3420808f3a | ||
|  | d3d245992d | ||
|  | 9a3414b847 | ||
|  | 90c26f8c1b | ||
|  | ec4dc6cc9e | ||
|  | 93b28d1495 | ||
|  | 84291deaf6 | ||
|  | 21e0696917 | ||
|  | 19247ef4f2 | ||
|  | 1e5601e773 | ||
|  | ae1fd87f02 | ||
|  | ab2aee316c | ||
|  | 109374277e | ||
|  | 692436f6e4 | ||
|  | eccb715d0c | ||
|  | f6f074e0c7 | ||
|  | 50a77a7e60 | ||
|  | a4f3c92a03 | ||
|  | 37920b6476 | ||
|  | 314b8bf72d | ||
|  | 5f0858bab2 | ||
|  | 007761a027 | ||
|  | 801f1be6b2 | ||
|  | 9cc793e328 | ||
|  | 1e01313612 | ||
|  | 4283cacae6 | ||
|  | 6f9dacdd53 | ||
|  | e64c343645 | ||
|  | b9effce7d6 | ||
|  | 45a13227de | 
							
								
								
									
										7
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| # Important | ||||
|  | ||||
| Only minor bug fixes and bandplans are accepted. | ||||
|  | ||||
| Pull requests adding features or any bug fix that requires significant code changes will be automatically rejected. | ||||
|  | ||||
| Open an issue requesting a feature or discussing a possible bugfix instead. | ||||
							
								
								
									
										79
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										79
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							| @@ -34,10 +34,10 @@ jobs: | ||||
|  | ||||
|         - name: Patch Pothos with earlier libusb version | ||||
|           working-directory: ${{runner.workspace}} | ||||
|           run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" | ||||
|           run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/" | ||||
|   | ||||
|         - name: Download SDRPlay API | ||||
|           run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu" -OutFile ${{runner.workspace}}/SDRPlay.zip | ||||
|           run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu&confirm=t" -OutFile ${{runner.workspace}}/SDRPlay.zip | ||||
|  | ||||
|         - name: Install SDRPlay API | ||||
|           run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/" | ||||
| @@ -58,14 +58,17 @@ jobs: | ||||
|           run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2" | ||||
|  | ||||
|         - name: Install vcpkg dependencies | ||||
|           run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows | ||||
|           run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows | ||||
|  | ||||
|         - name: Install rtaudio | ||||
|           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" | ||||
|  | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.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 | ||||
|           run: cmake "$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 | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
| @@ -93,11 +96,8 @@ jobs: | ||||
|         - name: Update brew repositories | ||||
|           run: brew update | ||||
|  | ||||
|         - name: Fix stuff | ||||
|           run: rm -f /usr/local/bin/2to3* /usr/local/bin/idle3* /usr/local/bin/pydoc3* /usr/local/bin/python3* /usr/local/bin/python3-config* && brew reinstall gettext | ||||
|  | ||||
|         - name: Install dependencies | ||||
|           run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2 zstd && pip3 install mako | ||||
|           run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako | ||||
|  | ||||
|         - name: Install volk | ||||
|           run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
| @@ -106,7 +106,7 @@ jobs: | ||||
|           run: wget https://www.sdrplay.com/software/SDRplay_RSP_API-MacOSX-3.07.3.pkg && sudo installer -pkg SDRplay_RSP_API-MacOSX-3.07.3.pkg -target / | ||||
|  | ||||
|         - name: Install libiio | ||||
|           run: git clone https://github.com/analogdevicesinc/libiio && cd libiio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
|           run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Install libad9361 | ||||
|           run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
| @@ -114,9 +114,15 @@ jobs: | ||||
|         - name: Install LimeSuite | ||||
|           run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Install libperseus | ||||
|           run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && 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_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 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -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_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|           run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -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 -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
| @@ -131,13 +137,13 @@ jobs: | ||||
|           with: | ||||
|               name: sdrpp_macos_intel | ||||
|               path: ${{runner.workspace}}/sdrpp_macos_intel.zip | ||||
|    | ||||
|  | ||||
|     build_debian_buster: | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
|         steps: | ||||
|         - uses: actions/checkout@v3 | ||||
|          | ||||
|  | ||||
|         - name: Create Docker Image | ||||
|           run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build | ||||
|  | ||||
| @@ -176,6 +182,28 @@ jobs: | ||||
|               name: sdrpp_debian_bullseye_amd64 | ||||
|               path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     build_debian_bookworm: | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
|         steps: | ||||
|         - uses: actions/checkout@v3 | ||||
|          | ||||
|         - name: Create Docker Image | ||||
|           run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build | ||||
|  | ||||
|         - name: Run Container | ||||
|           run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh | ||||
|  | ||||
|         - name: Recover Deb Archive | ||||
|           working-directory: ${{runner.workspace}} | ||||
|           run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ | ||||
|  | ||||
|         - name: Save Deb Archive | ||||
|           uses: actions/upload-artifact@v3 | ||||
|           with: | ||||
|               name: sdrpp_debian_bookworm_amd64 | ||||
|               path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     build_debian_sid: | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
| @@ -198,28 +226,6 @@ jobs: | ||||
|               name: sdrpp_debian_sid_amd64 | ||||
|               path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     # build_ubuntu_bionic: | ||||
|     #     runs-on: ubuntu-latest | ||||
|  | ||||
|     #     steps: | ||||
|     #     - uses: actions/checkout@v3 | ||||
|          | ||||
|     #     - name: Create Docker Image | ||||
|     #       run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && docker build . --tag sdrpp_build | ||||
|  | ||||
|     #     - name: Run Container | ||||
|     #       run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh | ||||
|  | ||||
|     #     - name: Recover Deb Archive | ||||
|     #       working-directory: ${{runner.workspace}} | ||||
|     #       run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ | ||||
|  | ||||
|     #     - name: Save Deb Archive | ||||
|     #       uses: actions/upload-artifact@v3 | ||||
|     #       with: | ||||
|     #           name: sdrpp_ubuntu_bionic_amd64 | ||||
|     #           path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     build_ubuntu_focal: | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
| @@ -275,7 +281,7 @@ jobs: | ||||
|          | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake $GITHUB_WORKSPACE -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_USRP_SOURCE=ON | ||||
|           run: cmake $GITHUB_WORKSPACE -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_USRP_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
| @@ -319,7 +325,7 @@ jobs: | ||||
|               path: ${{runner.workspace}}/sdrpp.apk | ||||
|  | ||||
|     create_full_archive: | ||||
|         needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_raspios_bullseye_armhf', 'build_android'] | ||||
|         needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_raspios_bullseye_armhf', 'build_android'] | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
|         steps: | ||||
| @@ -333,6 +339,7 @@ jobs: | ||||
|             mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ &&  | ||||
|             mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&  | ||||
|             mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&  | ||||
|             mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&  | ||||
|             mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&  | ||||
|             mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb && | ||||
|             mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb && | ||||
|   | ||||
| @@ -17,24 +17,25 @@ option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) | ||||
| option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON) | ||||
| option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF) | ||||
| option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF) | ||||
| option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON) | ||||
| option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON) | ||||
| option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF) | ||||
| option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON) | ||||
| option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF) | ||||
| option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON) | ||||
| option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF) | ||||
|  | ||||
| # Sinks | ||||
| option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF) | ||||
| option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON) | ||||
| option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF) | ||||
| option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF) | ||||
| option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF) | ||||
|  | ||||
| # Decoders | ||||
| option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF) | ||||
| @@ -141,9 +142,13 @@ if (OPT_BUILD_LIMESDR_SOURCE) | ||||
| add_subdirectory("source_modules/limesdr_source") | ||||
| endif (OPT_BUILD_LIMESDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
| add_subdirectory("source_modules/sdrpp_server_source") | ||||
| endif (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
| if (OPT_BUILD_PERSEUS_SOURCE) | ||||
| add_subdirectory("source_modules/perseus_source") | ||||
| endif (OPT_BUILD_PERSEUS_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
| add_subdirectory("source_modules/plutosdr_source") | ||||
| endif (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_RFSPACE_SOURCE) | ||||
| add_subdirectory("source_modules/rfspace_source") | ||||
| @@ -157,6 +162,10 @@ if (OPT_BUILD_RTL_TCP_SOURCE) | ||||
| add_subdirectory("source_modules/rtl_tcp_source") | ||||
| endif (OPT_BUILD_RTL_TCP_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
| add_subdirectory("source_modules/sdrpp_server_source") | ||||
| endif (OPT_BUILD_SDRPP_SERVER_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_SDRPLAY_SOURCE) | ||||
| add_subdirectory("source_modules/sdrplay_source") | ||||
| endif (OPT_BUILD_SDRPLAY_SOURCE) | ||||
| @@ -177,10 +186,6 @@ if (OPT_BUILD_SPYSERVER_SOURCE) | ||||
| add_subdirectory("source_modules/spyserver_source") | ||||
| endif (OPT_BUILD_SPYSERVER_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
| add_subdirectory("source_modules/plutosdr_source") | ||||
| endif (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_USRP_SOURCE) | ||||
| add_subdirectory("source_modules/usrp_source") | ||||
| endif (OPT_BUILD_USRP_SOURCE) | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| # Pull Requests | ||||
|  | ||||
| TODO | ||||
| **I DO NOT ACCEPT PULL-REQUEST FOR FEATURES OR BUGFIXES REQUIRING SIGNIFICANT CODE/STRUCTURE CHANGES.** | ||||
| **SUCH PULL REQUESTS WILL BE CLOSED AUTOMATICALLY. OPEN AN ISSUE DETAILING FEATURE REQUESTS OR POTENTIAL BUGFIX INSTEAD.** | ||||
|  | ||||
| # Code Style | ||||
|  | ||||
| @@ -122,4 +123,4 @@ Please follow this guide to properly format the JSON files for custom color maps | ||||
|  | ||||
| * All additions and/or bug fixes to the core must not add additional dependencies. | ||||
| * Use VSCode for development, VS seems to cause issues. | ||||
| * DO NOT use libboost for any code meant for this repository | ||||
| * DO NOT use libboost for any code meant for this repository | ||||
|   | ||||
| @@ -13,6 +13,7 @@ endif (USE_BUNDLE_DEFAULTS) | ||||
| file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") | ||||
|  | ||||
| add_definitions(-DSDRPP_IS_CORE) | ||||
| add_definitions(-DFLOG_ANDROID_TAG="SDR++") | ||||
| if (MSVC) | ||||
|     set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) | ||||
| endif () | ||||
| @@ -107,7 +108,6 @@ elseif (ANDROID) | ||||
|     ) | ||||
|  | ||||
|     target_link_libraries(sdrpp_core PUBLIC | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/libcpu_features.a | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/libvolk.so | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/libzstd.so | ||||
|   | ||||
| @@ -117,6 +117,10 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["colorMap"] = "Classic"; | ||||
|     defConfig["fftHold"] = false; | ||||
|     defConfig["fftHoldSpeed"] = 60; | ||||
|     defConfig["fftSmoothing"] = false; | ||||
|     defConfig["fftSmoothingSpeed"] = 100; | ||||
|     defConfig["snrSmoothing"] = false; | ||||
|     defConfig["snrSmoothingSpeed"] = 20; | ||||
|     defConfig["fastFFT"] = false; | ||||
|     defConfig["fftHeight"] = 300; | ||||
|     defConfig["fftRate"] = 20; | ||||
| @@ -177,6 +181,8 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source"; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source"; | ||||
|     defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source"; | ||||
|     defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source"; | ||||
|   | ||||
| @@ -12,6 +12,7 @@ namespace sdrpp_credits { | ||||
|         "Howard0su", | ||||
|         "John Donkersley", | ||||
|         "Joshua Kimsey", | ||||
|         "Manawyrm", | ||||
|         "Martin Hauke", | ||||
|         "Marvin Sinister", | ||||
|         "Maxime Biette", | ||||
| @@ -21,7 +22,6 @@ namespace sdrpp_credits { | ||||
|         "Shuyuan Liu", | ||||
|         "Syne Ardwin (WI9SYN)", | ||||
|         "Szymon Zakrent", | ||||
|         "Tobias Mädel", | ||||
|         "Youssef Touil", | ||||
|         "Zimm" | ||||
|     }; | ||||
| @@ -55,24 +55,33 @@ namespace sdrpp_credits { | ||||
|         "Dale L Puckett (K0HYD)", | ||||
|         "Daniele D'Agnelli", | ||||
|         "D. Jones", | ||||
|         "Dexruus", | ||||
|         "EB3FRN", | ||||
|         "Eric Johnson", | ||||
|         "Ernest Murphy (NH7L)", | ||||
|         "Flinger Films", | ||||
|         "Frank Werner (HB9FXQ)", | ||||
|         "gringogrigio", | ||||
|         "Jeff Moe", | ||||
|         "Joe Cupano", | ||||
|         "KD1SQ", | ||||
|         "Kezza", | ||||
|         "Krys Kamieniecki", | ||||
|         "Lee Donaghy", | ||||
|         "Lee KD1SQ", | ||||
|         ".lozenge. (Hank Hill)", | ||||
|         "Martin Herren (HB9FXX)", | ||||
|         "ON4MU", | ||||
|         "Passion-Radio.com", | ||||
|         "Paul Maine", | ||||
|         "Peter Betz", | ||||
|         "Scanner School", | ||||
|         "Scott Palmer", | ||||
|         "SignalsEverywhere", | ||||
|         "Syne Ardwin (WI9SYN)", | ||||
|         "W4IPA", | ||||
|         "William Arcand (W1WRA)", | ||||
|         "Yves Rougy", | ||||
|         "Zipper" | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -67,10 +67,6 @@ namespace dsp::buffer { | ||||
|                 sizes[writeCur] = count; | ||||
|                 writeCur++; | ||||
|                 writeCur = ((writeCur) % TEST_BUFFER_SIZE); | ||||
|  | ||||
|                 // if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) { | ||||
|                 //     flog::warn("Overflow"); | ||||
|                 // } | ||||
|             } | ||||
|             cnd.notify_all(); | ||||
|             _in->flush(); | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| #include "quadrature.h" | ||||
| #include "../filter/fir.h" | ||||
| #include "../taps/low_pass.h" | ||||
| #include "../taps/high_pass.h" | ||||
| #include "../taps/band_pass.h" | ||||
| #include "../convert/mono_to_stereo.h" | ||||
|  | ||||
| namespace dsp::demod { | ||||
| @@ -17,22 +19,26 @@ namespace dsp::demod { | ||||
|         ~FM() { | ||||
|             if (!base_type::_block_init) { return; } | ||||
|             base_type::stop(); | ||||
|             dsp::taps::free(lpfTaps); | ||||
|             dsp::taps::free(filterTaps); | ||||
|         } | ||||
|  | ||||
|         void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) { | ||||
|         void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass, bool highPass) { | ||||
|             _samplerate = samplerate; | ||||
|             _bandwidth = bandwidth; | ||||
|             _lowPass = lowPass; | ||||
|             _highPass = highPass; | ||||
|  | ||||
|             demod.init(NULL, bandwidth / 2.0, _samplerate); | ||||
|             lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate); | ||||
|             lpf.init(NULL, lpfTaps); | ||||
|             loadDummyTaps(); | ||||
|             fir.init(NULL, filterTaps); | ||||
|  | ||||
|             // Initialize taps | ||||
|             updateFilter(lowPass, highPass); | ||||
|  | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 demod.out.free(); | ||||
|             } | ||||
|             lpf.out.free(); | ||||
|             fir.out.free(); | ||||
|  | ||||
|             base_type::init(in); | ||||
|         } | ||||
| @@ -43,9 +49,7 @@ namespace dsp::demod { | ||||
|             base_type::tempStop(); | ||||
|             _samplerate = samplerate; | ||||
|             demod.setDeviation(_bandwidth / 2.0, _samplerate); | ||||
|             dsp::taps::free(lpfTaps); | ||||
|             lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate); | ||||
|             lpf.setTaps(lpfTaps); | ||||
|             updateFilter(_lowPass, _highPass); | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
| @@ -54,19 +58,20 @@ namespace dsp::demod { | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             if (bandwidth == _bandwidth) { return; } | ||||
|             _bandwidth = bandwidth; | ||||
|             std::lock_guard<std::mutex> lck2(lpfMtx); | ||||
|             demod.setDeviation(_bandwidth / 2.0, _samplerate); | ||||
|             dsp::taps::free(lpfTaps); | ||||
|             lpfTaps = dsp::taps::lowPass(_bandwidth / 2, (_bandwidth / 2) * 0.1, _samplerate); | ||||
|             lpf.setTaps(lpfTaps); | ||||
|             updateFilter(_lowPass, _highPass); | ||||
|         } | ||||
|  | ||||
|         void setLowPass(bool lowPass) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             std::lock_guard<std::mutex> lck2(lpfMtx); | ||||
|             _lowPass = lowPass; | ||||
|             lpf.reset(); | ||||
|             updateFilter(lowPass, _highPass); | ||||
|         } | ||||
|  | ||||
|         void setHighPass(bool highPass) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             updateFilter(_lowPass, highPass); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
| @@ -74,23 +79,23 @@ namespace dsp::demod { | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             demod.reset(); | ||||
|             lpf.reset(); | ||||
|             fir.reset(); | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         inline int process(int count, dsp::complex_t* in, T* out) { | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 demod.process(count, in, out); | ||||
|                 if (_lowPass) { | ||||
|                     std::lock_guard<std::mutex> lck(lpfMtx); | ||||
|                     lpf.process(count, out, out); | ||||
|                 if (filtering) { | ||||
|                     std::lock_guard<std::mutex> lck(filterMtx); | ||||
|                     fir.process(count, out, out); | ||||
|                 } | ||||
|             } | ||||
|             if constexpr (std::is_same_v<T, stereo_t>) { | ||||
|                 demod.process(count, in, demod.out.writeBuf); | ||||
|                 if (_lowPass) { | ||||
|                     std::lock_guard<std::mutex> lck(lpfMtx); | ||||
|                     lpf.process(count, demod.out.writeBuf, demod.out.writeBuf); | ||||
|                 if (filtering) { | ||||
|                     std::lock_guard<std::mutex> lck(filterMtx); | ||||
|                     fir.process(count, demod.out.writeBuf, demod.out.writeBuf); | ||||
|                 } | ||||
|                 convert::MonoToStereo::process(count, demod.out.writeBuf, out); | ||||
|             } | ||||
| @@ -109,13 +114,50 @@ namespace dsp::demod { | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         void updateFilter(bool lowPass, bool highPass) { | ||||
|             std::lock_guard<std::mutex> lck(filterMtx); | ||||
|  | ||||
|             // Update values | ||||
|             _lowPass = lowPass; | ||||
|             _highPass = highPass; | ||||
|             filtering = (lowPass || highPass); | ||||
|  | ||||
|             // Free filter taps | ||||
|             dsp::taps::free(filterTaps); | ||||
|  | ||||
|             // Generate filter depending on low and high pass settings | ||||
|             if (_lowPass && _highPass) { | ||||
|                 filterTaps = dsp::taps::bandPass<float>(300.0, _bandwidth / 2.0, 100.0, _samplerate); | ||||
|             } | ||||
|             else if (_highPass) { | ||||
|                 filterTaps = dsp::taps::highPass(300.0, 100.0, _samplerate); | ||||
|             } | ||||
|             else if (_lowPass) { | ||||
|                 filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate); | ||||
|             } | ||||
|             else { | ||||
|                 loadDummyTaps(); | ||||
|             } | ||||
|  | ||||
|             // Set filter to use new taps | ||||
|             fir.setTaps(filterTaps); | ||||
|             fir.reset(); | ||||
|         } | ||||
|  | ||||
|         void loadDummyTaps() { | ||||
|             float dummyTap = 1.0f; | ||||
|             filterTaps = dsp::taps::fromArray<float>(1, &dummyTap); | ||||
|         } | ||||
|  | ||||
|         double _samplerate; | ||||
|         double _bandwidth; | ||||
|         bool _lowPass; | ||||
|         bool _highPass; | ||||
|         bool filtering; | ||||
|  | ||||
|         Quadrature demod; | ||||
|         tap<float> lpfTaps; | ||||
|         filter::FIR<float, float> lpf; | ||||
|         std::mutex lpfMtx; | ||||
|         tap<float> filterTaps; | ||||
|         filter::FIR<float, float> fir; | ||||
|         std::mutex filterMtx; | ||||
|     }; | ||||
| } | ||||
| @@ -2,5 +2,6 @@ | ||||
| #include "../multirate/rrc_interpolator.h" | ||||
|  | ||||
| namespace dsp::mod { | ||||
|     // TODO: Check if resample before RRC is better than using the RRC taps as a filter (bandwidth probably not correct for alias-free resampling) | ||||
|     typedef multirate::RRCInterpolator<complex_t> PSK; | ||||
| } | ||||
| @@ -5,7 +5,7 @@ | ||||
| #include "../math/hz_to_rads.h" | ||||
|  | ||||
| namespace dsp::mod { | ||||
|     class Quadrature : Processor<float, complex_t> { | ||||
|     class Quadrature : public Processor<float, complex_t> { | ||||
|         using base_type = Processor<float, complex_t>; | ||||
|     public: | ||||
|         Quadrature() {} | ||||
|   | ||||
| @@ -83,8 +83,6 @@ namespace dsp::multirate { | ||||
|             int interp = OutSR / gcd; | ||||
|             int decim = InSR / gcd; | ||||
|  | ||||
|             flog::warn("interp: {0}, decim: {1}", interp, decim); | ||||
|  | ||||
|             // Configure resampler | ||||
|             double tapSamplerate = _symbolrate * (double)interp; | ||||
|             rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount * interp, _rrcBeta, _symbolrate, tapSamplerate); | ||||
|   | ||||
| @@ -15,7 +15,7 @@ namespace dsp::taps { | ||||
|         if (oddTapCount && !(count % 2)) { count++; } | ||||
|         return windowedSinc<T>(count, (bandStop - bandStart) / 2.0, sampleRate, [=](double n, double N) { | ||||
|             if constexpr (std::is_same_v<T, float>) { | ||||
|                 return cosf(offsetOmega * (float)n) * window::nuttall(n, N); | ||||
|                 return 2.0f * cosf(offsetOmega * (float)n) * window::nuttall(n, N); | ||||
|             } | ||||
|             if constexpr (std::is_same_v<T, complex_t>) { | ||||
|                 // The offset is negative to flip the taps. Complex bandpass are asymetric | ||||
|   | ||||
| @@ -82,7 +82,7 @@ namespace dsp { | ||||
|  | ||||
|         inline float fastAmplitude() { | ||||
|             float re_abs = fabsf(re); | ||||
|             float im_abs = fabsf(re); | ||||
|             float im_abs = fabsf(im); | ||||
|             if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; } | ||||
|             return im_abs + 0.4f * re_abs; | ||||
|         } | ||||
| @@ -125,4 +125,4 @@ namespace dsp { | ||||
|         float l; | ||||
|         float r; | ||||
|     }; | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -574,10 +574,22 @@ void MainWindow::draw() { | ||||
|         // Handle scrollwheel | ||||
|         int wheel = ImGui::GetIO().MouseWheel; | ||||
|         if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) { | ||||
|             // Select factor depending on modifier keys | ||||
|             double interval; | ||||
|             if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { | ||||
|                 interval = vfo->snapInterval * 10.0; | ||||
|             } | ||||
|             else if (ImGui::IsKeyDown(ImGuiKey_LeftAlt)) { | ||||
|                 interval = vfo->snapInterval * 0.1; | ||||
|             } | ||||
|             else { | ||||
|                 interval = vfo->snapInterval; | ||||
|             } | ||||
|  | ||||
|             double nfreq; | ||||
|             if (vfo != NULL) { | ||||
|                 nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (vfo->snapInterval * wheel); | ||||
|                 nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval; | ||||
|                 nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (interval * wheel); | ||||
|                 nfreq = roundl(nfreq / interval) * interval; | ||||
|             } | ||||
|             else { | ||||
|                 nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <gui/style.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| namespace displaymenu { | ||||
|     bool showWaterfall; | ||||
| @@ -22,6 +23,10 @@ namespace displaymenu { | ||||
|     bool restartRequired = false; | ||||
|     bool fftHold = false; | ||||
|     int fftHoldSpeed = 60; | ||||
|     bool fftSmoothing = false; | ||||
|     int fftSmoothingSpeed = 100; | ||||
|     bool snrSmoothing = false; | ||||
|     int snrSmoothingSpeed = 20; | ||||
|  | ||||
|     OptionList<float, float> uiScales; | ||||
|  | ||||
| @@ -57,8 +62,10 @@ namespace displaymenu { | ||||
|         IQFrontEnd::FFTWindow::NUTTALL | ||||
|     }; | ||||
|  | ||||
|     void updateFFTHoldSpeed() { | ||||
|         gui::waterfall.setFFTHoldSpeed(fftHoldSpeed / (fftRate * 10.0f)); | ||||
|     void updateFFTSpeeds() { | ||||
|         gui::waterfall.setFFTHoldSpeed((float)fftHoldSpeed / ((float)fftRate * 10.0f)); | ||||
|         gui::waterfall.setFFTSmoothingSpeed(std::min<float>((float)fftSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f)); | ||||
|         gui::waterfall.setSNRSmoothingSpeed(std::min<float>((float)snrSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f)); | ||||
|     } | ||||
|  | ||||
|     void init() { | ||||
| @@ -104,7 +111,13 @@ namespace displaymenu { | ||||
|         fftHold = core::configManager.conf["fftHold"]; | ||||
|         fftHoldSpeed = core::configManager.conf["fftHoldSpeed"]; | ||||
|         gui::waterfall.setFFTHold(fftHold); | ||||
|         updateFFTHoldSpeed(); | ||||
|         fftSmoothing = core::configManager.conf["fftSmoothing"]; | ||||
|         fftSmoothingSpeed = core::configManager.conf["fftSmoothingSpeed"]; | ||||
|         gui::waterfall.setFFTSmoothing(fftSmoothing); | ||||
|         snrSmoothing = core::configManager.conf["snrSmoothing"]; | ||||
|         snrSmoothingSpeed = core::configManager.conf["snrSmoothingSpeed"]; | ||||
|         gui::waterfall.setSNRSmoothing(snrSmoothing); | ||||
|         updateFFTSpeeds(); | ||||
|  | ||||
|         // Define and load UI scales | ||||
|         uiScales.define(1.0f, "100%", 1.0f); | ||||
| @@ -144,16 +157,47 @@ namespace displaymenu { | ||||
|             core::configManager.conf["fftHold"] = fftHold; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         ImGui::LeftLabel("FFT Hold Speed"); | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) { | ||||
|             updateFFTHoldSpeed(); | ||||
|             updateFFTSpeeds(); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         if (ImGui::Checkbox("FFT Smoothing##_sdrpp", &fftSmoothing)) { | ||||
|             gui::waterfall.setFFTSmoothing(fftSmoothing); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["fftSmoothing"] = fftSmoothing; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::InputInt("##sdrpp_fft_smoothing_speed", &fftSmoothingSpeed)) { | ||||
|             fftSmoothingSpeed = std::max<int>(fftSmoothingSpeed, 1); | ||||
|             updateFFTSpeeds(); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["fftSmoothingSpeed"] = fftSmoothingSpeed; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         if (ImGui::Checkbox("SNR Smoothing##_sdrpp", &snrSmoothing)) { | ||||
|             gui::waterfall.setSNRSmoothing(snrSmoothing); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["snrSmoothing"] = snrSmoothing; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::InputInt("##sdrpp_snr_smoothing_speed", &snrSmoothingSpeed)) { | ||||
|             snrSmoothingSpeed = std::max<int>(snrSmoothingSpeed, 1); | ||||
|             updateFFTSpeeds(); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["snrSmoothingSpeed"] = snrSmoothingSpeed; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|         ImGui::LeftLabel("High-DPI Scaling"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) { | ||||
| @@ -168,7 +212,7 @@ namespace displaymenu { | ||||
|         if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) { | ||||
|             fftRate = std::max<int>(1, fftRate); | ||||
|             sigpath::iqFrontEnd.setFFTRate(fftRate); | ||||
|             updateFFTHoldSpeed(); | ||||
|             updateFFTSpeeds(); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["fftRate"] = fftRate; | ||||
|             core::configManager.release(true); | ||||
| @@ -210,4 +254,4 @@ namespace displaymenu { | ||||
|             ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Restart required."); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,10 @@ namespace sourcemenu { | ||||
|     bool iqCorrection = false; | ||||
|     bool invertIQ = false; | ||||
|  | ||||
|     EventHandler<std::string> sourceRegisteredHandler; | ||||
|     EventHandler<std::string> sourceUnregisterHandler; | ||||
|     EventHandler<std::string> sourceUnregisteredHandler; | ||||
|  | ||||
|     std::vector<std::string> sourceNames; | ||||
|     std::string sourceNamesTxt; | ||||
|     std::string selectedSource; | ||||
| @@ -95,10 +99,10 @@ namespace sourcemenu { | ||||
|         } | ||||
|         sourceId = std::distance(sourceNames.begin(), it); | ||||
|         selectedSource = sourceNames[sourceId]; | ||||
|         sigpath::sourceManager.select(sourceNames[sourceId]); | ||||
|         sigpath::sourceManager.selectSource(sourceNames[sourceId]); | ||||
|     } | ||||
|  | ||||
|     void onSourceRegistered(std::string name) { | ||||
|     void onSourceRegistered(std::string name, void* ctx) { | ||||
|         refreshSources(); | ||||
|  | ||||
|         if (selectedSource.empty()) { | ||||
| @@ -110,13 +114,13 @@ namespace sourcemenu { | ||||
|         sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); | ||||
|     } | ||||
|  | ||||
|     void onSourceUnregister(std::string name) { | ||||
|     void onSourceUnregister(std::string name, void* ctx) { | ||||
|         if (name != selectedSource) { return; } | ||||
|  | ||||
|         // TODO: Stop everything | ||||
|     } | ||||
|  | ||||
|     void onSourceUnregistered(std::string name) { | ||||
|     void onSourceUnregistered(std::string name, void* ctx) { | ||||
|         refreshSources(); | ||||
|  | ||||
|         if (sourceNames.empty()) { | ||||
| @@ -149,9 +153,12 @@ namespace sourcemenu { | ||||
|         selectSource(selected); | ||||
|         sigpath::iqFrontEnd.setDecimation(1 << decimationPower); | ||||
|  | ||||
|         sigpath::sourceManager.onSourceRegistered.bind(onSourceRegistered); | ||||
|         sigpath::sourceManager.onSourceUnregister.bind(onSourceUnregister); | ||||
|         sigpath::sourceManager.onSourceUnregistered.bind(onSourceUnregistered); | ||||
|         sourceRegisteredHandler.handler = onSourceRegistered; | ||||
|         sourceUnregisterHandler.handler = onSourceUnregister; | ||||
|         sourceUnregisteredHandler.handler = onSourceUnregistered; | ||||
|         sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler); | ||||
|         sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler); | ||||
|         sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler); | ||||
|  | ||||
|         core::configManager.release(); | ||||
|     } | ||||
| @@ -172,7 +179,7 @@ namespace sourcemenu { | ||||
|  | ||||
|         if (running) { style::endDisabled(); } | ||||
|  | ||||
|         sigpath::sourceManager.showMenu(); | ||||
|         sigpath::sourceManager.showSelectedMenu(); | ||||
|  | ||||
|         if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) { | ||||
|             sigpath::iqFrontEnd.setDCBlocking(iqCorrection); | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <imgui.h> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| @@ -144,4 +145,4 @@ namespace SmGui { | ||||
|     // Config configs | ||||
|     void ForceSyncForNext(); | ||||
|  | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -689,6 +689,7 @@ namespace ImGui { | ||||
|  | ||||
|     void WaterFall::onResize() { | ||||
|         std::lock_guard<std::recursive_mutex> lck(latestFFTMtx); | ||||
|         std::lock_guard<std::mutex> lck2(smoothingBufMtx); | ||||
|         // return if widget is too small | ||||
|         if (widgetSize.x < 100 || widgetSize.y < 100) { | ||||
|             return; | ||||
| @@ -740,14 +741,23 @@ namespace ImGui { | ||||
|         } | ||||
|         latestFFTHold = new float[dataWidth]; | ||||
|  | ||||
|         // Reallocate smoothing buffer | ||||
|         if (fftSmoothing) { | ||||
|             if (smoothingBuf) { delete[] smoothingBuf; } | ||||
|             smoothingBuf = new float[dataWidth]; | ||||
|             for (int i = 0; i < dataWidth; i++) { | ||||
|                 smoothingBuf[i] = -1000.0f;  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (waterfallVisible) { | ||||
|             delete[] waterfallFb; | ||||
|             waterfallFb = new uint32_t[dataWidth * waterfallHeight]; | ||||
|             memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t)); | ||||
|         } | ||||
|         for (int i = 0; i < dataWidth; i++) { | ||||
|             latestFFT[i] = -1000.0; // Hide everything | ||||
|             latestFFTHold[i] = -1000.0; | ||||
|             latestFFT[i] = -1000.0f; // Hide everything | ||||
|             latestFFTHold[i] = -1000.0f; | ||||
|         } | ||||
|  | ||||
|         fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale)); | ||||
| @@ -873,9 +883,25 @@ namespace ImGui { | ||||
|             fftLines = 1; | ||||
|         } | ||||
|  | ||||
|         // Apply smoothing if enabled | ||||
|         if (fftSmoothing && latestFFT != NULL && smoothingBuf != NULL && fftLines != 0) { | ||||
|             std::lock_guard<std::mutex> lck2(smoothingBufMtx); | ||||
|             volk_32f_s32f_multiply_32f(latestFFT, latestFFT, fftSmoothingAlpha, dataWidth); | ||||
|             volk_32f_s32f_multiply_32f(smoothingBuf, smoothingBuf, fftSmoothingBeta, dataWidth); | ||||
|             volk_32f_x2_add_32f(smoothingBuf, latestFFT, smoothingBuf, dataWidth); | ||||
|             memcpy(latestFFT, smoothingBuf, dataWidth * sizeof(float)); | ||||
|         } | ||||
|  | ||||
|         if (selectedVFO != "" && vfos.size() > 0) { | ||||
|             float dummy; | ||||
|             calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR); | ||||
|             if (snrSmoothing) { | ||||
|                 float newSNR = 0.0f; | ||||
|                 calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, newSNR); | ||||
|                 selectedVFOSNR = (snrSmoothingBeta*selectedVFOSNR) + (snrSmoothingAlpha*newSNR); | ||||
|             } | ||||
|             else { | ||||
|                 calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // If FFT hold is enabled, update it | ||||
| @@ -1110,6 +1136,45 @@ namespace ImGui { | ||||
|         fftHoldSpeed = speed; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setFFTSmoothing(bool enabled) { | ||||
|         std::lock_guard<std::mutex> lck(smoothingBufMtx); | ||||
|         fftSmoothing = enabled; | ||||
|  | ||||
|         // Free buffer if not null | ||||
|         if (smoothingBuf) {delete[] smoothingBuf; } | ||||
|  | ||||
|         // If disabled, stop here | ||||
|         if (!enabled) { | ||||
|             smoothingBuf = NULL; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Allocate and copy existing FFT into it | ||||
|         smoothingBuf = new float[dataWidth]; | ||||
|         if (latestFFT) { | ||||
|             std::lock_guard<std::recursive_mutex> lck2(latestFFTMtx); | ||||
|             memcpy(smoothingBuf, latestFFT, dataWidth * sizeof(float)); | ||||
|         } | ||||
|         else { | ||||
|             memset(smoothingBuf, 0, dataWidth * sizeof(float)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setFFTSmoothingSpeed(float speed) { | ||||
|         std::lock_guard<std::mutex> lck(smoothingBufMtx); | ||||
|         fftSmoothingAlpha = speed; | ||||
|         fftSmoothingBeta = 1.0f - speed; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setSNRSmoothing(bool enabled) { | ||||
|         snrSmoothing = enabled; | ||||
|     } | ||||
|  | ||||
|     void WaterFall::setSNRSmoothingSpeed(float speed) { | ||||
|         snrSmoothingAlpha = speed; | ||||
|         snrSmoothingBeta = 1.0f - speed; | ||||
|     } | ||||
|  | ||||
|     float* WaterFall::acquireLatestFFT(int& width) { | ||||
|         latestFFTMtx.lock(); | ||||
|         if (!latestFFT) { | ||||
|   | ||||
| @@ -169,6 +169,12 @@ namespace ImGui { | ||||
|         void setFFTHold(bool hold); | ||||
|         void setFFTHoldSpeed(float speed); | ||||
|  | ||||
|         void setFFTSmoothing(bool enabled); | ||||
|         void setFFTSmoothingSpeed(float speed); | ||||
|  | ||||
|         void setSNRSmoothing(bool enabled); | ||||
|         void setSNRSmoothingSpeed(float speed); | ||||
|  | ||||
|         float* acquireLatestFFT(int& width); | ||||
|         void releaseLatestFFT(); | ||||
|  | ||||
| @@ -182,7 +188,7 @@ namespace ImGui { | ||||
|         bool mouseInFFT = false; | ||||
|         bool mouseInWaterfall = false; | ||||
|  | ||||
|         float selectedVFOSNR = NAN; | ||||
|         float selectedVFOSNR = 0.0f; | ||||
|  | ||||
|         bool centerFrequencyLocked = false; | ||||
|  | ||||
| @@ -270,6 +276,7 @@ namespace ImGui { | ||||
|         std::recursive_mutex buf_mtx; | ||||
|         std::recursive_mutex latestFFTMtx; | ||||
|         std::mutex texMtx; | ||||
|         std::mutex smoothingBufMtx; | ||||
|  | ||||
|         float vRange; | ||||
|  | ||||
| @@ -304,8 +311,9 @@ namespace ImGui { | ||||
|         //std::vector<std::vector<float>> rawFFTs; | ||||
|         int rawFFTSize; | ||||
|         float* rawFFTs = NULL; | ||||
|         float* latestFFT; | ||||
|         float* latestFFTHold; | ||||
|         float* latestFFT = NULL; | ||||
|         float* latestFFTHold = NULL; | ||||
|         float* smoothingBuf = NULL; | ||||
|         int currentFFTLine = 0; | ||||
|         int fftLines = 0; | ||||
|  | ||||
| @@ -325,6 +333,14 @@ namespace ImGui { | ||||
|         bool fftHold = false; | ||||
|         float fftHoldSpeed = 0.3f; | ||||
|  | ||||
|         bool fftSmoothing = false; | ||||
|         float fftSmoothingAlpha = 0.5; | ||||
|         float fftSmoothingBeta = 0.5; | ||||
|  | ||||
|         bool snrSmoothing = false; | ||||
|         float snrSmoothingAlpha = 0.5; | ||||
|         float snrSmoothingBeta = 0.5; | ||||
|  | ||||
|         // UI Select elements | ||||
|         bool fftResizeSelect = false; | ||||
|         bool freqScaleSelect = false; | ||||
|   | ||||
| @@ -33,7 +33,7 @@ ModuleManager::Module_t ModuleManager::loadModule(std::string path) { | ||||
| #else | ||||
|     mod.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); | ||||
|     if (mod.handle == NULL) { | ||||
|         flog::error("Couldn't load {0}.", path); | ||||
|         flog::error("Couldn't load {0}: {1}", path, dlerror()); | ||||
|         mod.handle = NULL; | ||||
|         return mod; | ||||
|     } | ||||
| @@ -183,4 +183,4 @@ void ModuleManager::doPostInitAll() { | ||||
|         flog::info("Running post-init for {0}", name); | ||||
|         inst.instance->postInit(); | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| #include <utils/flog.h> | ||||
|  | ||||
| bool ModuleComManager::registerInterface(std::string moduleName, std::string name, void (*handler)(int code, void* in, void* out, void* ctx), void* ctx) { | ||||
|     std::lock_guard<std::mutex> lck(mtx); | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     if (interfaces.find(name) != interfaces.end()) { | ||||
|         flog::error("Tried creating module interface with an existing name: {0}", name); | ||||
|         return false; | ||||
| @@ -16,7 +16,7 @@ bool ModuleComManager::registerInterface(std::string moduleName, std::string nam | ||||
| } | ||||
|  | ||||
| bool ModuleComManager::unregisterInterface(std::string name) { | ||||
|     std::lock_guard<std::mutex> lck(mtx); | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     if (interfaces.find(name) == interfaces.end()) { | ||||
|         flog::error("Tried to erase module interface with unknown name: {0}", name); | ||||
|         return false; | ||||
| @@ -26,13 +26,13 @@ bool ModuleComManager::unregisterInterface(std::string name) { | ||||
| } | ||||
|  | ||||
| bool ModuleComManager::interfaceExists(std::string name) { | ||||
|     std::lock_guard<std::mutex> lck(mtx); | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     if (interfaces.find(name) == interfaces.end()) { return false; } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| std::string ModuleComManager::getModuleName(std::string name) { | ||||
|     std::lock_guard<std::mutex> lck(mtx); | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     if (interfaces.find(name) == interfaces.end()) { | ||||
|         flog::error("Tried to call unknown module interface: {0}", name); | ||||
|         return ""; | ||||
| @@ -41,7 +41,7 @@ std::string ModuleComManager::getModuleName(std::string name) { | ||||
| } | ||||
|  | ||||
| bool ModuleComManager::callInterface(std::string name, int code, void* in, void* out) { | ||||
|     std::lock_guard<std::mutex> lck(mtx); | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     if (interfaces.find(name) == interfaces.end()) { | ||||
|         flog::error("Tried to call unknown module interface: {0}", name); | ||||
|         return false; | ||||
|   | ||||
| @@ -18,6 +18,6 @@ public: | ||||
|     bool callInterface(std::string name, int code, void* in, void* out); | ||||
|  | ||||
| private: | ||||
|     std::mutex mtx; | ||||
|     std::recursive_mutex mtx; | ||||
|     std::map<std::string, ModuleComInterface> interfaces; | ||||
| }; | ||||
| @@ -146,7 +146,7 @@ namespace server { | ||||
|         // Load sourceId from config | ||||
|         sourceId = 0; | ||||
|         if (sourceList.keyExists(sourceName)) { sourceId = sourceList.keyId(sourceName); } | ||||
|         sigpath::sourceManager.select(sourceList[sourceId]); | ||||
|         sigpath::sourceManager.selectSource(sourceList[sourceId]); | ||||
|  | ||||
|         // TODO: Use command line option | ||||
|         std::string host = (std::string)core::args["addr"]; | ||||
| @@ -280,7 +280,8 @@ namespace server { | ||||
|             } | ||||
|         } | ||||
|         else if (cmd == COMMAND_START) { | ||||
|             running = sigpath::sourceManager.start(); | ||||
|             sigpath::sourceManager.start(); | ||||
|             running = true; | ||||
|         } | ||||
|         else if (cmd == COMMAND_STOP) { | ||||
|             sigpath::sourceManager.stop(); | ||||
| @@ -308,14 +309,14 @@ namespace server { | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo("##sdrpp_server_src_sel", &sourceId, sourceList.txt)) { | ||||
|             sigpath::sourceManager.select(sourceList[sourceId]); | ||||
|             sigpath::sourceManager.selectSource(sourceList[sourceId]); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["source"] = sourceList.key(sourceId); | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         if (running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         sigpath::sourceManager.showMenu(); | ||||
|         sigpath::sourceManager.showSelectedMenu(); | ||||
|     } | ||||
|  | ||||
|     void renderUI(SmGui::DrawList* dl, std::string diffId, SmGui::DrawListElem diffValue) { | ||||
|   | ||||
| @@ -1,186 +1,106 @@ | ||||
| #include "source.h" | ||||
| #include <server.h> | ||||
| #include <signal_path/source.h> | ||||
| #include <utils/flog.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
|  | ||||
| void SourceManager::registerSource(const std::string& name, Source* source) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
| SourceManager::SourceManager() { | ||||
| } | ||||
|  | ||||
|     // Check arguments | ||||
|     if (source || name.empty()) { | ||||
|         flog::error("Invalid argument to register source", name); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Check that a source with that name doesn't already exist | ||||
| void SourceManager::registerSource(std::string name, SourceHandler* handler) { | ||||
|     if (sources.find(name) != sources.end()) { | ||||
|         flog::error("Tried to register source with existing name: {}", name); | ||||
|         flog::error("Tried to register new source with existing name: {0}", name); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Add source to map | ||||
|     sources[name] = source; | ||||
|  | ||||
|     // Add source to lists | ||||
|     sourceNames.push_back(name); | ||||
|     onSourceRegistered(name); | ||||
|     sources[name] = handler; | ||||
|     onSourceRegistered.emit(name); | ||||
| } | ||||
|  | ||||
| void SourceManager::unregisterSource(const std::string& name) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Check that a source with that name exists | ||||
| void SourceManager::unregisterSource(std::string name) { | ||||
|     if (sources.find(name) == sources.end()) { | ||||
|         flog::error("Tried to unregister a non-existent source: {}", name); | ||||
|         flog::error("Tried to unregister non existent source: {0}", name); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Notify event listeners of the imminent deletion | ||||
|     onSourceUnregister(name); | ||||
|  | ||||
|     // Delete from lists | ||||
|     sourceNames.erase(std::find(sourceNames.begin(), sourceNames.end(), name)); | ||||
|     onSourceUnregister.emit(name); | ||||
|     if (name == selectedName) { | ||||
|         if (selectedHandler != NULL) { | ||||
|             sources[selectedName]->deselectHandler(sources[selectedName]->ctx); | ||||
|         } | ||||
|         sigpath::iqFrontEnd.setInput(&nullSource); | ||||
|         selectedHandler = NULL; | ||||
|     } | ||||
|     sources.erase(name); | ||||
|  | ||||
|     // Notify event listeners of the deletion | ||||
|     onSourceUnregistered(name); | ||||
|     onSourceUnregistered.emit(name); | ||||
| } | ||||
|  | ||||
| const std::vector<std::string>& SourceManager::getSourceNames() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return sourceNames; | ||||
| std::vector<std::string> SourceManager::getSourceNames() { | ||||
|     std::vector<std::string> names; | ||||
|     for (auto const& [name, src] : sources) { names.push_back(name); } | ||||
|     return names; | ||||
| } | ||||
|  | ||||
| void SourceManager::select(const std::string& name) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // make sure that source isn't currently selected | ||||
|     if (selectedSourceName == name) { return; } | ||||
|  | ||||
|     // Deselect current source | ||||
|     deselect(); | ||||
|  | ||||
|     // Check that a source with that name exists | ||||
| void SourceManager::selectSource(std::string name) { | ||||
|     if (sources.find(name) == sources.end()) { | ||||
|         flog::error("Tried to select a non-existent source: {}", name); | ||||
|         flog::error("Tried to select non existent source: {0}", name); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Select the source | ||||
|     selectedSourceName = name; | ||||
|     selectedSource = sources[name]; | ||||
|  | ||||
|     // Call the selected source | ||||
|     selectedSource->select(); | ||||
|  | ||||
|     // Retune to make sure the source has the latest frequency | ||||
|     tune(frequency); | ||||
|     if (selectedHandler != NULL) { | ||||
|         sources[selectedName]->deselectHandler(sources[selectedName]->ctx); | ||||
|     } | ||||
|     selectedHandler = sources[name]; | ||||
|     selectedHandler->selectHandler(selectedHandler->ctx); | ||||
|     selectedName = name; | ||||
|     if (core::args["server"].b()) { | ||||
|         server::setInput(selectedHandler->stream); | ||||
|     } | ||||
|     else { | ||||
|         sigpath::iqFrontEnd.setInput(selectedHandler->stream); | ||||
|     } | ||||
|     // Set server input here | ||||
| } | ||||
|  | ||||
| const std::string& SourceManager::getSelected() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return selectedSourceName; | ||||
| void SourceManager::showSelectedMenu() { | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     selectedHandler->menuHandler(selectedHandler->ctx); | ||||
| } | ||||
|  | ||||
| bool SourceManager::start() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Check if not already running | ||||
|     if (running) { return true; } | ||||
|  | ||||
|     // Call source if selected and save if started | ||||
|     running = (!selectedSource) ? false : selectedSource->start(); | ||||
|  | ||||
|     return running; | ||||
| void SourceManager::start() { | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     selectedHandler->startHandler(selectedHandler->ctx); | ||||
| } | ||||
|  | ||||
| void SourceManager::stop() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Check if running | ||||
|     if (!running) { return; } | ||||
|  | ||||
|     // Call source if selected and save state | ||||
|     if (selectedSource) { selectedSource->stop(); } | ||||
|     running = false; | ||||
| } | ||||
|  | ||||
| bool SourceManager::isRunning() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return running; | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     selectedHandler->stopHandler(selectedHandler->ctx); | ||||
| } | ||||
|  | ||||
| void SourceManager::tune(double freq) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Save frequency | ||||
|     frequency = freq; | ||||
|      | ||||
|     // Call source if selected | ||||
|     if (selectedSource) { | ||||
|         selectedSource->tune(((mode == TUNING_MODE_NORMAL) ? freq : ifFrequency) + offset); | ||||
|     if (selectedHandler == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     // TODO: No need to always retune the hardware in panadpter mode | ||||
|     selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx); | ||||
|     onRetune.emit(freq); | ||||
|     currentFreq = freq; | ||||
| } | ||||
|  | ||||
| void SourceManager::showMenu() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|      | ||||
|     // Call source if selected | ||||
|     if (selectedSource) { selectedSource->showMenu(); } | ||||
| } | ||||
|  | ||||
| double SourceManager::getSamplerate() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return samplerate; | ||||
| } | ||||
|  | ||||
| // =========== TODO: These functions should not happen in this class =========== | ||||
|  | ||||
| void SourceManager::setTuningOffset(double offset) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Update offset | ||||
|     this->offset = offset; | ||||
|  | ||||
|     // Retune to take affect | ||||
|     tune(frequency); | ||||
|     tuneOffset = offset; | ||||
|     tune(currentFreq); | ||||
| } | ||||
|  | ||||
| void SourceManager::setTuningMode(TuningMode mode) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Update mode | ||||
|     this->mode = mode; | ||||
|  | ||||
|     // Retune to take affect | ||||
|     tune(frequency); | ||||
|     tuneMode = mode; | ||||
|     tune(currentFreq); | ||||
| } | ||||
|  | ||||
| void SourceManager::setPanadpterIF(double freq) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Update offset | ||||
|     ifFrequency = freq; | ||||
|  | ||||
|     // Return to take affect if in panadapter mode  | ||||
|     if (mode == TUNING_MODE_PANADAPTER) { tune(frequency); } | ||||
| } | ||||
|  | ||||
| // ============================================================================= | ||||
|  | ||||
| void SourceManager::deselect() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Call source if selected | ||||
|     if (selectedSource) { selectedSource->deselect(); } | ||||
|  | ||||
|     // Mark as deselected | ||||
|     selectedSourceName.clear(); | ||||
|     selectedSource = NULL; | ||||
| } | ||||
|  | ||||
| void SourceManager::setSamplerate(double samplerate) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Save samplerate and emit event | ||||
|     this->samplerate = samplerate; | ||||
|     onSamplerateChanged(samplerate); | ||||
|     ifFreq = freq; | ||||
|     tune(currentFreq); | ||||
| } | ||||
| @@ -1,153 +1,56 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <functional> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <mutex> | ||||
| #include <dsp/types.h> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/types.h> | ||||
| #include <utils/event.h> | ||||
|  | ||||
| enum TuningMode { | ||||
|     TUNING_MODE_NORMAL, | ||||
|     TUNING_MODE_PANADAPTER | ||||
| }; | ||||
|  | ||||
| class Source; | ||||
|  | ||||
| class SourceManager { | ||||
|     friend Source; | ||||
| public: | ||||
|     /** | ||||
|      * Register a source. | ||||
|      * @param name Name of the source. | ||||
|      * @param source Pointer to the source instance. | ||||
|      */ | ||||
|     void registerSource(const std::string& name, Source* source); | ||||
|     SourceManager(); | ||||
|  | ||||
|     /** | ||||
|      * Unregister a source. | ||||
|      * @param name Name of the source. | ||||
|      */ | ||||
|     void unregisterSource(const std::string& name); | ||||
|     struct SourceHandler { | ||||
|         dsp::stream<dsp::complex_t>* stream; | ||||
|         void (*menuHandler)(void* ctx); | ||||
|         void (*selectHandler)(void* ctx); | ||||
|         void (*deselectHandler)(void* ctx); | ||||
|         void (*startHandler)(void* ctx); | ||||
|         void (*stopHandler)(void* ctx); | ||||
|         void (*tuneHandler)(double freq, void* ctx); | ||||
|         void* ctx; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Get a list of source names. | ||||
|      * @return List of source names. | ||||
|      */ | ||||
|     const std::vector<std::string>& getSourceNames(); | ||||
|     enum TuningMode { | ||||
|         NORMAL, | ||||
|         PANADAPTER | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Select a source. | ||||
|      * @param name Name of the source. | ||||
|      */ | ||||
|     void select(const std::string& name); | ||||
|  | ||||
|     /** | ||||
|      * Get the name of the currently selected source. | ||||
|      * @return Name of the source or empty if no source is selected. | ||||
|      */ | ||||
|     const std::string& getSelected(); | ||||
|      | ||||
|     /** | ||||
|      * Start the radio. | ||||
|      * @return True if the radio started successfully, false if not. | ||||
|      */ | ||||
|     bool start(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the radio. | ||||
|      */ | ||||
|     void registerSource(std::string name, SourceHandler* handler); | ||||
|     void unregisterSource(std::string name); | ||||
|     void selectSource(std::string name); | ||||
|     void showSelectedMenu(); | ||||
|     void start(); | ||||
|     void stop(); | ||||
|  | ||||
|     /** | ||||
|      * Check if the radio is running. | ||||
|      * @return True if the radio is running, false if not. | ||||
|      */ | ||||
|     bool isRunning(); | ||||
|  | ||||
|     /** | ||||
|      * Tune the radio. | ||||
|      * @param freq Frequency in Hz. | ||||
|      */ | ||||
|     void tune(double freq); | ||||
|  | ||||
|     /** | ||||
|      * Tune the radio. | ||||
|      * @param freq Frequency to tune the radio to. | ||||
|      */ | ||||
|     void showMenu(); | ||||
|  | ||||
|     /** | ||||
|      * Get the current samplerate of the radio. | ||||
|      * @return Samplerate in Hz. | ||||
|      */ | ||||
|     double getSamplerate(); | ||||
|  | ||||
|     // =========== TODO: These functions should not happen in this class =========== | ||||
|  | ||||
|     /** | ||||
|      * Set offset to add to the tuned frequency. | ||||
|      * @param offset Offset in Hz. | ||||
|      */ | ||||
|     void setTuningOffset(double offset); | ||||
|  | ||||
|     /** | ||||
|      * Set tuning mode. | ||||
|      * @param mode Tuning mode. | ||||
|      */ | ||||
|     void setTuningMode(TuningMode mode); | ||||
|  | ||||
|     /** | ||||
|      * Set panadapter mode IF frequency. | ||||
|      * @param freq IF frequency in Hz. | ||||
|      */ | ||||
|     void setPanadpterIF(double freq); | ||||
|  | ||||
|     // ============================================================================= | ||||
|     std::vector<std::string> getSourceNames(); | ||||
|  | ||||
|     // Emitted after a new source has been registered. | ||||
|     Event<std::string> onSourceRegistered; | ||||
|  | ||||
|     // Emitted when a source is about to be unregistered. | ||||
|     Event<std::string> onSourceUnregister; | ||||
|  | ||||
|     // Emitted after a source has been unregistered. | ||||
|     Event<std::string> onSourceUnregistered; | ||||
|  | ||||
|     // Emitted when the samplerate of the incoming IQ has changed. | ||||
|     Event<double> onSamplerateChanged; | ||||
|  | ||||
|     // Emitted when the source manager is instructed to tune the radio. | ||||
|     Event<double> onRetune; | ||||
|  | ||||
| private: | ||||
|     void deselect(); | ||||
|     void setSamplerate(double samplerate); | ||||
|      | ||||
|     std::vector<std::string> sourceNames; | ||||
|     std::map<std::string, Source*> sources; | ||||
|  | ||||
|     std::string selectedSourceName = ""; | ||||
|     Source* selectedSource = NULL; | ||||
|  | ||||
|     bool running = false; | ||||
|     double samplerate = 1e6; | ||||
|     double frequency = 100e6; | ||||
|     double offset = 0; | ||||
|     double ifFrequency = 8.830e6; | ||||
|     TuningMode mode = TUNING_MODE_NORMAL; | ||||
|  | ||||
|     std::recursive_mutex mtx; | ||||
| }; | ||||
|  | ||||
| class Source { | ||||
| public: | ||||
|     virtual void showMenu() {} | ||||
|     virtual void select() = 0; | ||||
|     virtual void deselect() {} | ||||
|     virtual bool start() = 0; | ||||
|     virtual void stop() = 0; | ||||
|     virtual void tune(double freq) {} | ||||
|  | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     std::map<std::string, SourceHandler*> sources; | ||||
|     std::string selectedName; | ||||
|     SourceHandler* selectedHandler = NULL; | ||||
|     double tuneOffset; | ||||
|     double currentFreq; | ||||
|     double ifFreq = 0.0; | ||||
|     TuningMode tuneMode = TuningMode::NORMAL; | ||||
|     dsp::stream<dsp::complex_t> nullSource; | ||||
| }; | ||||
| @@ -1,51 +1,43 @@ | ||||
| #pragma once | ||||
| #include <functional> | ||||
| #include <stdexcept> | ||||
| #include <mutex> | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <utils/flog.h> | ||||
|  | ||||
| typedef int HandlerID; | ||||
| template <class T> | ||||
| struct EventHandler { | ||||
|     EventHandler() {} | ||||
|     EventHandler(void (*handler)(T, void*), void* ctx) { | ||||
|         this->handler = handler; | ||||
|         this->ctx = ctx; | ||||
|     } | ||||
|  | ||||
| template <typename... Args> | ||||
|     void (*handler)(T, void*); | ||||
|     void* ctx; | ||||
| }; | ||||
|  | ||||
| template <class T> | ||||
| class Event { | ||||
|     using Handler = std::function<void(Args...)>; | ||||
| public: | ||||
|     HandlerID bind(Handler handler) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         HandlerID id = genID(); | ||||
|         handlers[id] = handler; | ||||
|         return id; | ||||
|     } | ||||
|     Event() {} | ||||
|     ~Event() {} | ||||
|  | ||||
|     template<typename MHandler, class T> | ||||
|     HandlerID bind(MHandler handler, T* ctx) { | ||||
|         return bind([=](Args... args){ | ||||
|             (ctx->*handler)(args...); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     void unbind(HandlerID id) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         if (handlers.find(id) == handlers.end()) { | ||||
|             throw std::runtime_error("Could not unbind handler, unknown ID"); | ||||
|     void emit(T value) { | ||||
|         for (auto const& handler : handlers) { | ||||
|             handler->handler(value, handler->ctx); | ||||
|         } | ||||
|         handlers.erase(id); | ||||
|     } | ||||
|  | ||||
|     void operator()(Args... args) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         for (const auto& [desc, handler] : handlers) { | ||||
|             handler(args...); | ||||
|     void bindHandler(EventHandler<T>* handler) { | ||||
|         handlers.push_back(handler); | ||||
|     } | ||||
|  | ||||
|     void unbindHandler(EventHandler<T>* handler) { | ||||
|         if (std::find(handlers.begin(), handlers.end(), handler) == handlers.end()) { | ||||
|             flog::error("Tried to remove a non-existent event handler"); | ||||
|             return; | ||||
|         } | ||||
|         handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end()); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     HandlerID genID() { | ||||
|         int id; | ||||
|         for (id = 1; handlers.find(id) != handlers.end(); id++); | ||||
|         return id; | ||||
|     } | ||||
|  | ||||
|     std::map<HandlerID, Handler> handlers; | ||||
|     std::mutex mtx; | ||||
|     std::vector<EventHandler<T>*> handlers; | ||||
| }; | ||||
| @@ -169,7 +169,7 @@ namespace flog { | ||||
|             fprintf(outStream, "] %s\n", out.c_str()); | ||||
| #elif defined(__ANDROID__) | ||||
|             // Print format string | ||||
|             __android_log_buf_print(LOG_ID_DEFAULT, TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n", | ||||
|             __android_log_print(TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n", | ||||
|                     nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str()); | ||||
| #else | ||||
|             // Print format string | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "net.h" | ||||
| #include <string.h> | ||||
| #include <codecvt> | ||||
| #include <stdexcept> | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK) | ||||
| @@ -288,6 +289,7 @@ namespace net { | ||||
|  | ||||
|         // Save data | ||||
|         for (auto iface = addresses; iface; iface = iface->ifa_next) { | ||||
|             if (!iface->ifa_addr || !iface->ifa_netmask) { continue; } | ||||
|             if (iface->ifa_addr->sa_family != AF_INET) { continue; } | ||||
|             InterfaceInfo info; | ||||
|             info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]); | ||||
| @@ -402,4 +404,4 @@ namespace net { | ||||
|     std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport) { | ||||
|         return openudp(Address(rhost, rport), Address(lhost, lport)); | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <string> | ||||
| #include <mutex> | ||||
| #include <memory> | ||||
| #include <map> | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include <utils/networking.h> | ||||
| #include <assert.h> | ||||
| #include <utils/flog.h> | ||||
| #include <stdexcept> | ||||
|  | ||||
| namespace net { | ||||
|  | ||||
|   | ||||
							
								
								
									
										52
									
								
								core/src/utils/new_event.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								core/src/utils/new_event.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| #pragma once | ||||
| #include <functional> | ||||
| #include <stdexcept> | ||||
| #include <mutex> | ||||
| #include <map> | ||||
|  | ||||
| typedef int HandlerID; | ||||
|  | ||||
| template <typename... Args> | ||||
| class NewEvent { | ||||
| public: | ||||
|     using Handler = std::function<void(Args...)>; | ||||
|  | ||||
|     HandlerID bind(const Handler& handler) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         HandlerID id = genID(); | ||||
|         handlers[id] = handler; | ||||
|         return id; | ||||
|     } | ||||
|  | ||||
|     template<typename MHandler, class T> | ||||
|     HandlerID bind(MHandler handler, T* ctx) { | ||||
|         return bind([=](Args... args){ | ||||
|             (ctx->*handler)(args...); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     void unbind(HandlerID id) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         if (handlers.find(id) == handlers.end()) { | ||||
|             throw std::runtime_error("Could not unbind handler, unknown ID"); | ||||
|         } | ||||
|         handlers.erase(id); | ||||
|     } | ||||
|  | ||||
|     void operator()(Args... args) { | ||||
|         std::lock_guard<std::mutex> lck(mtx); | ||||
|         for (const auto& [desc, handler] : handlers) { | ||||
|             handler(args...); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     HandlerID genID() { | ||||
|         int id; | ||||
|         for (id = 1; handlers.find(id) != handlers.end(); id++); | ||||
|         return id; | ||||
|     } | ||||
|  | ||||
|     std::map<HandlerID, Handler> handlers; | ||||
|     std::mutex mtx; | ||||
| }; | ||||
| @@ -8,7 +8,7 @@ class OptionList { | ||||
| public: | ||||
|     OptionList() { updateText(); } | ||||
|  | ||||
|     void define(K key, std::string name, T value) { | ||||
|     void define(const K& key, const std::string& name, const T& value) { | ||||
|         if (keyExists(key)) { throw std::runtime_error("Key already exists"); } | ||||
|         if (nameExists(name)) { throw std::runtime_error("Name already exists"); } | ||||
|         if (valueExists(value)) { throw std::runtime_error("Value already exists"); } | ||||
| @@ -18,27 +18,27 @@ public: | ||||
|         updateText(); | ||||
|     } | ||||
|  | ||||
|     void define(std::string name, T value) { | ||||
|     void define(const std::string& name, const T& value) { | ||||
|         define(name, name, value); | ||||
|     } | ||||
|  | ||||
|     void undefined(int id) { | ||||
|     void undefine(int id) { | ||||
|         keys.erase(keys.begin() + id); | ||||
|         names.erase(names.begin() + id); | ||||
|         values.erase(values.begin() + id); | ||||
|         updateText(); | ||||
|     } | ||||
|  | ||||
|     void undefineKey(K key) { | ||||
|         undefined(keyId(key)); | ||||
|     void undefineKey(const K& key) { | ||||
|         undefine(keyId(key)); | ||||
|     } | ||||
|  | ||||
|     void undefineName(std::string name) { | ||||
|         undefined(nameId(name)); | ||||
|     void undefineName(const std::string& name) { | ||||
|         undefine(nameId(name)); | ||||
|     } | ||||
|  | ||||
|     void undefineValue(T value) { | ||||
|         undefined(valueId(value)); | ||||
|     void undefineValue(const T& value) { | ||||
|         undefine(valueId(value)); | ||||
|     } | ||||
|  | ||||
|     void clear() { | ||||
| @@ -48,61 +48,61 @@ public: | ||||
|         updateText(); | ||||
|     } | ||||
|  | ||||
|     int size() { | ||||
|     int size() const { | ||||
|         return keys.size(); | ||||
|     } | ||||
|  | ||||
|     bool empty() { | ||||
|     bool empty() const { | ||||
|         return keys.empty(); | ||||
|     } | ||||
|  | ||||
|     bool keyExists(K key) { | ||||
|     bool keyExists(const K& key) const { | ||||
|         if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     bool nameExists(std::string name) { | ||||
|     bool nameExists(const std::string& name) const { | ||||
|         if (std::find(names.begin(), names.end(), name) != names.end()) { return true; } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     bool valueExists(T value) { | ||||
|     bool valueExists(const T& value) const { | ||||
|         if (std::find(values.begin(), values.end(), value) != values.end()) { return true; } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     int keyId(K key) { | ||||
|     int keyId(const K& key) const { | ||||
|         auto it = std::find(keys.begin(), keys.end(), key); | ||||
|         if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); } | ||||
|         return std::distance(keys.begin(), it); | ||||
|     } | ||||
|  | ||||
|     int nameId(std::string name) { | ||||
|     int nameId(const std::string& name) const { | ||||
|         auto it = std::find(names.begin(), names.end(), name); | ||||
|         if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); } | ||||
|         return std::distance(names.begin(), it); | ||||
|     } | ||||
|  | ||||
|     int valueId(T value) { | ||||
|     int valueId(const T& value) const { | ||||
|         auto it = std::find(values.begin(), values.end(), value); | ||||
|         if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); } | ||||
|         return std::distance(values.begin(), it); | ||||
|     } | ||||
|  | ||||
|     K key(int id) { | ||||
|     inline const K& key(int id) const { | ||||
|         return keys[id]; | ||||
|     } | ||||
|  | ||||
|     std::string name(int id) { | ||||
|     inline const std::string& name(int id) const { | ||||
|         return names[id]; | ||||
|     } | ||||
|  | ||||
|     T value(int id) { | ||||
|     inline const T& value(int id) const { | ||||
|         return values[id]; | ||||
|     } | ||||
|  | ||||
|     T operator[](int& id) { | ||||
|         return value(id); | ||||
|     inline const T& operator[](int& id) const { | ||||
|         return values[id]; | ||||
|     } | ||||
|  | ||||
|     const char* txt = NULL; | ||||
|   | ||||
| @@ -91,9 +91,9 @@ namespace riff { | ||||
|         file.write((char*)&desc.hdr.size, sizeof(desc.hdr.size)); | ||||
|         file.seekp(pos); | ||||
|  | ||||
|         // If parent chunk, increment its size | ||||
|         // If parent chunk, increment its size by the size of the sub-chunk plus the size of its header) | ||||
|         if (!chunks.empty()) { | ||||
|             chunks.top().hdr.size += desc.hdr.size; | ||||
|             chunks.top().hdr.size += desc.hdr.size + sizeof(ChunkHeader); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -214,10 +214,10 @@ private: | ||||
|  | ||||
|         if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) { | ||||
|             if (_this->showLines) { | ||||
|                 _this->diag.lines.push_back(-0.75f); | ||||
|                 _this->diag.lines.push_back(-0.25f); | ||||
|                 _this->diag.lines.push_back(0.25f); | ||||
|                 _this->diag.lines.push_back(0.75f); | ||||
|                 _this->diag.lines.push_back(-1.0); | ||||
|                 _this->diag.lines.push_back(-1.0/3.0); | ||||
|                 _this->diag.lines.push_back(1.0/3.0); | ||||
|                 _this->diag.lines.push_back(1.0); | ||||
|             } | ||||
|             else { | ||||
|                 _this->diag.lines.clear(); | ||||
|   | ||||
| @@ -57,10 +57,13 @@ public: | ||||
|         if (config.conf[name].contains("brokenModulation")) { | ||||
|             brokenModulation = config.conf[name]["brokenModulation"]; | ||||
|         } | ||||
|         if (config.conf[name].contains("oqpsk")) { | ||||
|             oqpsk = config.conf[name]["oqpsk"]; | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true); | ||||
|         demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, 1e-6, 0.01); | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true); | ||||
|         demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, oqpsk, 1e-6, 0.01); | ||||
|         split.init(&demod.out); | ||||
|         split.bindStream(&symSinkStream); | ||||
|         split.bindStream(&sinkStream); | ||||
| @@ -99,6 +102,7 @@ public: | ||||
|         double bw = gui::waterfall.getBandwidth(); | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 150000, INPUT_SAMPLE_RATE, 150000, 150000, true); | ||||
|  | ||||
|         demod.setBrokenModulation(brokenModulation); | ||||
|         demod.setInput(vfo->output); | ||||
|  | ||||
|         demod.start(); | ||||
| @@ -151,6 +155,13 @@ private: | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (ImGui::Checkbox(CONCAT("OQPSK##oqpsk", _this->name), &_this->oqpsk)) { | ||||
|             _this->demod.setOQPSK(_this->oqpsk); | ||||
|             config.acquire(); | ||||
|             config.conf[_this->name]["oqpsk"] = _this->oqpsk; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); } | ||||
|  | ||||
|         if (_this->recording) { | ||||
| @@ -245,7 +256,7 @@ private: | ||||
|     uint64_t dataWritten = 0; | ||||
|     std::ofstream recFile; | ||||
|     bool brokenModulation = false; | ||||
|  | ||||
|     bool oqpsk = false; | ||||
|     int8_t* writeBuffer; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -11,8 +11,8 @@ namespace dsp::demod { | ||||
|     public: | ||||
|         Meteor() {} | ||||
|  | ||||
|         Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) { | ||||
|             init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, omegaGain, muGain); | ||||
|         Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) { | ||||
|             init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, oqpsk, omegaGain, muGain); | ||||
|         } | ||||
|  | ||||
|         ~Meteor() { | ||||
| @@ -21,11 +21,12 @@ namespace dsp::demod { | ||||
|             taps::free(rrcTaps); | ||||
|         } | ||||
|  | ||||
|         void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) { | ||||
|         void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) { | ||||
|             _symbolrate = symbolrate; | ||||
|             _samplerate = samplerate; | ||||
|             _rrcTapCount = rrcTapCount; | ||||
|             _rrcBeta = rrcBeta; | ||||
|             _oqpsk = oqpsk; | ||||
|              | ||||
|             rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate); | ||||
|             rrc.init(NULL, rrcTaps); | ||||
| @@ -129,6 +130,12 @@ namespace dsp::demod { | ||||
|             costas.setBrokenModulation(enabled); | ||||
|         } | ||||
|  | ||||
|         void setOQPSK(bool enabled) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             _oqpsk = enabled; | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
| @@ -144,6 +151,18 @@ namespace dsp::demod { | ||||
|             rrc.process(count, in, out); | ||||
|             agc.process(count, out, out); | ||||
|             costas.process(count, out, out); | ||||
|  | ||||
|             if (_oqpsk) { | ||||
|                 // Single sample delay + deinterleave | ||||
|                 for (int i = 0; i < count; i++) { | ||||
|                     float tmp = out[i].im; | ||||
|                     out[i].im = lastI; | ||||
|                     lastI = tmp; | ||||
|                 } | ||||
|  | ||||
|                 // TODO: Additional 1/24th sample delay | ||||
|             } | ||||
|  | ||||
|             return recov.process(count, out, out); | ||||
|         } | ||||
|  | ||||
| @@ -166,6 +185,8 @@ namespace dsp::demod { | ||||
|         double _samplerate; | ||||
|         int _rrcTapCount; | ||||
|         double _rrcBeta; | ||||
|         float lastI = 0.0f; | ||||
|         bool _oqpsk = false; | ||||
|  | ||||
|         tap<float> rrcTaps; | ||||
|         filter::FIR<complex_t, float> rrc; | ||||
|   | ||||
| @@ -19,15 +19,17 @@ namespace demod { | ||||
|  | ||||
|             // Load config | ||||
|             _config->acquire(); | ||||
|             bool modified = false; | ||||
|             if (config->conf[name][getName()].contains("lowPass")) { | ||||
|                 _lowPass = config->conf[name][getName()]["lowPass"]; | ||||
|             } | ||||
|             _config->release(modified); | ||||
|             if (config->conf[name][getName()].contains("highPass")) { | ||||
|                 _highPass = config->conf[name][getName()]["highPass"]; | ||||
|             } | ||||
|             _config->release(); | ||||
|  | ||||
|  | ||||
|             // Define structure | ||||
|             demod.init(input, getIFSampleRate(), bandwidth, _lowPass); | ||||
|             demod.init(input, getIFSampleRate(), bandwidth, _lowPass, _highPass); | ||||
|         } | ||||
|  | ||||
|         void start() { demod.start(); } | ||||
| @@ -41,6 +43,12 @@ namespace demod { | ||||
|                 _config->conf[name][getName()]["lowPass"] = _lowPass; | ||||
|                 _config->release(true); | ||||
|             } | ||||
|             if (ImGui::Checkbox(("High Pass##_radio_wfm_highpass_" + name).c_str(), &_highPass)) { | ||||
|                 demod.setHighPass(_highPass); | ||||
|                 _config->acquire(); | ||||
|                 _config->conf[name][getName()]["highPass"] = _highPass; | ||||
|                 _config->release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         void setBandwidth(double bandwidth) { | ||||
| @@ -75,6 +83,7 @@ namespace demod { | ||||
|         ConfigManager* _config = NULL; | ||||
|  | ||||
|         bool _lowPass = true; | ||||
|         bool _highPass = false; | ||||
|  | ||||
|         std::string name; | ||||
|     }; | ||||
|   | ||||
							
								
								
									
										4
									
								
								docker_builds/debian_bookworm/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docker_builds/debian_bookworm/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| FROM debian:bookworm | ||||
| ENV DEBIAN_FRONTEND=noninteractive  | ||||
| COPY do_build.sh /root | ||||
| RUN chmod +x /root/do_build.sh | ||||
							
								
								
									
										35
									
								
								docker_builds/debian_bookworm/do_build.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docker_builds/debian_bookworm/do_build.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
| cd /root | ||||
|  | ||||
| # Install dependencies and tools | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.07.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.07.1 | ||||
| cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| 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 | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev' | ||||
| @@ -6,7 +6,7 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| @@ -15,10 +15,20 @@ wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| 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 | ||||
| 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 | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -6,7 +6,7 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| @@ -15,10 +15,20 @@ wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| 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 | ||||
| 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 | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -4,9 +4,9 @@ cd /root | ||||
|  | ||||
| # Install dependencies and tools | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| @@ -15,11 +15,21 @@ wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| 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 | ||||
| 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 | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev' | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev' | ||||
| @@ -12,7 +12,7 @@ apt update | ||||
| # Install dependencies and tools | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev libudev-dev | ||||
|             libcodec2-dev libudev-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| @@ -41,6 +41,16 @@ make install | ||||
| ldconfig | ||||
| cd ../../ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| 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 | ||||
| @@ -56,7 +66,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 | ||||
| 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 | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| # Generate package | ||||
|   | ||||
| @@ -6,7 +6,7 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| @@ -15,10 +15,20 @@ wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| 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 | ||||
| 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 | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -6,7 +6,7 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \ | ||||
|             libcodec2-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| @@ -15,10 +15,20 @@ wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run | ||||
| cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| 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 | ||||
| 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 | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -81,7 +81,7 @@ bundle_find_full_path() { | ||||
|  | ||||
|         # Correct dep path | ||||
|         echo $RPATH/$RPATH_NEXT | ||||
|         return | ||||
|         return -1 | ||||
|     done | ||||
|  | ||||
|     # Search other common paths | ||||
|   | ||||
| @@ -38,6 +38,7 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules | ||||
| 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/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/rfspace_source/rfspace_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_sdr_source/rtl_sdr_source.dylib | ||||
| @@ -62,6 +63,7 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_module | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/discord_integration/discord_integration.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/frequency_manager/frequency_manager.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/recorder/recorder.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_client/rigctl_client.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_server/rigctl_server.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/scanner/scanner.dylib | ||||
|  | ||||
|   | ||||
| @@ -32,6 +32,9 @@ 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/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/libiio.dll' sdrpp_windows_x64/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/libad9361.dll' sdrpp_windows_x64/ | ||||
|   | ||||
| @@ -5,4 +5,5 @@ file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| target_include_directories(recorder PRIVATE "src/") | ||||
| target_include_directories(recorder PRIVATE "src/") | ||||
| target_include_directories(recorder PRIVATE "../../decoder_modules/radio/src") | ||||
| @@ -21,9 +21,11 @@ | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <utils/wav.h> | ||||
| #include <radio_interface.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
| #define SILENCE_LVL 10e-20 | ||||
|  | ||||
| #define SILENCE_LVL 10e-6 | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "recorder", | ||||
| @@ -167,7 +169,7 @@ public: | ||||
|  | ||||
|         // Open file | ||||
|         std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband"; | ||||
|         std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? gui::waterfall.selectedVFO : ""; | ||||
|         std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : ""; | ||||
|         std::string extension = ".wav"; | ||||
|         std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension); | ||||
|         if (!writer.open(expandedPath)) { | ||||
| @@ -315,11 +317,11 @@ private: | ||||
|             } | ||||
|             if (_this->recording) { style::endDisabled(); } | ||||
|  | ||||
|             // if (ImGui::Checkbox(CONCAT("Ignore silence##_recorder_ignore_silence_", _this->name), &_this->ignoreSilence)) { | ||||
|             //     config.acquire(); | ||||
|             //     config.conf[_this->name]["ignoreSilence"] = _this->ignoreSilence; | ||||
|             //     config.release(true); | ||||
|             // } | ||||
|             if (ImGui::Checkbox(CONCAT("Ignore silence##_recorder_ignore_silence_", _this->name), &_this->ignoreSilence)) { | ||||
|                 config.acquire(); | ||||
|                 config.conf[_this->name]["ignoreSilence"] = _this->ignoreSilence; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Record button | ||||
| @@ -338,7 +340,13 @@ private: | ||||
|             uint64_t seconds = _this->writer.getSamplesWritten() / _this->samplerate; | ||||
|             time_t diff = seconds; | ||||
|             tm* dtm = gmtime(&diff); | ||||
|             ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec); | ||||
|  | ||||
|             if (_this->ignoreSilence && _this->ignoringSilence) { | ||||
|                 ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Paused %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec); | ||||
|             } | ||||
|             else { | ||||
|                 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -430,6 +438,17 @@ private: | ||||
|         if (dbLvl.r > lvl.r) { lvl.r = dbLvl.r; } | ||||
|     } | ||||
|  | ||||
|     std::map<int, const char*> radioModeToString = { | ||||
|         { RADIO_IFACE_MODE_NFM, "NFM" }, | ||||
|         { RADIO_IFACE_MODE_WFM, "WFM" }, | ||||
|         { RADIO_IFACE_MODE_AM,  "AM"  }, | ||||
|         { RADIO_IFACE_MODE_DSB, "DSB" }, | ||||
|         { RADIO_IFACE_MODE_USB, "USB" }, | ||||
|         { RADIO_IFACE_MODE_CW,  "CW"  }, | ||||
|         { RADIO_IFACE_MODE_LSB, "LSB" }, | ||||
|         { RADIO_IFACE_MODE_RAW, "RAW" } | ||||
|     }; | ||||
|  | ||||
|     std::string genFileName(std::string templ, std::string type, std::string name) { | ||||
|         // Get data | ||||
|         time_t now = time(0); | ||||
| @@ -448,6 +467,7 @@ private: | ||||
|         char dayStr[128]; | ||||
|         char monStr[128]; | ||||
|         char yearStr[128]; | ||||
|         const char* modeStr = "Unknown"; | ||||
|         sprintf(freqStr, "%.0lfHz", freq); | ||||
|         sprintf(hourStr, "%02d", ltm->tm_hour); | ||||
|         sprintf(minStr, "%02d", ltm->tm_min); | ||||
| @@ -455,6 +475,11 @@ private: | ||||
|         sprintf(dayStr, "%02d", ltm->tm_mday); | ||||
|         sprintf(monStr, "%02d", ltm->tm_mon + 1); | ||||
|         sprintf(yearStr, "%02d", ltm->tm_year + 1900); | ||||
|         if (core::modComManager.getModuleName(name) == "radio") { | ||||
|             int mode; | ||||
|             core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode); | ||||
|             modeStr = radioModeToString[mode]; | ||||
|         } | ||||
|  | ||||
|         // Replace in template | ||||
|         templ = std::regex_replace(templ, std::regex("\\$t"), type); | ||||
| @@ -465,6 +490,7 @@ private: | ||||
|         templ = std::regex_replace(templ, std::regex("\\$d"), dayStr); | ||||
|         templ = std::regex_replace(templ, std::regex("\\$M"), monStr); | ||||
|         templ = std::regex_replace(templ, std::regex("\\$y"), yearStr); | ||||
|         templ = std::regex_replace(templ, std::regex("\\$r"), modeStr); | ||||
|         return templ; | ||||
|     } | ||||
|  | ||||
| @@ -480,13 +506,31 @@ private: | ||||
|  | ||||
|     static void stereoHandler(dsp::stereo_t* data, int count, void* ctx) { | ||||
|         RecorderModule* _this = (RecorderModule*)ctx; | ||||
|         // TODO: Ignore silence | ||||
|         if (_this->ignoreSilence) { | ||||
|             float absMax = 0.0f; | ||||
|             float* _data = (float*)data; | ||||
|             int _count = count * 2; | ||||
|             for (int i = 0; i < _count; i++) { | ||||
|                 float val = fabsf(_data[i]); | ||||
|                 if (val > absMax) { absMax = val; } | ||||
|             } | ||||
|             _this->ignoringSilence = (absMax < SILENCE_LVL); | ||||
|             if (_this->ignoringSilence) { return; } | ||||
|         } | ||||
|         _this->writer.write((float*)data, count); | ||||
|     } | ||||
|  | ||||
|     static void monoHandler(float* data, int count, void* ctx) { | ||||
|         RecorderModule* _this = (RecorderModule*)ctx; | ||||
|         // TODO: Ignore silence | ||||
|         if (_this->ignoreSilence) { | ||||
|             float absMax = 0.0f; | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 float val = fabsf(data[i]); | ||||
|                 if (val > absMax) { absMax = val; } | ||||
|             } | ||||
|             _this->ignoringSilence = (absMax < SILENCE_LVL); | ||||
|             if (_this->ignoringSilence) { return; } | ||||
|         } | ||||
|         _this->writer.write(data, count); | ||||
|     } | ||||
|  | ||||
| @@ -529,6 +573,7 @@ private: | ||||
|     dsp::stereo_t audioLvl = { -100.0f, -100.0f }; | ||||
|  | ||||
|     bool recording = false; | ||||
|     bool ignoringSilence = false; | ||||
|     wav::Writer writer; | ||||
|     std::recursive_mutex recMtx; | ||||
|     dsp::stream<dsp::complex_t>* basebandStream; | ||||
|   | ||||
| @@ -45,6 +45,9 @@ public: | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         _retuneHandler.ctx = this; | ||||
|         _retuneHandler.handler = retuneHandler; | ||||
|  | ||||
|         gui::menu.registerEntry(name, menuHandler, this, NULL); | ||||
|     } | ||||
|  | ||||
| @@ -84,8 +87,8 @@ public: | ||||
|  | ||||
|         // Switch source to panadapter mode | ||||
|         sigpath::sourceManager.setPanadpterIF(ifFreq); | ||||
|         sigpath::sourceManager.setTuningMode(TUNING_MODE_PANADAPTER); | ||||
|         retuneHandlerId = sigpath::sourceManager.onRetune.bind(retuneHandler, this); | ||||
|         sigpath::sourceManager.setTuningMode(SourceManager::TuningMode::PANADAPTER); | ||||
|         sigpath::sourceManager.onRetune.bindHandler(&_retuneHandler); | ||||
|  | ||||
|         running = true; | ||||
|     } | ||||
| @@ -95,8 +98,8 @@ public: | ||||
|         if (!running) { return; } | ||||
|  | ||||
|         // Switch source back to normal mode | ||||
|         sigpath::sourceManager.onRetune.unbind(retuneHandlerId); | ||||
|         sigpath::sourceManager.setTuningMode(TUNING_MODE_NORMAL); | ||||
|         sigpath::sourceManager.onRetune.unbindHandler(&_retuneHandler); | ||||
|         sigpath::sourceManager.setTuningMode(SourceManager::TuningMode::NORMAL); | ||||
|  | ||||
|         // Disconnect from rigctl server | ||||
|         client->close(); | ||||
| @@ -156,9 +159,10 @@ private: | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void retuneHandler(double freq) { | ||||
|         if (!client || !client->isOpen()) { return; } | ||||
|         if (client->setFreq(freq)) { | ||||
|     static void retuneHandler(double freq, void* ctx) { | ||||
|         RigctlClientModule* _this = (RigctlClientModule*)ctx; | ||||
|         if (!_this->client || !_this->client->isOpen()) { return; } | ||||
|         if (_this->client->setFreq(freq)) { | ||||
|             flog::error("Could not set frequency"); | ||||
|         } | ||||
|     } | ||||
| @@ -174,7 +178,7 @@ private: | ||||
|  | ||||
|     double ifFreq = 8830000.0; | ||||
|  | ||||
|     HandlerID retuneHandlerId; | ||||
|     EventHandler<double> _retuneHandler; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|   | ||||
| @@ -333,6 +333,17 @@ private: | ||||
|         _this->client->readAsync(1024, _this->dataBuf, dataHandler, _this, false); | ||||
|     } | ||||
|  | ||||
|     std::map<int, const char*> radioModeToString = { | ||||
|         { RADIO_IFACE_MODE_NFM, "NFM" }, | ||||
|         { RADIO_IFACE_MODE_WFM, "WFM" }, | ||||
|         { RADIO_IFACE_MODE_AM,  "AM"  }, | ||||
|         { RADIO_IFACE_MODE_DSB, "DSB" }, | ||||
|         { RADIO_IFACE_MODE_USB, "USB" }, | ||||
|         { RADIO_IFACE_MODE_CW,  "CW"  }, | ||||
|         { RADIO_IFACE_MODE_LSB, "LSB" }, | ||||
|         { RADIO_IFACE_MODE_RAW, "RAW" } | ||||
|     }; | ||||
|  | ||||
|     void commandHandler(std::string cmd) { | ||||
|         std::string corr = ""; | ||||
|         std::vector<std::string> parts; | ||||
| @@ -442,38 +453,18 @@ private: | ||||
|                 pos++; | ||||
|             } | ||||
|  | ||||
|             const std::string& newModeStr = parts[1]; | ||||
|             float newBandwidth = std::atoi(parts[2].c_str()); | ||||
|  | ||||
|             int newMode; | ||||
|             if (parts[1] == "FM") { | ||||
|                 newMode = RADIO_IFACE_MODE_NFM; | ||||
|             } | ||||
|             else if (parts[1] == "WFM") { | ||||
|                 newMode = RADIO_IFACE_MODE_WFM; | ||||
|             } | ||||
|             else if (parts[1] == "AM") { | ||||
|                 newMode = RADIO_IFACE_MODE_AM; | ||||
|             } | ||||
|             else if (parts[1] == "DSB") { | ||||
|                 newMode = RADIO_IFACE_MODE_DSB; | ||||
|             } | ||||
|             else if (parts[1] == "USB") { | ||||
|                 newMode = RADIO_IFACE_MODE_USB; | ||||
|             } | ||||
|             else if (parts[1] == "CW") { | ||||
|                 newMode = RADIO_IFACE_MODE_CW; | ||||
|             } | ||||
|             else if (parts[1] == "LSB") { | ||||
|                 newMode = RADIO_IFACE_MODE_LSB; | ||||
|             } | ||||
|             else if (parts[1] == "RAW") { | ||||
|                 newMode = RADIO_IFACE_MODE_RAW; | ||||
|             } | ||||
|             else { | ||||
|              | ||||
|             auto it = std::find_if(radioModeToString.begin(), radioModeToString.end(), [&newModeStr](const auto& e) { | ||||
|                 return e.second == newModeStr; | ||||
|             }); | ||||
|             if (it == radioModeToString.end()) { | ||||
|                 resp = "RPRT 1\n"; | ||||
|                 client->write(resp.size(), (uint8_t*)resp.c_str()); | ||||
|                 return; | ||||
|             } | ||||
|             int newMode = it->first; | ||||
|  | ||||
|             // If tuning is enabled, set the mode and optionally the bandwidth | ||||
|             if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio" && tuningEnabled) { | ||||
| @@ -492,31 +483,9 @@ private: | ||||
|             if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio") { | ||||
|                 int mode; | ||||
|                 core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_GET_MODE, NULL, &mode); | ||||
|  | ||||
|                 if (mode == RADIO_IFACE_MODE_NFM) { | ||||
|                     resp = "FM\n"; | ||||
|                 } | ||||
|                 else if (mode == RADIO_IFACE_MODE_WFM) { | ||||
|                     resp = "WFM\n"; | ||||
|                 } | ||||
|                 else if (mode == RADIO_IFACE_MODE_AM) { | ||||
|                     resp = "AM\n"; | ||||
|                 } | ||||
|                 else if (mode == RADIO_IFACE_MODE_DSB) { | ||||
|                     resp = "DSB\n"; | ||||
|                 } | ||||
|                 else if (mode == RADIO_IFACE_MODE_USB) { | ||||
|                     resp = "USB\n"; | ||||
|                 } | ||||
|                 else if (mode == RADIO_IFACE_MODE_CW) { | ||||
|                     resp = "CW\n"; | ||||
|                 } | ||||
|                 else if (mode == RADIO_IFACE_MODE_LSB) { | ||||
|                     resp = "LSB\n"; | ||||
|                 } | ||||
|                 resp = std::string(radioModeToString[mode]) + "\n"; | ||||
|             } | ||||
|  | ||||
|             if (!selectedVfo.empty()) { | ||||
|             else if (!selectedVfo.empty()) { | ||||
|                 resp += std::to_string((int)sigpath::vfoManager.getBandwidth(selectedVfo)) + "\n"; | ||||
|             } | ||||
|             else { | ||||
| @@ -690,6 +659,11 @@ private: | ||||
|                 "0\n" /* RIG_PARM_NONE */; | ||||
|             client->write(resp.size(), (uint8_t*)resp.c_str()); | ||||
|         } | ||||
|         // This get_powerstat stuff is a wordaround for WSJT-X 2.7.0 | ||||
|         else if (parts[0] == "\\get_powerstat") { | ||||
|             resp = "1\n"; | ||||
|             client->write(resp.size(), (uint8_t*)resp.c_str()); | ||||
|         } | ||||
|         else { | ||||
|             // If command is not recognized, return error | ||||
|             flog::error("Rigctl client sent invalid command: '{0}'", cmd); | ||||
|   | ||||
							
								
								
									
										40
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								readme.md
									
									
									
									
									
								
							| @@ -44,7 +44,7 @@ Download the latest release from [the Releases page](https://github.com/Alexandr | ||||
| Then, run: | ||||
|  | ||||
| ```sh | ||||
| sudo apt install libfftw3-dev libglfw3-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev | ||||
| sudo apt install libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev | ||||
| sudo dpkg -i sdrpp_debian_amd64.deb | ||||
| ``` | ||||
|  | ||||
| @@ -52,7 +52,9 @@ If `libvolk2-dev` is not available, use `libvolk1-dev`. | ||||
|  | ||||
| ### Arch-based | ||||
|  | ||||
| Install the latest release from the [sdrpp-git](https://aur.archlinux.org/packages/sdrpp-git/) AUR package | ||||
| Install from source following the instructions below. | ||||
|  | ||||
| **WARNING: The sdrpp-git AUR package is no longer official, it is not recommended to use it.** | ||||
|  | ||||
| ### Other | ||||
|  | ||||
| @@ -74,7 +76,7 @@ The preferred IDE is [VS Code](https://code.visualstudio.com/) in order to have | ||||
|  | ||||
| * [cmake](https://cmake.org) | ||||
| * [vcpkg](https://vcpkg.io) | ||||
| * [PothosSDR](https://github.com/pothosware/PothosSDR) (This will install libraries for most SDRs) | ||||
| * [PothosSDR](https://github.com/pothosware/PothosSDR) (This will install libraries for most SDRs. You have to install it in `C:/Program Files/PothosSDR`) | ||||
| * [RtAudio](https://www.music.mcgill.ca/~gary/rtaudio/) (You have to build and install it in `C:/Program Files (x86)/RtAudio/`) | ||||
|  | ||||
| After this, install the following dependencies using vcpkg: | ||||
| @@ -113,16 +115,16 @@ You will next need to edit the `root_dev/config.json` file to point to the modul | ||||
| From the top directory, you can simply run: | ||||
|  | ||||
| ```bat | ||||
| ./build/Release/sdrpp.exe -r root_dev -s | ||||
| ./build/Release/sdrpp.exe -r root_dev -c | ||||
| ``` | ||||
|  | ||||
| Or, if you wish to run from the build directory e.g. `build/Release` and adapt the relative path to the `root_dev` folder: | ||||
|  | ||||
| ```bat | ||||
| ./sdrpp.exe -r ../../root_dev -s | ||||
| ./sdrpp.exe -r ../../root_dev -c | ||||
| ``` | ||||
|  | ||||
| The optional `-s` argument is for keeping the console active in order to see the error messages. | ||||
| The optional `-c` argument is for keeping the console active in order to see the error messages. | ||||
|  | ||||
| Because all the paths are relative, for the rest of the command line instructions we are going to assume you are running from the top directory using the former command. | ||||
| As mentioned previously you need to edit `root_dev/config.json` to add the modules that were built. From the default configuration file you need to add the paths in the `modules` section. Add to this list all the modules you wish to use. | ||||
| @@ -302,7 +304,7 @@ Here is an example of build commands that will build almost all modules at the t | ||||
| ```sh | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
| cmake .. -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
| make -j<N> | ||||
| ``` | ||||
|  | ||||
| @@ -327,11 +329,12 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| |----------------------|------------|-------------------|--------------------------------|:---------------:|:-----------------------:|:---------------------------:| | ||||
| | airspy_source        | Working    | libairspy         | OPT_BUILD_AIRSPY_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | airspyhf_source      | Working    | libairspyhf       | OPT_BUILD_AIRSPYHF_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | bladerf_source       | Working    | libbladeRF        | OPT_BUILD_BLADERF_SOURCE       | ⛔              | ⚠️ (not Debian Buster) | ✅                         | | ||||
| | bladerf_source       | Working    | libbladeRF        | OPT_BUILD_BLADERF_SOURCE       | ⛔              | ✅ (not Debian Buster) | ✅                         | | ||||
| | file_source          | Working    | -                 | OPT_BUILD_FILE_SOURCE          | ✅              | ✅                     | ✅                         | | ||||
| | hackrf_source        | Working    | libhackrf         | OPT_BUILD_HACKRF_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | hermes_source        | Beta       | -                 | OPT_BUILD_HERMES_SOURCE        | ✅              | ✅                     | ⛔                         | | ||||
| | hermes_source        | Beta       | -                 | OPT_BUILD_HERMES_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | limesdr_source       | Working    | liblimesuite      | OPT_BUILD_LIMESDR_SOURCE       | ⛔              | ✅                     | ✅                         | | ||||
| | perseus_source       | Beta       | libperseus-sdr    | OPT_BUILD_PERSEUS_SOURCE       | ⛔              | ⛔                     | ⛔                         | | ||||
| | plutosdr_source      | Working    | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | rfspace_source       | Working    | -                 | OPT_BUILD_RFSPACE_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | rtl_sdr_source       | Working    | librtlsdr         | OPT_BUILD_RTL_SDR_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| @@ -348,7 +351,7 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
|  | ||||
| | Name               | Stage      | Dependencies | Option                       | Built by default| Built in Release | Enabled in SDR++ by default | | ||||
| |--------------------|------------|--------------|------------------------------|:---------------:|:----------------:|:---------------------------:| | ||||
| | android_audio_sink | Working    | -            | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | 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 | ⛔              | ✅              | ⛔                         | | ||||
| @@ -374,9 +377,9 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | discord_integration | Working    | -            | OPT_BUILD_DISCORD_PRESENCE  | ✅              | ✅               | ⛔                         | | ||||
| | frequency_manager   | Working    | -            | OPT_BUILD_FREQUENCY_MANAGER | ✅              | ✅               | ✅                         | | ||||
| | recorder            | Working    | -            | OPT_BUILD_RECORDER          | ✅              | ✅               | ✅                         | | ||||
| | rigctl_client       | Unfinished | -            | OPT_BUILD_RIGCTL_CLIENT     | ⛔              | ⛔               | ⛔                         | | ||||
| | rigctl_client       | Unfinished | -            | OPT_BUILD_RIGCTL_CLIENT     | ✅              | ✅               | ⛔                         | | ||||
| | rigctl_server       | Working    | -            | OPT_BUILD_RIGCTL_SERVER     | ✅              | ✅               | ✅                         | | ||||
| | scanner             | Beta       | -            | OPT_BUILD_SCANNER           | ✅              | ✅               | ✅                         | | ||||
| | scanner             | Beta       | -            | OPT_BUILD_SCANNER           | ✅              | ✅               | ⛔                         | | ||||
| | scheduler           | Unfinished | -            | OPT_BUILD_SCHEDULER         | ⛔              | ⛔               | ⛔                         | | ||||
|  | ||||
| # Troubleshooting | ||||
| @@ -429,25 +432,34 @@ I will soon publish a contributing.md listing the code style to use. | ||||
| * Dale L Puckett (K0HYD) | ||||
| * [Daniele D'Agnelli](https://linkedin.com/in/dagnelli) | ||||
| * D. Jones | ||||
| * Dexruus | ||||
| * [EB3FRN](https://www.eb3frn.net/) | ||||
| * Eric Johnson | ||||
| * Ernest Murphy (NH7L) | ||||
| * Flinger Films | ||||
| * [Frank Werner (HB9FXQ)](https://twitter.com/HB9FXQ) | ||||
| * gringogrigio | ||||
| * Jeff Moe | ||||
| * Joe Cupano | ||||
| * KD1SQ | ||||
| * Kezza | ||||
| * Krys Kamieniecki | ||||
| * Lee Donaghy | ||||
| * Lee KD1SQ | ||||
| * .lozenge. (Hank Hill) | ||||
| * Martin Herren (HB9FXX) | ||||
| * ON4MU | ||||
| * [Passion-Radio.com](https://passion-radio.com/) | ||||
| * Paul Maine | ||||
| * Peter Betz | ||||
| * [Scanner School](https://scannerschool.com/) | ||||
| * Scott Palmer | ||||
| * [SignalsEverywhere](https://signalseverywhere.com/) | ||||
| * Syne Ardwin (WI9SYN) | ||||
| * [W4IPA](https://twitter.com/W4IPAstroke5) | ||||
| * [Zipper](github.com/reppiZ) | ||||
| * William Arcand (W1WRA) | ||||
| * [Yves Rougy](https://www.twitch.tv/yorzian) | ||||
| * [Zipper](https://github.com/reppiZ) | ||||
|  | ||||
| ## Contributors | ||||
|  | ||||
| @@ -461,6 +473,7 @@ I will soon publish a contributing.md listing the code style to use. | ||||
| * [Howard0su](https://github.com/howard0su) | ||||
| * John Donkersley | ||||
| * [Joshua Kimsey](https://github.com/JoshuaKimsey) | ||||
| * [Manawyrm](https://github.com/Manawyrm) | ||||
| * [Martin Hauke](https://github.com/mnhauke) | ||||
| * [Marvin Sinister](https://github.com/marvin-sinister) | ||||
| * [Maxime Biette](https://github.com/mbiette) | ||||
| @@ -470,7 +483,6 @@ I will soon publish a contributing.md listing the code style to use. | ||||
| * [Shuyuan Liu](https://github.com/shuyuan-liu) | ||||
| * [Syne Ardwin (WI9SYN)](https://esaille.me/) | ||||
| * [Szymon Zakrent](https://github.com/zakrent) | ||||
| * [Tobias Mädel](https://github.com/Manawyrm) | ||||
| * Youssef Touil | ||||
| * [Zimm](https://github.com/invader-zimm) | ||||
|  | ||||
|   | ||||
							
								
								
									
										280
									
								
								root/res/bandplans/belgium.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								root/res/bandplans/belgium.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| { | ||||
|   "name": "Belgium", | ||||
|   "country_name": "Belgium", | ||||
|   "country_code": "BE", | ||||
|   "author_name": "Bastien Cabay - ON4BCY", | ||||
|   "author_url": "https://qrz.com/db/ON4BCY", | ||||
|   "bands": [ | ||||
|     { | ||||
|       "name": "2200m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 135700, | ||||
|       "end": 137800 | ||||
|     }, | ||||
|     { | ||||
|       "name": "630m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 472000, | ||||
|       "end": 479000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "600m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 501000, | ||||
|       "end": 504000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 526500, | ||||
|       "end": 1606500 | ||||
|     }, | ||||
|     { | ||||
|       "name": "160m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 1810000, | ||||
|       "end": 2000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "80m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 3500000, | ||||
|       "end": 3800000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "60m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 5351500, | ||||
|       "end": 5366500 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 5950000, | ||||
|       "end": 6200000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "40m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 7000000, | ||||
|       "end": 7200000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 7200000, | ||||
|       "end": 7300000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 9500000, | ||||
|       "end": 9900000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "30m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 10100000, | ||||
|       "end": 10150000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 11650000, | ||||
|       "end": 12050000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 13600000, | ||||
|       "end": 13800000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "20m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 14000000, | ||||
|       "end": 14350000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 15100000, | ||||
|       "end": 15600000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 17550000, | ||||
|       "end": 17900000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "17m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 18068000, | ||||
|       "end": 18168000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "15m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 21000000, | ||||
|       "end": 21450000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 21450000, | ||||
|       "end": 21850000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "12m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 24890000, | ||||
|       "end": 24990000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "AM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 25670000, | ||||
|       "end": 26100000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "11m - Citizen Band", | ||||
|       "type": "amateur", | ||||
|       "start": 26960000, | ||||
|       "end": 27410000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "10m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 28000000, | ||||
|       "end": 29700000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "8m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 40660000, | ||||
|       "end": 40690000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "6m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 50000000, | ||||
|       "end": 52000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "4m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 69945000, | ||||
|       "end": 69955000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "4m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 70190000, | ||||
|       "end": 70412500 | ||||
|     }, | ||||
|     { | ||||
|       "name": "FM Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 87500000, | ||||
|       "end": 108000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Space Exploration / Meteorology Sat. / S-PCS", | ||||
|       "type": "satellite", | ||||
|       "start": 137000000, | ||||
|       "end": 138000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "2m - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 144000000, | ||||
|       "end": 146000000 | ||||
|     }, | ||||
|  | ||||
|     { | ||||
|       "name": "T-DAB Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 174000000, | ||||
|       "end": 223000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "70cm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 430000000, | ||||
|       "end": 440000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "PMR446", | ||||
|       "type": "amateur", | ||||
|       "start": 446000000, | ||||
|       "end": 446200000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "DVB-T - Broadcast", | ||||
|       "type": "broadcast", | ||||
|       "start": 470000000, | ||||
|       "end": 790000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "23cm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 1240000000, | ||||
|       "end": 1300000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "13cm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 2300000000, | ||||
|       "end": 2450000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "6cm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 5650000000, | ||||
|       "end": 5850000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "3cm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 10000000000, | ||||
|       "end": 10500000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "1.25cm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 24000000000, | ||||
|       "end": 24250000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "6mm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 47000000000, | ||||
|       "end": 47200000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "4mm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 75500000000, | ||||
|       "end": 81000000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "2.5mm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 122250000000, | ||||
|       "end": 123000000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "2mm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 142000000000, | ||||
|       "end": 149000000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "1mm - Amateur", | ||||
|       "type": "amateur", | ||||
|       "start": 241000000000, | ||||
|       "end": 250000000000 | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -115,7 +115,7 @@ | ||||
|             "end": 4995000 | ||||
|         }, | ||||
|         { | ||||
| 			"name": "60m - radiodiffusion", | ||||
| 	    "name": "60m - radiodiffusion", | ||||
|             "type": "broadcast", | ||||
|             "start": 5005000, | ||||
|             "end": 5060000 | ||||
| @@ -340,7 +340,7 @@ | ||||
|             "name": "11m - CB", | ||||
|             "type": "amateur", | ||||
|             "start": 26960000, | ||||
|             "end": 27230000 | ||||
|             "end": 27410000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "10m - Radioamateur", | ||||
| @@ -493,4 +493,4 @@ | ||||
|             "end": 250000000000 | ||||
|         }		 | ||||
|     ] | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|     "name": "Germany", | ||||
|     "country_name": "Germany", | ||||
|     "country_code": "DE", | ||||
|     "author_name": "Tobias Mädel", | ||||
|     "author_name": "Manawyrm", | ||||
|     "author_url": "https://tbspace.de", | ||||
|     "bands": [ | ||||
|         { | ||||
|   | ||||
							
								
								
									
										3177
									
								
								root/res/bandplans/netherlands.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3177
									
								
								root/res/bandplans/netherlands.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -529,9 +529,9 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "Train communications", | ||||
|             "type": "aviation", | ||||
|             "start": 151775000, | ||||
|             "end": 151875000 | ||||
|             "type": "railway", | ||||
|             "start": 151712500, | ||||
|             "end": 156012500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Marine", | ||||
| @@ -557,12 +557,6 @@ | ||||
|             "start": 270000000, | ||||
|             "end": 380000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Train communications", | ||||
|             "type": "aviation", | ||||
|             "start": 299999000, | ||||
|             "end": 300001000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "70cm", | ||||
|             "type": "amateur", | ||||
|   | ||||
| @@ -5,6 +5,12 @@ | ||||
|     "author_name": "John Donkersley", | ||||
|     "author_url": "", | ||||
|     "bands": [ | ||||
|     { | ||||
|             "name": "2200m Ham Band", | ||||
|             "type": "amateur", | ||||
|             "start": 135700, | ||||
|             "end": 137800 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Long Wave", | ||||
|             "type": "broadcast", | ||||
| @@ -209,18 +215,18 @@ | ||||
|             "start": 15010000, | ||||
|             "end": 15100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Maritime", | ||||
|             "type": "marine", | ||||
|             "start": 16360000, | ||||
|             "end": 17410000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "19m Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15100000, | ||||
|             "end": 15800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Maritime", | ||||
|             "type": "marine", | ||||
|             "start": 16360000, | ||||
|             "end": 17410000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "16m Broadcast", | ||||
|             "type": "broadcast", | ||||
| @@ -345,7 +351,7 @@ | ||||
|             "name": "Air Band TACAN/ILS", | ||||
|             "type": "aviation", | ||||
|             "start": 108000000, | ||||
|             "end": 118000000 | ||||
|             "end": 117975000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Air Band Voice", | ||||
| @@ -371,6 +377,12 @@ | ||||
|             "start": 147343750, | ||||
|             "end": 147500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Satellites", | ||||
|             "type": "satellite", | ||||
|             "start": 148000000, | ||||
|             "end": 150050000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Pagers - Flex/POCSAG", | ||||
|             "type": "PMR", | ||||
| @@ -425,6 +437,18 @@ | ||||
|             "start": 230000000, | ||||
|             "end": 400000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Satellites", | ||||
|             "type": "satellite", | ||||
|             "start": 399900000, | ||||
|             "end": 401000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Weather Balloons", | ||||
|             "type": "aviation", | ||||
|             "start": 401000000, | ||||
|             "end": 406000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Private Mobile Radio inc trams", | ||||
|             "type": "PMR", | ||||
| @@ -462,22 +486,34 @@ | ||||
|             "end": 455000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Private Mobile Radio", | ||||
|             "name": "Private Mobile Radio inc OB", | ||||
|             "type": "PMR", | ||||
|             "start": 455000000, | ||||
|             "end": 467200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Outside Broadcast Talkback", | ||||
|             "type": "PMR", | ||||
|             "start": 467200000, | ||||
|             "end": 468600000 | ||||
|             "end": 470000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Digital TV Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 470000000, | ||||
|             "end": 790000000 | ||||
|             "end": 700000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Cell phones", | ||||
|             "type": "cellular", | ||||
|             "start": 703000000, | ||||
|             "end": 788000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 20 Cell phone downlink", | ||||
|             "type": "cellular", | ||||
|             "start": 791000000, | ||||
|             "end": 821000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 20 Cell phone uplink", | ||||
|             "type": "cellular", | ||||
|             "start": 832000000, | ||||
|             "end": 862000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Licence Exempt Short Range", | ||||
| @@ -485,12 +521,84 @@ | ||||
|             "start": 862000000, | ||||
|             "end": 875800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 8 Cell phone uplink", | ||||
|             "type": "cellular", | ||||
|             "start": 880100000, | ||||
|             "end": 914900000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 8 Cell phone downlink", | ||||
|             "type": "cellular", | ||||
|             "start": 925100000, | ||||
|             "end": 929500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "23cm Ham Band", | ||||
|             "type": "amateur", | ||||
|             "start": 1240000000, | ||||
|             "end": 1325000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 32 Cell phone", | ||||
|             "type": "cellular", | ||||
|             "start": 1452000000, | ||||
|             "end": 1492000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Satellite L-band", | ||||
|             "type": "satellite", | ||||
|             "start": 1518000000, | ||||
|             "end": 1559000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Satellite L-band", | ||||
|             "type": "satellite", | ||||
|             "start": 1626500000, | ||||
|             "end": 1660500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Satellite L-band", | ||||
|             "type": "satellite", | ||||
|             "start": 1668000000, | ||||
|             "end": 1675000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 3 Cell phone uplink", | ||||
|             "type": "cellular", | ||||
|             "start": 1710000000, | ||||
|             "end": 1785000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 3 Cell phone downlink", | ||||
|             "type": "cellular", | ||||
|             "start": 1805100000, | ||||
|             "end": 1880000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "DECT cordless phones", | ||||
|             "type": "cellular", | ||||
|             "start": 1880000000, | ||||
|             "end": 1900000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 3 Cell phones", | ||||
|             "type": "cellular", | ||||
|             "start": 1900000000, | ||||
|             "end": 1920000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 1 Cell phone uplink", | ||||
|             "type": "cellular", | ||||
|             "start": 1920000000, | ||||
|             "end": 1979700000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 1 Cell phone downlink", | ||||
|             "type": "cellular", | ||||
|             "start": 2110300000, | ||||
|             "end": 2169700000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "13cm Ham Band", | ||||
|             "type": "amateur", | ||||
| @@ -498,10 +606,28 @@ | ||||
|             "end": 2302000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "13cm Ham Band", | ||||
|             "type": "amateur", | ||||
|             "start": 2310000000, | ||||
|             "end": 2450000000 | ||||
|             "name": "ISM - wifi and bluettoth", | ||||
|             "type": "ISM", | ||||
|             "start": 2400000000, | ||||
|             "end": 2483000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Band 38 Cell phones", | ||||
|             "type": "cellular", | ||||
|             "start": 2500000000, | ||||
|             "end": 269000000 | ||||
|         }, | ||||
|                 { | ||||
|             "name": "Band 42 5G Cell phones", | ||||
|             "type": "cellular", | ||||
|             "start": 3410000000, | ||||
|             "end": 3720000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "ISM - wifi", | ||||
|             "type": "ISM", | ||||
|             "start": 5150000000, | ||||
|             "end": 5850000000 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
|             ] | ||||
| } | ||||
|   | ||||
| @@ -227,6 +227,18 @@ | ||||
|             "start": 144000000, | ||||
|             "end": 148000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "MURS (lower)", | ||||
|             "type": "amateur", | ||||
|             "start": 151820000, | ||||
|             "end": 151940000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "MURS (upper)", | ||||
|             "type": "amateur", | ||||
|             "start": 154570000, | ||||
|             "end": 154600000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Marine", | ||||
|             "type": "marine", | ||||
|   | ||||
							
								
								
									
										14
									
								
								root/res/colormaps/smoke.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								root/res/colormaps/smoke.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| { | ||||
|     "name": "Smoke", | ||||
|     "author": "Yaroslav Andrianov", | ||||
|     "map": [ | ||||
|         "#FFFFFF", | ||||
|         "#EEEEEE", | ||||
|         "#CCCCCC", | ||||
|         "#777777", | ||||
|         "#555555", | ||||
|         "#333333", | ||||
|         "#111111", | ||||
|         "#000000" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										28
									
								
								root/res/colormaps/temper_colors.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								root/res/colormaps/temper_colors.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| { | ||||
|     "name": "Temper Colors", | ||||
|     "author": "Yaroslav Andrianov", | ||||
|     "map": [ | ||||
|         "#000000", | ||||
|         "#05011f", | ||||
|         "#0f0836", | ||||
|         "#2f1436", | ||||
|         "#3d114d", | ||||
|         "#4e186f", | ||||
|         "#592a8f", | ||||
|         "#5e43a5", | ||||
|         "#5f5eb3", | ||||
|         "#6276ba", | ||||
|         "#6b8cbf", | ||||
|         "#7ba1c2", | ||||
|         "#95b5c7", | ||||
|         "#b3c6ce", | ||||
|         "#d4bcac", | ||||
|         "#cca389", | ||||
|         "#c68a6d", | ||||
|         "#be6f5b", | ||||
|         "#b25652", | ||||
|         "#a24050", | ||||
|         "#8e2c50", | ||||
|         "#741e4f" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										29
									
								
								root/res/colormaps/vivid.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								root/res/colormaps/vivid.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| { | ||||
|     "name": "Vivid", | ||||
|     "author": "Yaroslav Andrianov", | ||||
|     "map": [ | ||||
|         "#000000", | ||||
|         "#06001c", | ||||
|         "#090028", | ||||
|         "#12002c", | ||||
|         "#230039", | ||||
|         "#360143", | ||||
|         "#440154", | ||||
|         "#472c7a", | ||||
|         "#3b518b", | ||||
|         "#2c718e", | ||||
|         "#21908d", | ||||
|         "#27ad81", | ||||
|         "#5cc863", | ||||
|         "#aadc32", | ||||
|         "#f6fd25", | ||||
|         "#fdde17", | ||||
|         "#fecb31", | ||||
|         "#FE9029", | ||||
|         "#F56918", | ||||
|         "#DC3B07", | ||||
|         "#CE2D04", | ||||
|         "#AC1701", | ||||
|         "#980E01"       | ||||
|     ] | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| #!/bin/sh | ||||
| set -e | ||||
|  | ||||
| [ $(id -u) = 0 ] && echo "Please do not run this script as root" && exit 100 | ||||
|  | ||||
| echo "Installing dependencies" | ||||
| sudo apt update | ||||
| sudo apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ | ||||
|             libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget | ||||
|  | ||||
| echo "Preparing build" | ||||
| mkdir -p build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_LIMESDR_SOURCE=ON | ||||
|  | ||||
| echo "Building" | ||||
| make | ||||
|  | ||||
| echo "Installing" | ||||
| sudo make install | ||||
|  | ||||
| echo "Done!" | ||||
| @@ -45,16 +45,20 @@ public: | ||||
|         int count = audio.getDeviceCount(); | ||||
|         RtAudio::DeviceInfo info; | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             info = audio.getDeviceInfo(i); | ||||
|             if (!info.probed) { continue; } | ||||
|             if (info.outputChannels == 0) { continue; } | ||||
|             if (info.isDefaultOutput) { defaultDevId = devList.size(); } | ||||
|             devList.push_back(info); | ||||
|             deviceIds.push_back(i); | ||||
|             txtDevList += info.name; | ||||
|             txtDevList += '\0'; | ||||
|             try { | ||||
|                 info = audio.getDeviceInfo(i); | ||||
|                 if (!info.probed) { continue; } | ||||
|                 if (info.outputChannels == 0) { continue; } | ||||
|                 if (info.isDefaultOutput) { defaultDevId = devList.size(); } | ||||
|                 devList.push_back(info); | ||||
|                 deviceIds.push_back(i); | ||||
|                 txtDevList += info.name; | ||||
|                 txtDevList += '\0'; | ||||
|             } | ||||
|             catch (std::exception e) { | ||||
|                 flog::error("AudioSinkModule Error getting audio device info: {0}", e.what()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         selectByName(device); | ||||
|     } | ||||
|  | ||||
| @@ -290,4 +294,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -25,7 +25,7 @@ SDRPP_MOD_INFO{ | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| class AirspySourceModule : public ModuleManager::Instance, public Source { | ||||
| class AirspySourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     AirspySourceModule(std::string name) { | ||||
|         this->name = name; | ||||
| @@ -34,6 +34,15 @@ public: | ||||
|  | ||||
|         sampleRate = 10000000.0; | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &stream; | ||||
|  | ||||
|         refresh(); | ||||
|         if (sampleRateList.size() > 0) { | ||||
|             sampleRate = sampleRateList[0]; | ||||
| @@ -45,11 +54,11 @@ public: | ||||
|         config.release(); | ||||
|         selectByString(devSerial); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("Airspy", this); | ||||
|         sigpath::sourceManager.registerSource("Airspy", &handler); | ||||
|     } | ||||
|  | ||||
|     ~AirspySourceModule() { | ||||
|         stop(); | ||||
|         stop(this); | ||||
|         sigpath::sourceManager.unregisterSource("Airspy"); | ||||
|         airspy_exit(); | ||||
|     } | ||||
| @@ -222,315 +231,6 @@ public: | ||||
|         airspy_close(dev); | ||||
|     } | ||||
|  | ||||
|         void select() { | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|         flog::info("AirspySourceModule '{0}': Select!", name); | ||||
|     } | ||||
|  | ||||
|     void deselect() { | ||||
|         flog::info("AirspySourceModule '{0}': Deselect!", name); | ||||
|     } | ||||
|  | ||||
|     bool start() { | ||||
|         if (running) { return true; } | ||||
|         if (selectedSerial == 0) { | ||||
|             flog::error("Tried to start Airspy source with null serial"); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
| #ifndef __ANDROID__ | ||||
|         int err = airspy_open_sn(&openDev, selectedSerial); | ||||
| #else | ||||
|         int err = airspy_open_fd(&openDev, devFd); | ||||
| #endif | ||||
|         if (err != 0) { | ||||
|             char buf[1024]; | ||||
|             sprintf(buf, "%016" PRIX64, selectedSerial); | ||||
|             flog::error("Could not open Airspy {0}", buf); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         airspy_set_samplerate(openDev, sampleRateList[srId]); | ||||
|         airspy_set_freq(openDev, freq); | ||||
|  | ||||
|         if (gainMode == 0) { | ||||
|             airspy_set_lna_agc(openDev, 0); | ||||
|             airspy_set_mixer_agc(openDev, 0); | ||||
|             airspy_set_sensitivity_gain(openDev, sensitiveGain); | ||||
|         } | ||||
|         else if (gainMode == 1) { | ||||
|             airspy_set_lna_agc(openDev, 0); | ||||
|             airspy_set_mixer_agc(openDev, 0); | ||||
|             airspy_set_linearity_gain(openDev, linearGain); | ||||
|         } | ||||
|         else if (gainMode == 2) { | ||||
|             if (lnaAgc) { | ||||
|                 airspy_set_lna_agc(openDev, 1); | ||||
|             } | ||||
|             else { | ||||
|                 airspy_set_lna_agc(openDev, 0); | ||||
|                 airspy_set_lna_gain(openDev, lnaGain); | ||||
|             } | ||||
|             if (mixerAgc) { | ||||
|                 airspy_set_mixer_agc(openDev, 1); | ||||
|             } | ||||
|             else { | ||||
|                 airspy_set_mixer_agc(openDev, 0); | ||||
|                 airspy_set_mixer_gain(openDev, mixerGain); | ||||
|             } | ||||
|             airspy_set_vga_gain(openDev, vgaGain); | ||||
|         } | ||||
|  | ||||
|         airspy_set_rf_bias(openDev, biasT); | ||||
|  | ||||
|         airspy_start_rx(openDev, callback, this); | ||||
|  | ||||
|         running = true; | ||||
|         flog::info("AirspySourceModule '{0}': Start!", name); | ||||
|     } | ||||
|  | ||||
|     void stop() { | ||||
|         if (!running) { return; } | ||||
|         running = false; | ||||
|         stream.stopWriter(); | ||||
|         airspy_close(openDev); | ||||
|         stream.clearWriteStop(); | ||||
|         flog::info("AirspySourceModule '{0}': Stop!", name); | ||||
|     } | ||||
|  | ||||
|     void tune(double freq) { | ||||
|         this->freq = freq; | ||||
|         if (running) { | ||||
|             airspy_set_freq(openDev, freq); | ||||
|         } | ||||
|         flog::info("AirspySourceModule '{0}': Tune: {1}!", name, freq); | ||||
|     } | ||||
|  | ||||
|     void showMenu() { | ||||
|         if (running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_airspy_dev_sel_", name), &devId, devListTxt.c_str())) { | ||||
|             selectBySerial(devList[devId]); | ||||
|             core::setInputSampleRate(sampleRate); | ||||
|             if (selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["device"] = selectedSerStr; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_airspy_sr_sel_", name), &srId, sampleRateListTxt.c_str())) { | ||||
|             sampleRate = sampleRateList[srId]; | ||||
|             core::setInputSampleRate(sampleRate); | ||||
|             if (selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][selectedSerStr]["sampleRate"] = sampleRate; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_airspy_refr_", name))) { | ||||
|             refresh(); | ||||
|             config.acquire(); | ||||
|             std::string devSerial = config.conf["device"]; | ||||
|             config.release(); | ||||
|             selectByString(devSerial); | ||||
|             core::setInputSampleRate(sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::BeginGroup(); | ||||
|         SmGui::Columns(3, CONCAT("AirspyGainModeColumns##_", name), false); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", name), gainMode == 0)) { | ||||
|             gainMode = 0; | ||||
|             if (running) { | ||||
|                 airspy_set_lna_agc(openDev, 0); | ||||
|                 airspy_set_mixer_agc(openDev, 0); | ||||
|                 airspy_set_sensitivity_gain(openDev, sensitiveGain); | ||||
|             } | ||||
|             if (selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][selectedSerStr]["gainMode"] = 0; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         SmGui::NextColumn(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::RadioButton(CONCAT("Linear##_airspy_gm_", name), gainMode == 1)) { | ||||
|             gainMode = 1; | ||||
|             if (running) { | ||||
|                 airspy_set_lna_agc(openDev, 0); | ||||
|                 airspy_set_mixer_agc(openDev, 0); | ||||
|                 airspy_set_linearity_gain(openDev, linearGain); | ||||
|             } | ||||
|             if (selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][selectedSerStr]["gainMode"] = 1; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         SmGui::NextColumn(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::RadioButton(CONCAT("Free##_airspy_gm_", name), gainMode == 2)) { | ||||
|             gainMode = 2; | ||||
|             if (running) { | ||||
|                 if (lnaAgc) { | ||||
|                     airspy_set_lna_agc(openDev, 1); | ||||
|                 } | ||||
|                 else { | ||||
|                     airspy_set_lna_agc(openDev, 0); | ||||
|                     airspy_set_lna_gain(openDev, lnaGain); | ||||
|                 } | ||||
|                 if (mixerAgc) { | ||||
|                     airspy_set_mixer_agc(openDev, 1); | ||||
|                 } | ||||
|                 else { | ||||
|                     airspy_set_mixer_agc(openDev, 0); | ||||
|                     airspy_set_mixer_gain(openDev, mixerGain); | ||||
|                 } | ||||
|                 airspy_set_vga_gain(openDev, vgaGain); | ||||
|             } | ||||
|             if (selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][selectedSerStr]["gainMode"] = 2; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         SmGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", name), false); | ||||
|         SmGui::EndGroup(); | ||||
|  | ||||
|         // Gain menus | ||||
|  | ||||
|         if (gainMode == 0) { | ||||
|             SmGui::LeftLabel("Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_sens_gain_", name), &sensitiveGain, 0, 21)) { | ||||
|                 if (running) { | ||||
|                     airspy_set_sensitivity_gain(openDev, sensitiveGain); | ||||
|                 } | ||||
|                 if (selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][selectedSerStr]["sensitiveGain"] = sensitiveGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if (gainMode == 1) { | ||||
|             SmGui::LeftLabel("Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_lin_gain_", name), &linearGain, 0, 21)) { | ||||
|                 if (running) { | ||||
|                     airspy_set_linearity_gain(openDev, linearGain); | ||||
|                 } | ||||
|                 if (selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][selectedSerStr]["linearGain"] = linearGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if (gainMode == 2) { | ||||
|             // TODO: Switch to a table for alignment | ||||
|             if (lnaAgc) { SmGui::BeginDisabled(); } | ||||
|             SmGui::LeftLabel("LNA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_lna_gain_", name), &lnaGain, 0, 15)) { | ||||
|                 if (running) { | ||||
|                     airspy_set_lna_gain(openDev, lnaGain); | ||||
|                 } | ||||
|                 if (selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][selectedSerStr]["lnaGain"] = lnaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             if (lnaAgc) { SmGui::EndDisabled(); } | ||||
|  | ||||
|             if (mixerAgc) { SmGui::BeginDisabled(); } | ||||
|             SmGui::LeftLabel("Mixer Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_mix_gain_", name), &mixerGain, 0, 15)) { | ||||
|                 if (running) { | ||||
|                     airspy_set_mixer_gain(openDev, mixerGain); | ||||
|                 } | ||||
|                 if (selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][selectedSerStr]["mixerGain"] = mixerGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             if (mixerAgc) { SmGui::EndDisabled(); } | ||||
|  | ||||
|             SmGui::LeftLabel("VGA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_vga_gain_", name), &vgaGain, 0, 15)) { | ||||
|                 if (running) { | ||||
|                     airspy_set_vga_gain(openDev, vgaGain); | ||||
|                 } | ||||
|                 if (selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][selectedSerStr]["vgaGain"] = vgaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // AGC Control | ||||
|             SmGui::ForceSync(); | ||||
|             if (SmGui::Checkbox(CONCAT("LNA AGC##_airspy_", name), &lnaAgc)) { | ||||
|                 if (running) { | ||||
|                     if (lnaAgc) { | ||||
|                         airspy_set_lna_agc(openDev, 1); | ||||
|                     } | ||||
|                     else { | ||||
|                         airspy_set_lna_agc(openDev, 0); | ||||
|                         airspy_set_lna_gain(openDev, lnaGain); | ||||
|                     } | ||||
|                 } | ||||
|                 if (selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][selectedSerStr]["lnaAgc"] = lnaAgc; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             SmGui::ForceSync(); | ||||
|             if (SmGui::Checkbox(CONCAT("Mixer AGC##_airspy_", name), &mixerAgc)) { | ||||
|                 if (running) { | ||||
|                     if (mixerAgc) { | ||||
|                         airspy_set_mixer_agc(openDev, 1); | ||||
|                     } | ||||
|                     else { | ||||
|                         airspy_set_mixer_agc(openDev, 0); | ||||
|                         airspy_set_mixer_gain(openDev, mixerGain); | ||||
|                     } | ||||
|                 } | ||||
|                 if (selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][selectedSerStr]["mixerAgc"] = mixerAgc; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Bias T | ||||
|         if (SmGui::Checkbox(CONCAT("Bias T##_airspy_", name), &biasT)) { | ||||
|             if (running) { | ||||
|                 airspy_set_rf_bias(openDev, biasT); | ||||
|             } | ||||
|             if (selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][selectedSerStr]["biasT"] = biasT; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string getBandwdithScaled(double bw) { | ||||
|         char buf[1024]; | ||||
| @@ -546,6 +246,322 @@ private: | ||||
|         return std::string(buf); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("AirspySourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         flog::info("AirspySourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|         if (_this->selectedSerial == 0) { | ||||
|             flog::error("Tried to start Airspy source with null serial"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| #ifndef __ANDROID__ | ||||
|         int err = airspy_open_sn(&_this->openDev, _this->selectedSerial); | ||||
| #else | ||||
|         int err = airspy_open_fd(&_this->openDev, _this->devFd); | ||||
| #endif | ||||
|         if (err != 0) { | ||||
|             char buf[1024]; | ||||
|             sprintf(buf, "%016" PRIX64, _this->selectedSerial); | ||||
|             flog::error("Could not open Airspy {0}", buf); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         airspy_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]); | ||||
|         airspy_set_freq(_this->openDev, _this->freq); | ||||
|  | ||||
|         if (_this->gainMode == 0) { | ||||
|             airspy_set_lna_agc(_this->openDev, 0); | ||||
|             airspy_set_mixer_agc(_this->openDev, 0); | ||||
|             airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain); | ||||
|         } | ||||
|         else if (_this->gainMode == 1) { | ||||
|             airspy_set_lna_agc(_this->openDev, 0); | ||||
|             airspy_set_mixer_agc(_this->openDev, 0); | ||||
|             airspy_set_linearity_gain(_this->openDev, _this->linearGain); | ||||
|         } | ||||
|         else if (_this->gainMode == 2) { | ||||
|             if (_this->lnaAgc) { | ||||
|                 airspy_set_lna_agc(_this->openDev, 1); | ||||
|             } | ||||
|             else { | ||||
|                 airspy_set_lna_agc(_this->openDev, 0); | ||||
|                 airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|             } | ||||
|             if (_this->mixerAgc) { | ||||
|                 airspy_set_mixer_agc(_this->openDev, 1); | ||||
|             } | ||||
|             else { | ||||
|                 airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                 airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|             } | ||||
|             airspy_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|         } | ||||
|  | ||||
|         airspy_set_rf_bias(_this->openDev, _this->biasT); | ||||
|  | ||||
|         airspy_start_rx(_this->openDev, callback, _this); | ||||
|  | ||||
|         _this->running = true; | ||||
|         flog::info("AirspySourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|         _this->stream.stopWriter(); | ||||
|         airspy_close(_this->openDev); | ||||
|         _this->stream.clearWriteStop(); | ||||
|         flog::info("AirspySourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             airspy_set_freq(_this->openDev, freq); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("AirspySourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)ctx; | ||||
|  | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_airspy_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) { | ||||
|             _this->selectBySerial(_this->devList[_this->devId]); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["device"] = _this->selectedSerStr; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_airspy_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) { | ||||
|             _this->sampleRate = _this->sampleRateList[_this->srId]; | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_airspy_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             config.acquire(); | ||||
|             std::string devSerial = config.conf["device"]; | ||||
|             config.release(); | ||||
|             _this->selectByString(devSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::BeginGroup(); | ||||
|         SmGui::Columns(3, CONCAT("AirspyGainModeColumns##_", _this->name), false); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", _this->name), _this->gainMode == 0)) { | ||||
|             _this->gainMode = 0; | ||||
|             if (_this->running) { | ||||
|                 airspy_set_lna_agc(_this->openDev, 0); | ||||
|                 airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                 airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         SmGui::NextColumn(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::RadioButton(CONCAT("Linear##_airspy_gm_", _this->name), _this->gainMode == 1)) { | ||||
|             _this->gainMode = 1; | ||||
|             if (_this->running) { | ||||
|                 airspy_set_lna_agc(_this->openDev, 0); | ||||
|                 airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                 airspy_set_linearity_gain(_this->openDev, _this->linearGain); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         SmGui::NextColumn(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::RadioButton(CONCAT("Free##_airspy_gm_", _this->name), _this->gainMode == 2)) { | ||||
|             _this->gainMode = 2; | ||||
|             if (_this->running) { | ||||
|                 if (_this->lnaAgc) { | ||||
|                     airspy_set_lna_agc(_this->openDev, 1); | ||||
|                 } | ||||
|                 else { | ||||
|                     airspy_set_lna_agc(_this->openDev, 0); | ||||
|                     airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                 } | ||||
|                 if (_this->mixerAgc) { | ||||
|                     airspy_set_mixer_agc(_this->openDev, 1); | ||||
|                 } | ||||
|                 else { | ||||
|                     airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                     airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|                 } | ||||
|                 airspy_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|         SmGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", _this->name), false); | ||||
|         SmGui::EndGroup(); | ||||
|  | ||||
|         // Gain menus | ||||
|  | ||||
|         if (_this->gainMode == 0) { | ||||
|             SmGui::LeftLabel("Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if (_this->gainMode == 1) { | ||||
|             SmGui::LeftLabel("Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_lin_gain_", _this->name), &_this->linearGain, 0, 21)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_linearity_gain(_this->openDev, _this->linearGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if (_this->gainMode == 2) { | ||||
|             // TODO: Switch to a table for alignment | ||||
|             if (_this->lnaAgc) { SmGui::BeginDisabled(); } | ||||
|             SmGui::LeftLabel("LNA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             if (_this->lnaAgc) { SmGui::EndDisabled(); } | ||||
|  | ||||
|             if (_this->mixerAgc) { SmGui::BeginDisabled(); } | ||||
|             SmGui::LeftLabel("Mixer Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             if (_this->mixerAgc) { SmGui::EndDisabled(); } | ||||
|  | ||||
|             SmGui::LeftLabel("VGA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_airspy_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) { | ||||
|                 if (_this->running) { | ||||
|                     airspy_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // AGC Control | ||||
|             SmGui::ForceSync(); | ||||
|             if (SmGui::Checkbox(CONCAT("LNA AGC##_airspy_", _this->name), &_this->lnaAgc)) { | ||||
|                 if (_this->running) { | ||||
|                     if (_this->lnaAgc) { | ||||
|                         airspy_set_lna_agc(_this->openDev, 1); | ||||
|                     } | ||||
|                     else { | ||||
|                         airspy_set_lna_agc(_this->openDev, 0); | ||||
|                         airspy_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                     } | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|             SmGui::ForceSync(); | ||||
|             if (SmGui::Checkbox(CONCAT("Mixer AGC##_airspy_", _this->name), &_this->mixerAgc)) { | ||||
|                 if (_this->running) { | ||||
|                     if (_this->mixerAgc) { | ||||
|                         airspy_set_mixer_agc(_this->openDev, 1); | ||||
|                     } | ||||
|                     else { | ||||
|                         airspy_set_mixer_agc(_this->openDev, 0); | ||||
|                         airspy_set_mixer_gain(_this->openDev, _this->mixerGain); | ||||
|                     } | ||||
|                 } | ||||
|                 if (_this->selectedSerStr != "") { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Bias T | ||||
|         if (SmGui::Checkbox(CONCAT("Bias T##_airspy_", _this->name), &_this->biasT)) { | ||||
|             if (_this->running) { | ||||
|                 airspy_set_rf_bias(_this->openDev, _this->biasT); | ||||
|             } | ||||
|             if (_this->selectedSerStr != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static int callback(airspy_transfer_t* transfer) { | ||||
|         AirspySourceModule* _this = (AirspySourceModule*)transfer->ctx; | ||||
|         memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t)); | ||||
| @@ -558,6 +574,7 @@ private: | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|     uint64_t selectedSerial = 0; | ||||
|   | ||||
| @@ -25,7 +25,7 @@ ConfigManager config; | ||||
| struct DeviceInfo { | ||||
|     RtAudio::DeviceInfo info; | ||||
|     int id; | ||||
|     bool operator==(const struct DeviceInfo& other) { | ||||
|     bool operator==(const struct DeviceInfo& other) const { | ||||
|         return other.id == id; | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| #define NOMINMAX | ||||
| #include <imgui.h> | ||||
| #include <utils/flog.h> | ||||
| #include <module.h> | ||||
| @@ -9,6 +10,8 @@ | ||||
| #include <filesystem> | ||||
| #include <regex> | ||||
| #include <gui/tuner.h> | ||||
| #include <algorithm> | ||||
| #include <stdexcept> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| @@ -121,6 +124,12 @@ private: | ||||
|                 } | ||||
|                 try { | ||||
|                     _this->reader = new WavReader(_this->fileSelect.path); | ||||
|                     if (_this->reader->getSampleRate() == 0) { | ||||
|                         _this->reader->close(); | ||||
|                         delete _this->reader; | ||||
|                         _this->reader = NULL; | ||||
|                         throw std::runtime_error("Sample rate may not be zero"); | ||||
|                     } | ||||
|                     _this->sampleRate = _this->reader->getSampleRate(); | ||||
|                     core::setInputSampleRate(_this->sampleRate); | ||||
|                     std::string filename = std::filesystem::path(_this->fileSelect.path).filename().string(); | ||||
| @@ -130,7 +139,7 @@ private: | ||||
|                     //gui::freqSelect.maxFreq = _this->centerFreq + (_this->sampleRate/2); | ||||
|                     //gui::freqSelect.limitFreq = true; | ||||
|                 } | ||||
|                 catch (std::exception e) { | ||||
|                 catch (std::exception& e) { | ||||
|                     flog::error("Error: {0}", e.what()); | ||||
|                 } | ||||
|                 config.acquire(); | ||||
| @@ -144,8 +153,8 @@ private: | ||||
|  | ||||
|     static void worker(void* ctx) { | ||||
|         FileSourceModule* _this = (FileSourceModule*)ctx; | ||||
|         double sampleRate = _this->reader->getSampleRate(); | ||||
|         int blockSize = sampleRate / 200.0f; | ||||
|         double sampleRate = std::max(_this->reader->getSampleRate(), (uint32_t)1); | ||||
|         int blockSize = std::min((int)(sampleRate / 200.0f), (int)STREAM_BUFFER_SIZE); | ||||
|         int16_t* inBuf = new int16_t[blockSize * 2]; | ||||
|  | ||||
|         while (true) { | ||||
| @@ -159,8 +168,8 @@ private: | ||||
|  | ||||
|     static void floatWorker(void* ctx) { | ||||
|         FileSourceModule* _this = (FileSourceModule*)ctx; | ||||
|         double sampleRate = _this->reader->getSampleRate(); | ||||
|         int blockSize = sampleRate / 200.0f; | ||||
|         double sampleRate = std::max(_this->reader->getSampleRate(), (uint32_t)1); | ||||
|         int blockSize = std::min((int)(sampleRate / 200.0f), (int)STREAM_BUFFER_SIZE); | ||||
|         dsp::complex_t* inBuf = new dsp::complex_t[blockSize]; | ||||
|  | ||||
|         while (true) { | ||||
| @@ -214,4 +223,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -137,6 +137,10 @@ public: | ||||
|         hackrf_device_list_t* _devList = hackrf_device_list(); | ||||
|  | ||||
|         for (int i = 0; i < _devList->devicecount; i++) { | ||||
|             // Skip devices that are in use | ||||
|             if (_devList->serial_numbers[i] == NULL) { continue; } | ||||
|  | ||||
|             // Save the device serial number | ||||
|             devList.push_back(_devList->serial_numbers[i]); | ||||
|             devListTxt += (char*)(_devList->serial_numbers[i] + 16); | ||||
|             devListTxt += '\0'; | ||||
|   | ||||
| @@ -205,6 +205,7 @@ namespace hermes { | ||||
|     } | ||||
|  | ||||
|     std::vector<Info> discover() { | ||||
|         // TODO: Maybe try to instead detect on each interface as a work around for 0.0.0.0 not receiving anything? | ||||
|         auto sock = net::openudp("0.0.0.0", 1024); | ||||
|          | ||||
|         // Build discovery packet | ||||
|   | ||||
| @@ -39,7 +39,7 @@ namespace hermes { | ||||
|         uint8_t gatewareVerMin; | ||||
|         BoardID boardId; | ||||
|  | ||||
|         bool operator==(const Info& b) { | ||||
|         bool operator==(const Info& b) const { | ||||
|             return !memcmp(mac, b.mac, 6); | ||||
|         } | ||||
|     }; | ||||
|   | ||||
| @@ -319,6 +319,7 @@ private: | ||||
|     static void start(void* ctx) { | ||||
|         LimeSDRSourceModule* _this = (LimeSDRSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|         if (_this->selectedDevName.empty()) { return; } | ||||
|  | ||||
|         // Open device | ||||
|         _this->openDev = NULL; | ||||
| @@ -329,7 +330,10 @@ private: | ||||
|         if (err) { | ||||
|             LMS_Close(_this->openDev); | ||||
|             LMS_Open(&_this->openDev, _this->devList[_this->devId], NULL); | ||||
|             LMS_Init(_this->openDev); | ||||
|             if (err = LMS_Init(_this->openDev)) { | ||||
|                 flog::error("Failed to re-initialize device ({})", err); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         flog::warn("Channel count: {0}", LMS_GetNumChannels(_this->openDev, false)); | ||||
| @@ -546,4 +550,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										29
									
								
								source_modules/perseus_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								source_modules/perseus_source/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(perseus_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(perseus_source PRIVATE "C:/Program Files/PothosSDR/bin/") | ||||
|  | ||||
|     target_include_directories(perseus_source PUBLIC "C:/Program Files/PothosSDR/include/perseus-sdr") | ||||
|  | ||||
|     target_link_libraries(perseus_source PRIVATE perseus-sdr) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBPERSEUSSDR REQUIRED libperseus-sdr) | ||||
|  | ||||
|     target_include_directories(perseus_source PRIVATE ${LIBPERSEUSSDR_INCLUDE_DIRS}) | ||||
|     target_link_directories(perseus_source PRIVATE ${LIBPERSEUSSDR_LIBRARY_DIRS}) | ||||
|     target_link_libraries(perseus_source PRIVATE ${LIBPERSEUSSDR_LIBRARIES}) | ||||
|  | ||||
|     # Include it because for some reason pkgconfig doesn't look here? | ||||
|     if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") | ||||
|         target_include_directories(perseus_source PRIVATE "/usr/local/include") | ||||
|     endif() | ||||
|      | ||||
| endif () | ||||
							
								
								
									
										468
									
								
								source_modules/perseus_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										468
									
								
								source_modules/perseus_source/src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,468 @@ | ||||
| #include <utils/flog.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <config.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <gui/widgets/stepped_slider.h> | ||||
| #include <perseus-sdr.h> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "perseus_source", | ||||
|     /* Description:     */ "Perseus SDR source module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| #define MAX_SAMPLERATE_COUNT    128 | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| class PerseusSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     PerseusSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 768000; | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &stream; | ||||
|  | ||||
|         perseus_set_debug(9); | ||||
|  | ||||
|         refresh(); | ||||
|  | ||||
|         config.acquire(); | ||||
|         std::string serial = config.conf["device"]; | ||||
|         config.release(); | ||||
|         select(serial); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("Perseus", &handler); | ||||
|     } | ||||
|  | ||||
|     ~PerseusSourceModule() { | ||||
|         stop(this); | ||||
|         sigpath::sourceManager.unregisterSource("Perseus"); | ||||
|         if (libInit) { perseus_exit(); } | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
|     void refresh() { | ||||
|         // Re-initialize driver | ||||
|         if (libInit) { perseus_exit(); } | ||||
|         int devCount = perseus_init(); | ||||
|         if (devCount < 0) { | ||||
|             libInit = false; | ||||
|             flog::error("Could not initialize libperseus: {}", perseus_errorstr()); | ||||
|             return; | ||||
|         } | ||||
|         libInit = true; | ||||
|  | ||||
|         // Open each device to get the serial number | ||||
|         for (int i = 0; i < devCount; i++) { | ||||
|             // Open device | ||||
|             perseus_descr* dev = perseus_open(i); | ||||
|             if (!dev) { | ||||
|                 flog::error("Failed to open Perseus device with ID {}: {}", i, perseus_errorstr()); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Load firmware | ||||
|             int err = perseus_firmware_download(dev, NULL); | ||||
|             if (err) { | ||||
|                 flog::error("Could not upload firmware to device {}: {}", i, perseus_errorstr()); | ||||
|                 perseus_close(dev); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Get info | ||||
|             eeprom_prodid prodId; | ||||
|             err = perseus_get_product_id(dev, &prodId); | ||||
|             if (err) { | ||||
|                 flog::error("Could not getproduct info from device {}: {}", i, perseus_errorstr()); | ||||
|                 perseus_close(dev); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Create entry | ||||
|             char serial[128]; | ||||
|             char buf[128]; | ||||
|             sprintf(serial, "%05d", (int)prodId.sn); | ||||
|             sprintf(buf, "Perseus %d.%d [%s]", (int)prodId.hwver, (int)prodId.hwrel, serial); | ||||
|             devList.define(serial, buf, i); | ||||
|  | ||||
|             // Close device | ||||
|             perseus_close(dev); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devList.empty()) {  | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial number is not available, select first instead | ||||
|         if (!devList.keyExists(serial)) { | ||||
|             select(devList.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Open device | ||||
|         selectedSerial = serial; | ||||
|         selectedPerseusId = devList.value(devList.keyId(serial)); | ||||
|         perseus_descr* dev = perseus_open(selectedPerseusId); | ||||
|         if (!dev) { | ||||
|             flog::error("Failed to open device {}: {}", selectedPerseusId, perseus_errorstr()); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Load firmware | ||||
|         int err = perseus_firmware_download(dev, NULL); | ||||
|         if (err) { | ||||
|             flog::error("Could not upload firmware to device: {}", perseus_errorstr()); | ||||
|             perseus_close(dev); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get info | ||||
|         eeprom_prodid prodId; | ||||
|         err = perseus_get_product_id(dev, &prodId); | ||||
|         if (err) { | ||||
|             flog::error("Could not getproduct info from device: {}", perseus_errorstr()); | ||||
|             perseus_close(dev); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // List samplerates | ||||
|         srList.clear(); | ||||
|         int samplerates[MAX_SAMPLERATE_COUNT]; | ||||
|         memset(samplerates, 0, sizeof(int)*MAX_SAMPLERATE_COUNT); | ||||
|         err = perseus_get_sampling_rates(dev, samplerates, MAX_SAMPLERATE_COUNT); | ||||
|         if (err) { | ||||
|             flog::error("Could not get samplerate list: {}", perseus_errorstr()); | ||||
|             perseus_close(dev); | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|         for (int i = 0; i < MAX_SAMPLERATE_COUNT; i++) { | ||||
|             if (!samplerates[i]) { break; } | ||||
|             srList.define(samplerates[i], getBandwdithScaled(samplerates[i]), samplerates[i]); | ||||
|         } | ||||
|  | ||||
|         // TODO: List attenuator values | ||||
|  | ||||
|         // Load options | ||||
|         srId = 0; | ||||
|         dithering = false; | ||||
|         preamp = false; | ||||
|         preselector = true; | ||||
|         atten = 0; | ||||
|         config.acquire(); | ||||
|         if (config.conf["devices"][selectedSerial].contains("samplerate")) { | ||||
|             int sr = config.conf["devices"][selectedSerial]["samplerate"]; | ||||
|             if (srList.keyExists(sr)) { | ||||
|                 srId = srList.keyId(sr); | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("dithering")) { | ||||
|             dithering = config.conf["devices"][selectedSerial]["dithering"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("preamp")) { | ||||
|             preamp = config.conf["devices"][selectedSerial]["preamp"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("preselector")) { | ||||
|             preselector = config.conf["devices"][selectedSerial]["preselector"]; | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("attenuation")) { | ||||
|             atten = config.conf["devices"][selectedSerial]["attenuation"]; | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         // Update samplerate | ||||
|         sampleRate = srList[srId]; | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         // Close device | ||||
|         perseus_close(dev); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string getBandwdithScaled(double bw) { | ||||
|         char buf[1024]; | ||||
|         if (bw >= 1000000.0) { | ||||
|             sprintf(buf, "%.1lfMHz", bw / 1000000.0); | ||||
|         } | ||||
|         else if (bw >= 1000.0) { | ||||
|             sprintf(buf, "%.1lfKHz", bw / 1000.0); | ||||
|         } | ||||
|         else { | ||||
|             sprintf(buf, "%.1lfHz", bw); | ||||
|         } | ||||
|         return std::string(buf); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("PerseusSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         flog::info("PerseusSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|         if (_this->selectedSerial.empty()) { | ||||
|             flog::error("No device is selected"); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         // Open device | ||||
|         _this->openDev = perseus_open(_this->selectedPerseusId); | ||||
|         if (!_this->openDev) { | ||||
|             flog::error("Failed to open device {}: {}", _this->selectedPerseusId, perseus_errorstr()); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Load firmware | ||||
|         int err = perseus_firmware_download(_this->openDev, NULL); | ||||
|         if (err) { | ||||
|             flog::error("Could not upload firmware to device: {}", perseus_errorstr()); | ||||
|             perseus_close(_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Set samplerate | ||||
|         err = perseus_set_sampling_rate(_this->openDev, _this->sampleRate); | ||||
|         if (err) { | ||||
|             flog::error("Could not set samplerate: {}", perseus_errorstr()); | ||||
|             perseus_close(_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Set options | ||||
|         perseus_set_adc(_this->openDev, _this->dithering, _this->preamp); | ||||
|         perseus_set_attenuator_in_db(_this->openDev, _this->atten); | ||||
|         perseus_set_ddc_center_freq(_this->openDev, _this->freq, _this->preselector); | ||||
|  | ||||
|         // Start stream | ||||
|         int idealBufferSize = _this->sampleRate / 200; | ||||
|         int multipleOf1024 = std::clamp<int>(idealBufferSize / 1024, 1, 2); | ||||
|         int bufferSize = multipleOf1024 * 1024; | ||||
|         int bufferBytes = bufferSize*6; | ||||
|         err = perseus_start_async_input(_this->openDev, bufferBytes, callback, _this); | ||||
|         if (err) { | ||||
|             flog::error("Could not start stream: {}", perseus_errorstr()); | ||||
|             perseus_close(_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         _this->running = true; | ||||
|         flog::info("PerseusSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|  | ||||
|         // Stop stream | ||||
|         _this->stream.stopWriter(); | ||||
|         perseus_stop_async_input(_this->openDev); | ||||
|         _this->stream.clearWriteStop(); | ||||
|  | ||||
|         // Close device | ||||
|         perseus_close(_this->openDev); | ||||
|  | ||||
|         flog::info("PerseusSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             perseus_set_ddc_center_freq(_this->openDev, freq, _this->preselector); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("PerseusSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|  | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devList.txt)) { | ||||
|             std::string serial = _this->devList.key(_this->devId); | ||||
|             _this->select(serial); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = serial; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->srList.txt)) { | ||||
|             _this->sampleRate = _this->srList[_this->srId]; | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["samplerate"] = _this->sampleRate; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::LeftLabel("Attenuation"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderFloatWithSteps(CONCAT("##_airspyhf_atten_", _this->name), &_this->atten, 0, 30, 10, SmGui::FMT_STR_FLOAT_DB_NO_DECIMAL)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_attenuator_in_db(_this->openDev, _this->atten); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["attenuation"] = _this->atten; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("Preamp##_airspyhf_preamp_", _this->name), &_this->preamp)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_adc(_this->openDev, _this->dithering, _this->preamp); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["preamp"] = _this->preamp; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("Dithering##_airspyhf_dither_", _this->name), &_this->dithering)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_adc(_this->openDev, _this->dithering, _this->preamp); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["dithering"] = _this->dithering; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("Preselector##_airspyhf_presel_", _this->name), &_this->preselector)) { | ||||
|             if (_this->running) { | ||||
|                 perseus_set_ddc_center_freq(_this->openDev, _this->freq, _this->preselector); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["preselector"] = _this->preselector; | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static int callback(void* buf, int bufferSize, void* ctx) { | ||||
|         PerseusSourceModule* _this = (PerseusSourceModule*)ctx; | ||||
|         uint8_t* samples = (uint8_t*)buf; | ||||
|         int sampleCount = bufferSize / 6; | ||||
|         for (int i = 0; i < sampleCount; i++) { | ||||
|             int32_t re, im; | ||||
|             re = *(samples++); | ||||
|             re |= *(samples++) << 8; | ||||
|             re |= *(samples++) << 16; | ||||
|             re |= (re >> 23) * (0xFF << 24); // Sign extend | ||||
|             im = *(samples++); | ||||
|             im |= *(samples++) << 8; | ||||
|             im |= *(samples++) << 16; | ||||
|             im |= (im >> 23) * (0xFF << 24); // Sign extend | ||||
|             _this->stream.writeBuf[i].re = ((float)re / (float)0x7FFFFF); | ||||
|             _this->stream.writeBuf[i].im = ((float)im / (float)0x7FFFFF); | ||||
|         } | ||||
|         _this->stream.swap(sampleCount); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     int sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     bool libInit = false; | ||||
|     perseus_descr* openDev; | ||||
|     std::string selectedSerial = ""; | ||||
|     int selectedPerseusId; | ||||
|     float atten = 0; | ||||
|     bool preamp = false; | ||||
|     bool dithering = false; | ||||
|     bool preselector = true; | ||||
|  | ||||
|     OptionList<std::string, int> devList; | ||||
|     OptionList<int, int> srList; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     json def = json({}); | ||||
|     def["devices"] = json({}); | ||||
|     def["device"] = ""; | ||||
|     config.setPath(core::args["root"].s() + "/perseus_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new PerseusSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { | ||||
|     delete (PerseusSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| @@ -108,12 +108,12 @@ namespace rfspace { | ||||
|             } | ||||
|             break; | ||||
|         case RFSPACE_DEV_ID_NET_SDR: | ||||
|         case RFSPACE_DEV_ID_SDR_IP: | ||||
|         default: | ||||
|             for (int n = 80000000 / (4 * 25); n >= 32000; n /= 2) { | ||||
|                 sr.push_back(n); | ||||
|             } | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         return sr; | ||||
|   | ||||
| @@ -171,7 +171,7 @@ public: | ||||
| #ifndef __ANDROID__ | ||||
|         int oret = rtlsdr_open(&openDev, id); | ||||
| #else | ||||
|         int oret = rtlsdr_open_fd(&openDev, devFd); | ||||
|         int oret = rtlsdr_open_sys_dev(&openDev, devFd); | ||||
| #endif | ||||
|          | ||||
|         if (oret < 0) { | ||||
| @@ -285,7 +285,7 @@ private: | ||||
| #ifndef __ANDROID__ | ||||
|         int oret = rtlsdr_open(&_this->openDev, _this->devId); | ||||
| #else | ||||
|         int oret = rtlsdr_open_fd(&_this->openDev, _this->devFd); | ||||
|         int oret = rtlsdr_open_sys_dev(&_this->openDev, _this->devFd); | ||||
| #endif | ||||
|  | ||||
|         if (oret < 0) { | ||||
| @@ -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) / 128.0f; | ||||
|             _this->stream.writeBuf[i].im = (float)(buf[(i * 2) + 1] - 127) / 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; } | ||||
|     } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ public: | ||||
|         this->name = name; | ||||
|  | ||||
|         strcpy(hostname, "localhost"); | ||||
|         sampleRate = 41000000.0; | ||||
|         sampleRate = 5750000.0; | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
| @@ -103,8 +103,14 @@ private: | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         SpectranHTTPSourceModule* _this = (SpectranHTTPSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             // TODO | ||||
|         bool connected = (_this->client && _this->client->isOpen()); | ||||
|         if (connected) { | ||||
|             int64_t newfreq = round(freq); | ||||
|             if (newfreq != _this->lastReportedFreq && _this->gotReport) { | ||||
|                 flog::debug("Sending tuning command"); | ||||
|                 _this->lastReportedFreq = newfreq; | ||||
|                 _this->client->setCenterFrequency(newfreq); | ||||
|             } | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("SpectranHTTPSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
| @@ -138,6 +144,8 @@ private: | ||||
|             _this->tryConnect(); | ||||
|         } | ||||
|         else if (connected && SmGui::Button("Disconnect##spectran_http_source")) { | ||||
|             _this->client->onCenterFrequencyChanged.unbind(_this->onFreqChangedId); | ||||
|             _this->client->onCenterFrequencyChanged.unbind(_this->onSamplerateChangedId); | ||||
|             _this->client->close(); | ||||
|         } | ||||
|         if (_this->running) { style::endDisabled(); } | ||||
| @@ -154,13 +162,28 @@ private: | ||||
|  | ||||
|     void tryConnect() { | ||||
|         try { | ||||
|             gotReport = false; | ||||
|             client = std::make_shared<SpectranHTTPClient>(hostname, port, &stream); | ||||
|             onFreqChangedId = client->onCenterFrequencyChanged.bind(&SpectranHTTPSourceModule::onFreqChanged, this); | ||||
|             onSamplerateChangedId = client->onSamplerateChanged.bind(&SpectranHTTPSourceModule::onSamplerateChanged, this); | ||||
|             client->startWorker(); | ||||
|         } | ||||
|         catch (std::runtime_error e) { | ||||
|             flog::error("Could not connect: {0}", e.what()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void onFreqChanged(double newFreq) { | ||||
|         if (lastReportedFreq == newFreq) { return; } | ||||
|         lastReportedFreq = newFreq; | ||||
|         tuner::tune(tuner::TUNER_MODE_IQ_ONLY, "", newFreq); | ||||
|         gotReport = true; | ||||
|     } | ||||
|  | ||||
|     void onSamplerateChanged(double newSr) { | ||||
|         core::setInputSampleRate(newSr); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     double sampleRate; | ||||
| @@ -168,11 +191,16 @@ private: | ||||
|     bool running = false; | ||||
|  | ||||
|     std::shared_ptr<SpectranHTTPClient> client; | ||||
|     HandlerID onFreqChangedId; | ||||
|     HandlerID onSamplerateChangedId; | ||||
|  | ||||
|     double freq; | ||||
|  | ||||
|     int64_t lastReportedFreq = 0; | ||||
|     bool gotReport; | ||||
|  | ||||
|     char hostname[1024]; | ||||
|     int port = 80; | ||||
|     int port = 54664; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|  | ||||
| }; | ||||
|   | ||||
| @@ -5,6 +5,8 @@ SpectranHTTPClient::SpectranHTTPClient(std::string host, int port, dsp::stream<d | ||||
|     this->stream = stream; | ||||
|  | ||||
|     // Connect to server | ||||
|     this->host = host; | ||||
|     this->port = port; | ||||
|     sock = net::connect(host, port); | ||||
|     http = net::http::Client(sock); | ||||
|  | ||||
| @@ -14,6 +16,13 @@ SpectranHTTPClient::SpectranHTTPClient(std::string host, int port, dsp::stream<d | ||||
|     net::http::ResponseHeader rshdr; | ||||
|     http.recvResponseHeader(rshdr, 5000); | ||||
|  | ||||
|     if (rshdr.getStatusCode() != net::http::STATUS_CODE_OK) { | ||||
|         flog::error("HTTP request did not return ok: {}", rshdr.getStatusString()); | ||||
|         throw std::runtime_error("HTTP request did not return ok"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void SpectranHTTPClient::startWorker() { | ||||
|     // Start chunk worker | ||||
|     workerThread = std::thread(&SpectranHTTPClient::worker, this); | ||||
| } | ||||
| @@ -33,6 +42,27 @@ void SpectranHTTPClient::close() { | ||||
|     stream->clearWriteStop(); | ||||
| } | ||||
|  | ||||
| void SpectranHTTPClient::setCenterFrequency(uint64_t freq) { | ||||
|     // Connect to control endpoint (TODO: Switch to an always connected endpoint) | ||||
|     auto controlSock = net::connect(host, port); | ||||
|     auto controlHttp = net::http::Client(controlSock); | ||||
|  | ||||
|     // Make request | ||||
|     net::http::RequestHeader rqhdr(net::http::METHOD_PUT, "/control", host); | ||||
|     char buf[1024]; | ||||
|     sprintf(buf, "{\"frequencyCenter\":%d,\"frequencySpan\":%d,\"type\":\"capture\"}", freq, _samplerate); | ||||
|     std::string data = buf; | ||||
|     char lenBuf[16]; | ||||
|     sprintf(lenBuf, "%d", data.size()); | ||||
|     rqhdr.setField("Content-Length", lenBuf); | ||||
|     controlHttp.sendRequestHeader(rqhdr); | ||||
|     controlSock->sendstr(data); | ||||
|     net::http::ResponseHeader rshdr; | ||||
|     controlHttp.recvResponseHeader(rshdr, 5000); | ||||
|  | ||||
|     flog::debug("Response: {}", rshdr.getStatusString()); | ||||
| } | ||||
|  | ||||
| void SpectranHTTPClient::worker() { | ||||
|     while (sock->isOpen()) { | ||||
|         // Get chunk header | ||||
| @@ -52,6 +82,41 @@ void SpectranHTTPClient::worker() { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Decode JSON (yes, this is hacky, but it must be extremely fast) | ||||
|         auto startFreqBegin = jsonData.find("\"startFrequency\":"); | ||||
|         auto startFreqEnd = jsonData.find(',', startFreqBegin); | ||||
|         std::string startFreqStr = jsonData.substr(startFreqBegin + 17, startFreqEnd - startFreqBegin - 17); | ||||
|         int64_t startFreq = std::stoll(startFreqStr); | ||||
|  | ||||
|         auto endFreqBegin = jsonData.find("\"endFrequency\":"); | ||||
|         auto endFreqEnd = jsonData.find(',', endFreqBegin); | ||||
|         std::string endFreqStr = jsonData.substr(endFreqBegin + 15, endFreqEnd - endFreqBegin - 15); | ||||
|         int64_t endFreq = std::stoll(endFreqStr); | ||||
|  | ||||
|         auto sampleFreqBegin = jsonData.find("\"sampleFrequency\":"); | ||||
|         bool sampleFreqReceived = (sampleFreqBegin != -1); | ||||
|         int64_t sampleFreq; | ||||
|         if (sampleFreqReceived) { | ||||
|             auto sampleFreqEnd = jsonData.find(',', sampleFreqBegin); | ||||
|             std::string sampleFreqStr = jsonData.substr(sampleFreqBegin + 18, sampleFreqEnd - sampleFreqBegin - 18); | ||||
|             sampleFreq = std::stoll(sampleFreqStr); | ||||
|             //flog::debug("{}", jsonData); | ||||
|         } | ||||
|          | ||||
|         // Calculate and update center freq | ||||
|         int64_t samplerate = /* sampleFreqReceived ? sampleFreq :  */(endFreq - startFreq); | ||||
|         int64_t centerFreq = round(((double)endFreq + (double)startFreq) / 2.0); | ||||
|         if (centerFreq != _centerFreq) { | ||||
|             flog::debug("New center freq: {}", centerFreq); | ||||
|             _centerFreq = centerFreq; | ||||
|             onCenterFrequencyChanged(centerFreq); | ||||
|         } | ||||
|         if (samplerate != _samplerate) { | ||||
|             flog::debug("New samplerate: {}", samplerate); | ||||
|             _samplerate = samplerate; | ||||
|             onSamplerateChanged(samplerate); | ||||
|         } | ||||
|  | ||||
|         // Read (and check for) record separator | ||||
|         uint8_t rs; | ||||
|         int rslen = sock->recv(&rs, 1, true, 5000); | ||||
| @@ -72,10 +137,11 @@ void SpectranHTTPClient::worker() { | ||||
|             i += read; | ||||
|             sampLen += read; | ||||
|         } | ||||
|         int sampCount = sampLen / 8; | ||||
|  | ||||
|         // Swap to stream | ||||
|         if (streamingEnabled) { | ||||
|             if (!stream->swap(sampLen / 8)) { return; } | ||||
|             if (!stream->swap(sampCount)) { return; } | ||||
|         } | ||||
|          | ||||
|         // Read trailing CRLF | ||||
|   | ||||
| @@ -4,22 +4,35 @@ | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <utils/proto/http.h> | ||||
| #include <utils/new_event.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
| class SpectranHTTPClient { | ||||
| public: | ||||
|     SpectranHTTPClient(std::string host, int port, dsp::stream<dsp::complex_t>* stream); | ||||
|  | ||||
|     void startWorker(); | ||||
|     void streaming(bool enabled); | ||||
|     bool isOpen(); | ||||
|     void close(); | ||||
|  | ||||
|     void setCenterFrequency(uint64_t freq); | ||||
|  | ||||
|     NewEvent<uint64_t> onCenterFrequencyChanged; | ||||
|     NewEvent<uint64_t> onSamplerateChanged; | ||||
|  | ||||
| private: | ||||
|     void worker(); | ||||
|  | ||||
|     std::string host; | ||||
|     int port; | ||||
|  | ||||
|     std::shared_ptr<net::Socket> sock; | ||||
|     net::http::Client http; | ||||
|     dsp::stream<dsp::complex_t>* stream; | ||||
|     std::thread workerThread; | ||||
|  | ||||
|     bool streamingEnabled = false; | ||||
|     int64_t _centerFreq = 0; | ||||
|     uint64_t _samplerate = 0; | ||||
| }; | ||||
| @@ -503,7 +503,7 @@ private: | ||||
|     float refStep = 0.5; | ||||
|  | ||||
|     struct SRCombo { | ||||
|         bool operator==(const SRCombo& b) { | ||||
|         bool operator==(const SRCombo& b) const { | ||||
|             return baseId == b.baseId && decimId == b.decimId; | ||||
|         } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user