mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			27 Commits
		
	
	
		
			new_sdrpla
			...
			new_sinks
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 40f1ee5651 | ||
|  | c4b0cb37a6 | ||
|  | cd7dabda51 | ||
|  | 139f63ad25 | ||
|  | 75800e0ca2 | ||
|  | 84da183559 | ||
|  | 0144d8e8ce | ||
|  | 7ef88f23a8 | ||
|  | c934e41a61 | ||
|  | 9dd6c8546d | ||
|  | 191f652fc3 | ||
|  | e208511bde | ||
|  | 348bf75281 | ||
|  | cbf1d6703e | ||
|  | 03c30f202e | ||
|  | 25bc9f60ed | ||
|  | 2e80882ab5 | ||
|  | 4c584847de | ||
|  | 2a741932e0 | ||
|  | ff655caf31 | ||
|  | 0277232bdb | ||
|  | 2e3e2a7dca | ||
|  | 271a77e4ce | ||
|  | 48e9708d74 | ||
|  | 961cd3f133 | ||
|  | 2676190d3a | ||
|  | 8851735cb8 | 
							
								
								
									
										70
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								.github/workflows/build_all.yml
									
									
									
									
										vendored
									
									
								
							| @@ -36,13 +36,6 @@ jobs: | ||||
|           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/" ; 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 librtlsdr | ||||
|           run: Invoke-WebRequest -Uri "https://ftp.osmocom.org/binaries/windows/rtl-sdr/rtl-sdr-64bit-20240623.zip" -OutFile ${{runner.workspace}}/rtl-sdr.zip | ||||
|  | ||||
|         - name: Patch Pothos with newer librtlsdr version | ||||
|           working-directory: ${{runner.workspace}} | ||||
|           run: 7z x rtl-sdr.zip ; rm "C:/Program Files/PothosSDR/bin/rtlsdr.dll" ; cp "rtl-sdr-64bit-20240623/librtlsdr.dll" "C:/Program Files/PothosSDR/bin/rtlsdr.dll" | ||||
|  | ||||
|         - name: Download SDRPlay API | ||||
|           run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip | ||||
|  | ||||
| @@ -65,23 +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 libusb:x64-windows spdlog: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 -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release  ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr" | ||||
|  | ||||
|         - name: Install librfnm | ||||
|           run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install . | ||||
|  | ||||
|         - name: Install libfobos | ||||
|           run: git clone https://github.com/AlexandreRouma/libfobos ; cd libfobos ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install . | ||||
|           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 -DCOPY_MSVC_REDISTRIBUTABLES=ON "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
|           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 | ||||
| @@ -107,13 +94,13 @@ jobs: | ||||
|           run: cmake -E make_directory ${{runner.workspace}}/build | ||||
|  | ||||
|         - name: Install dependencies | ||||
|           run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && 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_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Install SDRplay API | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target / | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target / | ||||
|  | ||||
|         - name: Install libiio | ||||
|           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_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
| @@ -125,20 +112,14 @@ jobs: | ||||
|           run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -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 && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install librfnm | ||||
|           run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install libfobos | ||||
|           run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|           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_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
| @@ -164,13 +145,13 @@ jobs: | ||||
|           run: cmake -E make_directory ${{runner.workspace}}/build | ||||
|  | ||||
|         - name: Install dependencies | ||||
|           run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages | ||||
|           run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako --break-system-packages | ||||
|  | ||||
|         - name: Install volk | ||||
|           run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Install SDRplay API | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target / | ||||
|           run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target / | ||||
|  | ||||
|         - name: Install libiio | ||||
|           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_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../ | ||||
| @@ -184,18 +165,12 @@ jobs: | ||||
|         # - 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 librfnm | ||||
|           run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install libfobos | ||||
|           run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd .. | ||||
|  | ||||
|         - name: Install more recent librtlsdr | ||||
|           run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../ | ||||
|  | ||||
|         - name: Prepare CMake | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|           run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release | ||||
|  | ||||
|         - name: Build | ||||
|           working-directory: ${{runner.workspace}}/build | ||||
| @@ -365,28 +340,6 @@ jobs: | ||||
|               name: sdrpp_ubuntu_mantic_amd64 | ||||
|               path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     build_ubuntu_noble: | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
|         steps: | ||||
|         - uses: actions/checkout@v4 | ||||
|          | ||||
|         - name: Create Docker Image | ||||
|           run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && 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@v4 | ||||
|           with: | ||||
|               name: sdrpp_ubuntu_noble_amd64 | ||||
|               path: ${{runner.workspace}}/sdrpp_debian_amd64.deb | ||||
|  | ||||
|     build_raspios_bullseye_armhf: | ||||
|         runs-on: ARM | ||||
|  | ||||
| @@ -442,7 +395,7 @@ jobs: | ||||
|               path: ${{runner.workspace}}/sdrpp.apk | ||||
|  | ||||
|     create_full_archive: | ||||
|         needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android'] | ||||
|         needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_raspios_bullseye_armhf', 'build_android'] | ||||
|         runs-on: ubuntu-latest | ||||
|  | ||||
|         steps: | ||||
| @@ -462,7 +415,6 @@ jobs: | ||||
|             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 && | ||||
|             mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb && | ||||
|             mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb && | ||||
|             mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb && | ||||
|             mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk | ||||
|  | ||||
|   | ||||
| @@ -12,19 +12,14 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy | ||||
| option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON) | ||||
| option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON) | ||||
| option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON) | ||||
| option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF) | ||||
| option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF) | ||||
| option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) | ||||
| option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF) | ||||
| option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON) | ||||
| option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF) | ||||
| option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF) | ||||
| option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF) | ||||
| option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF) | ||||
| option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON) | ||||
| option(OPT_BUILD_RFNM_SOURCE "Build RFNM Source Module (Dependencies: librfnm)" OFF) | ||||
| 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) | ||||
| @@ -45,14 +40,12 @@ option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: port | ||||
|  | ||||
| # Decoders | ||||
| option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF) | ||||
| option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF) | ||||
| option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF) | ||||
| option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF) | ||||
| option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF) | ||||
| option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON) | ||||
| option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON) | ||||
| option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF) | ||||
| option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF) | ||||
|  | ||||
| # Misc | ||||
| @@ -68,7 +61,6 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF) | ||||
| # Other options | ||||
| option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON) | ||||
| option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF) | ||||
| option(COPY_MSVC_REDISTRIBUTABLES "Copy over the Visual C++ Redistributable" OFF) | ||||
|  | ||||
| # Module cmake path | ||||
| set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake") | ||||
| @@ -133,10 +125,6 @@ if (OPT_BUILD_AUDIO_SOURCE) | ||||
| add_subdirectory("source_modules/audio_source") | ||||
| endif (OPT_BUILD_AUDIO_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_BADGESDR_SOURCE) | ||||
| add_subdirectory("source_modules/badgesdr_source") | ||||
| endif (OPT_BUILD_BADGESDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_BLADERF_SOURCE) | ||||
| add_subdirectory("source_modules/bladerf_source") | ||||
| endif (OPT_BUILD_BLADERF_SOURCE) | ||||
| @@ -145,26 +133,14 @@ if (OPT_BUILD_FILE_SOURCE) | ||||
| add_subdirectory("source_modules/file_source") | ||||
| endif (OPT_BUILD_FILE_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_FOBOSSDR_SOURCE) | ||||
| add_subdirectory("source_modules/fobossdr_source") | ||||
| endif (OPT_BUILD_FOBOSSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_HACKRF_SOURCE) | ||||
| add_subdirectory("source_modules/hackrf_source") | ||||
| endif (OPT_BUILD_HACKRF_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_HAROGIC_SOURCE) | ||||
| add_subdirectory("source_modules/harogic_source") | ||||
| endif (OPT_BUILD_HAROGIC_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_HERMES_SOURCE) | ||||
| add_subdirectory("source_modules/hermes_source") | ||||
| endif (OPT_BUILD_HERMES_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_KCSDR_SOURCE) | ||||
| add_subdirectory("source_modules/kcsdr_source") | ||||
| endif (OPT_BUILD_KCSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_LIMESDR_SOURCE) | ||||
| add_subdirectory("source_modules/limesdr_source") | ||||
| endif (OPT_BUILD_LIMESDR_SOURCE) | ||||
| @@ -181,10 +157,6 @@ if (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
| add_subdirectory("source_modules/plutosdr_source") | ||||
| endif (OPT_BUILD_PLUTOSDR_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_RFNM_SOURCE) | ||||
| add_subdirectory("source_modules/rfnm_source") | ||||
| endif (OPT_BUILD_RFNM_SOURCE) | ||||
|  | ||||
| if (OPT_BUILD_RFSPACE_SOURCE) | ||||
| add_subdirectory("source_modules/rfspace_source") | ||||
| endif (OPT_BUILD_RFSPACE_SOURCE) | ||||
| @@ -253,10 +225,6 @@ if (OPT_BUILD_ATV_DECODER) | ||||
| add_subdirectory("decoder_modules/atv_decoder") | ||||
| endif (OPT_BUILD_ATV_DECODER) | ||||
|  | ||||
| if (OPT_BUILD_DAB_DECODER) | ||||
| add_subdirectory("decoder_modules/dab_decoder") | ||||
| endif (OPT_BUILD_DAB_DECODER) | ||||
|  | ||||
| if (OPT_BUILD_FALCON9_DECODER) | ||||
| add_subdirectory("decoder_modules/falcon9_decoder") | ||||
| endif (OPT_BUILD_FALCON9_DECODER) | ||||
| @@ -281,10 +249,6 @@ if (OPT_BUILD_RADIO) | ||||
| add_subdirectory("decoder_modules/radio") | ||||
| endif (OPT_BUILD_RADIO) | ||||
|  | ||||
| if (OPT_BUILD_RYFI_DECODER) | ||||
| add_subdirectory("decoder_modules/ryfi_decoder") | ||||
| endif (OPT_BUILD_RYFI_DECODER) | ||||
|  | ||||
| if (OPT_BUILD_WEATHER_SAT_DECODER) | ||||
| add_subdirectory("decoder_modules/weather_sat_decoder") | ||||
| endif (OPT_BUILD_WEATHER_SAT_DECODER) | ||||
| @@ -338,21 +302,6 @@ target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS}) | ||||
| if (MSVC) | ||||
|     add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) | ||||
|     add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) | ||||
|  | ||||
|     if (COPY_MSVC_REDISTRIBUTABLES) | ||||
|         # Get the list of Visual C++ runtime DLLs | ||||
|         set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP True) | ||||
|         include(InstallRequiredSystemLibraries) | ||||
|  | ||||
|         # Create a space sperated list | ||||
|         set(REDIST_DLLS_STR "") | ||||
|         foreach(DLL IN LISTS CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS) | ||||
|             set(REDIST_DLLS_STR COMMAND xcopy /F \"${DLL}\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y ${REDIST_DLLS_STR}) | ||||
|         endforeach() | ||||
|          | ||||
|         # Create target | ||||
|         add_custom_target(do_always_msvc ALL ${REDIST_DLLS_STR}) | ||||
|     endif () | ||||
| endif () | ||||
|  | ||||
|  | ||||
| @@ -393,6 +342,4 @@ endif () | ||||
|  | ||||
| # Create uninstall target | ||||
| configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY) | ||||
| add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) | ||||
|  | ||||
| # Create headers target | ||||
| add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) | ||||
| @@ -10,7 +10,7 @@ android { | ||||
|         minSdkVersion 28 | ||||
|         targetSdkVersion 28 | ||||
|         versionCode 1 | ||||
|         versionName "1.2.0" | ||||
|         versionName "1.1.0" | ||||
|  | ||||
|         externalNativeBuild { | ||||
|             cmake { | ||||
|   | ||||
| @@ -147,11 +147,11 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["menuElements"][3]["name"] = "Sinks"; | ||||
|     defConfig["menuElements"][3]["open"] = true; | ||||
|  | ||||
|     defConfig["menuElements"][4]["name"] = "Frequency Manager"; | ||||
|     defConfig["menuElements"][4]["open"] = true; | ||||
|     defConfig["menuElements"][3]["name"] = "Frequency Manager"; | ||||
|     defConfig["menuElements"][3]["open"] = true; | ||||
|  | ||||
|     defConfig["menuElements"][5]["name"] = "VFO Color"; | ||||
|     defConfig["menuElements"][5]["open"] = true; | ||||
|     defConfig["menuElements"][4]["name"] = "VFO Color"; | ||||
|     defConfig["menuElements"][4]["open"] = true; | ||||
|  | ||||
|     defConfig["menuElements"][6]["name"] = "Band Plan"; | ||||
|     defConfig["menuElements"][6]["open"] = true; | ||||
| @@ -173,24 +173,16 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["File Source"]["module"] = "file_source"; | ||||
|     defConfig["moduleInstances"]["File Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source"; | ||||
|     defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source"; | ||||
|     defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source"; | ||||
|     defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source"; | ||||
|     defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source"; | ||||
|     defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Network Source"]["module"] = "network_source"; | ||||
|     defConfig["moduleInstances"]["Network Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source"; | ||||
|     defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source"; | ||||
|     defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source"; | ||||
|     defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["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"; | ||||
| @@ -201,12 +193,8 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|     defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source"; | ||||
|     defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source"; | ||||
|     defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source"; | ||||
|     defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true; | ||||
|     defConfig["moduleInstances"]["USRP Source"]["module"] = "usrp_source"; | ||||
|     defConfig["moduleInstances"]["USRP Source"]["enabled"] = true; | ||||
|  | ||||
|     defConfig["moduleInstances"]["Audio Sink"] = "audio_sink"; | ||||
|     defConfig["moduleInstances"]["Network Sink"] = "network_sink"; | ||||
| @@ -232,19 +220,12 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|  | ||||
|     defConfig["modules"] = json::array(); | ||||
|  | ||||
|     defConfig["offsets"]["SpyVerter"] = 120000000.0; | ||||
|     defConfig["offsets"]["Ham-It-Up"] = 125000000.0; | ||||
|     defConfig["offsets"]["MMDS S-band (1998MHz)"] = -1998000000.0; | ||||
|     defConfig["offsets"]["DK5AV X-Band"] = -6800000000.0; | ||||
|     defConfig["offsets"]["Ku LNB (9750MHz)"] = -9750000000.0; | ||||
|     defConfig["offsets"]["Ku LNB (10700MHz)"] = -10700000000.0; | ||||
|  | ||||
|     defConfig["selectedOffset"] = "None"; | ||||
|     defConfig["manualOffset"] = 0.0; | ||||
|     defConfig["offsetMode"] = (int)0; // Off | ||||
|     defConfig["offset"] = 0.0; | ||||
|     defConfig["showMenu"] = true; | ||||
|     defConfig["showWaterfall"] = true; | ||||
|     defConfig["source"] = ""; | ||||
|     defConfig["decimation"] = 1; | ||||
|     defConfig["decimationPower"] = 0; | ||||
|     defConfig["iqCorrection"] = false; | ||||
|     defConfig["invertIQ"] = false; | ||||
|  | ||||
| @@ -325,18 +306,12 @@ int sdrpp_main(int argc, char* argv[]) { | ||||
|  | ||||
|     // Remove unused elements | ||||
|     auto items = core::configManager.conf.items(); | ||||
|     auto newConf = core::configManager.conf; | ||||
|     bool configCorrected = false; | ||||
|     for (auto const& item : items) { | ||||
|         if (!defConfig.contains(item.key())) { | ||||
|             flog::info("Unused key in config {0}, repairing", item.key()); | ||||
|             newConf.erase(item.key()); | ||||
|             configCorrected = true; | ||||
|             core::configManager.conf.erase(item.key()); | ||||
|         } | ||||
|     } | ||||
|     if (configCorrected) { | ||||
|         core::configManager.conf = newConf; | ||||
|     } | ||||
|  | ||||
|     // Update to new module representation in config if needed | ||||
|     for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) { | ||||
|   | ||||
| @@ -37,20 +37,14 @@ namespace sdrpp_credits { | ||||
|     const char* hardwareDonators[] = { | ||||
|         "Aaronia AG", | ||||
|         "Airspy", | ||||
|         "Alex 4Z5LV", | ||||
|         "Analog Devices", | ||||
|         "CaribouLabs", | ||||
|         "Deepace", | ||||
|         "Ettus Research", | ||||
|         "Harogic", | ||||
|         "Howard Su", | ||||
|         "MicroPhase", | ||||
|         "Microtelecom", | ||||
|         "MyriadRF", | ||||
|         "Nuand", | ||||
|         "RFNM", | ||||
|         "RFspace", | ||||
|         "RigExpert", | ||||
|         "RTL-SDRblog", | ||||
|         "SDRplay" | ||||
|     }; | ||||
|   | ||||
| @@ -9,7 +9,6 @@ | ||||
| namespace dsp { | ||||
|     class generic_block { | ||||
|     public: | ||||
|         virtual ~generic_block() {} | ||||
|         virtual void start() {} | ||||
|         virtual void stop() {} | ||||
|         virtual int run() { return -1; } | ||||
| @@ -17,6 +16,8 @@ namespace dsp { | ||||
|  | ||||
|     class block : public generic_block { | ||||
|     public: | ||||
|         virtual void init() {} | ||||
|  | ||||
|         virtual ~block() { | ||||
|             if (!_block_init) { return; } | ||||
|             stop(); | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| #pragma once | ||||
| #include <volk/volk.h> | ||||
| #include <string.h> | ||||
|  | ||||
| namespace dsp::buffer { | ||||
|     template<class T> | ||||
|   | ||||
| @@ -10,8 +10,6 @@ namespace dsp { | ||||
|  | ||||
|         Operator(stream<A>* a, stream<B>* b) { init(a, b); } | ||||
|  | ||||
|         virtual ~Operator() {} | ||||
|  | ||||
|         virtual void init(stream<A>* a, stream<B>* b) { | ||||
|             _a = a; | ||||
|             _b = b; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ | ||||
| namespace dsp { | ||||
|     class untyped_stream { | ||||
|     public: | ||||
|         virtual ~untyped_stream() {} | ||||
|         virtual bool swap(int size) { return false; } | ||||
|         virtual int read() { return -1; } | ||||
|         virtual void flush() {} | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| #pragma once | ||||
| #include <volk/volk.h> | ||||
| #include "../buffer/buffer.h" | ||||
|  | ||||
| namespace dsp { | ||||
|     template<class T> | ||||
|   | ||||
| @@ -16,6 +16,7 @@ namespace icons { | ||||
|     ImTextureID UNMUTED; | ||||
|     ImTextureID NORMAL_TUNING; | ||||
|     ImTextureID CENTER_TUNING; | ||||
|     ImTextureID ALIGN_CENTER; | ||||
|  | ||||
|     GLuint loadTexture(std::string path) { | ||||
|         int w, h, n; | ||||
| @@ -45,6 +46,7 @@ namespace icons { | ||||
|         UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png"); | ||||
|         NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png"); | ||||
|         CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png"); | ||||
|         ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png"); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|   | ||||
| @@ -13,7 +13,8 @@ namespace icons { | ||||
|     extern ImTextureID UNMUTED; | ||||
|     extern ImTextureID NORMAL_TUNING; | ||||
|     extern ImTextureID CENTER_TUNING; | ||||
|  | ||||
|     extern ImTextureID ALIGN_CENTER; | ||||
|      | ||||
|     GLuint loadTexture(std::string path); | ||||
|     bool load(std::string resDir); | ||||
| } | ||||
| @@ -17,6 +17,7 @@ | ||||
| #include <gui/menus/display.h> | ||||
| #include <gui/menus/bandplan.h> | ||||
| #include <gui/menus/sink.h> | ||||
| #include <gui/menus/streams.h> | ||||
| #include <gui/menus/vfo_color.h> | ||||
| #include <gui/menus/module_manager.h> | ||||
| #include <gui/menus/theme.h> | ||||
| @@ -72,6 +73,7 @@ void MainWindow::init() { | ||||
|  | ||||
|     gui::menu.registerEntry("Source", sourcemenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Streams", streamsmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Display", displaymenu::draw, NULL); | ||||
|     gui::menu.registerEntry("Theme", thememenu::draw, NULL); | ||||
| @@ -165,6 +167,7 @@ void MainWindow::init() { | ||||
|  | ||||
|     sourcemenu::init(); | ||||
|     sinkmenu::init(); | ||||
|     streamsmenu::init(); | ||||
|     bandplanmenu::init(); | ||||
|     displaymenu::init(); | ||||
|     vfo_color_menu::init(); | ||||
|   | ||||
| @@ -19,7 +19,6 @@ namespace displaymenu { | ||||
|     std::string colorMapAuthor = ""; | ||||
|     int selectedWindow = 0; | ||||
|     int fftRate = 20; | ||||
|     int fftSizeId = 0; | ||||
|     int uiScaleId = 0; | ||||
|     bool restartRequired = false; | ||||
|     bool fftHold = false; | ||||
| @@ -29,9 +28,34 @@ namespace displaymenu { | ||||
|     bool snrSmoothing = false; | ||||
|     int snrSmoothingSpeed = 20; | ||||
|  | ||||
|     OptionList<int, int> fftSizes; | ||||
|     OptionList<float, float> uiScales; | ||||
|  | ||||
|     const int FFTSizes[] = { | ||||
|         524288, | ||||
|         262144, | ||||
|         131072, | ||||
|         65536, | ||||
|         32768, | ||||
|         16384, | ||||
|         8192, | ||||
|         4096, | ||||
|         2048, | ||||
|         1024 | ||||
|     }; | ||||
|  | ||||
|     const char* FFTSizesStr = "524288\0" | ||||
|                               "262144\0" | ||||
|                               "131072\0" | ||||
|                               "65536\0" | ||||
|                               "32768\0" | ||||
|                               "16384\0" | ||||
|                               "8192\0" | ||||
|                               "4096\0" | ||||
|                               "2048\0" | ||||
|                               "1024\0"; | ||||
|  | ||||
|     int fftSizeId = 0; | ||||
|  | ||||
|     const IQFrontEnd::FFTWindow fftWindowList[] = { | ||||
|         IQFrontEnd::FFTWindow::RECTANGULAR, | ||||
|         IQFrontEnd::FFTWindow::BLACKMAN, | ||||
| @@ -45,18 +69,6 @@ namespace displaymenu { | ||||
|     } | ||||
|  | ||||
|     void init() { | ||||
|         // Define FFT sizes | ||||
|         fftSizes.define(524288, "524288", 524288); | ||||
|         fftSizes.define(262144, "262144", 262144); | ||||
|         fftSizes.define(131072, "131072", 131072); | ||||
|         fftSizes.define(65536, "65536", 65536); | ||||
|         fftSizes.define(32768, "32768", 32768); | ||||
|         fftSizes.define(16384, "16384", 16384); | ||||
|         fftSizes.define(8192, "8192", 8192); | ||||
|         fftSizes.define(4096, "4096", 4096); | ||||
|         fftSizes.define(2048, "2048", 2048); | ||||
|         fftSizes.define(1024, "1024", 1024); | ||||
|  | ||||
|         showWaterfall = core::configManager.conf["showWaterfall"]; | ||||
|         showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall(); | ||||
|         std::string colormapName = core::configManager.conf["colorMap"]; | ||||
| @@ -78,12 +90,15 @@ namespace displaymenu { | ||||
|         fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"]; | ||||
|         gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate); | ||||
|  | ||||
|         fftSizeId = fftSizes.valueId(65536); | ||||
|         int size = core::configManager.conf["fftSize"]; | ||||
|         if (fftSizes.keyExists(size)) { | ||||
|             fftSizeId = fftSizes.keyId(size); | ||||
|         fftSizeId = 3; | ||||
|         int fftSize = core::configManager.conf["fftSize"]; | ||||
|         for (int i = 0; i < 7; i++) { | ||||
|             if (fftSize == FFTSizes[i]) { | ||||
|                 fftSizeId = i; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId)); | ||||
|         sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]); | ||||
|  | ||||
|         fftRate = core::configManager.conf["fftRate"]; | ||||
|         sigpath::iqFrontEnd.setFFTRate(fftRate); | ||||
| @@ -214,10 +229,10 @@ namespace displaymenu { | ||||
|  | ||||
|         ImGui::LeftLabel("FFT Size"); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) { | ||||
|             sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId)); | ||||
|         if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) { | ||||
|             sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId); | ||||
|             core::configManager.conf["fftSize"] = FFTSizes[fftSizeId]; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -5,120 +5,113 @@ | ||||
| #include <gui/main_window.h> | ||||
| #include <gui/style.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <gui/dialogs/dialog_box.h> | ||||
|  | ||||
| namespace sourcemenu { | ||||
|     int offsetMode = 0; | ||||
|     int sourceId = 0; | ||||
|     EventHandler<std::string> sourcesChangedHandler; | ||||
|     EventHandler<std::string> sourceUnregisterHandler; | ||||
|     OptionList<std::string, std::string> sources; | ||||
|     std::string selectedSource; | ||||
|  | ||||
|     int decimId = 0; | ||||
|     OptionList<int, int> decimations; | ||||
|  | ||||
|     double customOffset = 0.0; | ||||
|     double effectiveOffset = 0.0; | ||||
|     int decimationPower = 0; | ||||
|     bool iqCorrection = false; | ||||
|     bool invertIQ = false; | ||||
|  | ||||
|     int offsetId = 0; | ||||
|     double manualOffset = 0.0; | ||||
|     std::string selectedOffset; | ||||
|     double effectiveOffset = 0.0; | ||||
|     OptionList<std::string, double> offsets; | ||||
|     std::map<std::string, double> namedOffsets; | ||||
|     EventHandler<std::string> sourceRegisteredHandler; | ||||
|     EventHandler<std::string> sourceUnregisterHandler; | ||||
|     EventHandler<std::string> sourceUnregisteredHandler; | ||||
|  | ||||
|     bool showAddOffsetDialog = false; | ||||
|     char newOffsetName[1024]; | ||||
|     double newOffset = 0.0; | ||||
|     std::vector<std::string> sourceNames; | ||||
|     std::string sourceNamesTxt; | ||||
|     std::string selectedSource; | ||||
|  | ||||
|     bool showDelOffsetDialog = false; | ||||
|     std::string delOffsetName = ""; | ||||
|  | ||||
|     // Offset IDs | ||||
|     enum { | ||||
|         OFFSET_ID_NONE, | ||||
|         OFFSET_ID_MANUAL, | ||||
|         OFFSET_ID_CUSTOM_BASE | ||||
|         OFFSET_MODE_NONE, | ||||
|         OFFSET_MODE_CUSTOM, | ||||
|         OFFSET_MODE_SPYVERTER, | ||||
|         OFFSET_MODE_HAM_IT_UP, | ||||
|         OFFSET_MODE_MMDS_SB_1998, | ||||
|         OFFSET_MODE_DK5AV_XB, | ||||
|         OFFSET_MODE_KU_LNB_9750, | ||||
|         OFFSET_MODE_KU_LNB_10700, | ||||
|         _OFFSET_MODE_COUNT | ||||
|     }; | ||||
|  | ||||
|     void updateOffset() { | ||||
|         // Compute the effective offset | ||||
|         switch (offsetId) { | ||||
|         case OFFSET_ID_NONE: | ||||
|             effectiveOffset = 0; | ||||
|             break; | ||||
|         case OFFSET_ID_MANUAL: | ||||
|             effectiveOffset = manualOffset; | ||||
|             break; | ||||
|         default: | ||||
|             effectiveOffset = namedOffsets[offsets.name(offsetId)]; | ||||
|             break; | ||||
|         } | ||||
|     const char* offsetModesTxt = "None\0" | ||||
|                                  "Custom\0" | ||||
|                                  "SpyVerter\0" | ||||
|                                  "Ham-It-Up\0" | ||||
|                                  "MMDS S-band (1998MHz)\0" | ||||
|                                  "DK5AV X-Band\0" | ||||
|                                  "Ku LNB (9750MHz)\0" | ||||
|                                  "Ku LNB (10700MHz)\0"; | ||||
|  | ||||
|         // Apply it | ||||
|     const char* decimationStages = "None\0" | ||||
|                                    "2\0" | ||||
|                                    "4\0" | ||||
|                                    "8\0" | ||||
|                                    "16\0" | ||||
|                                    "32\0" | ||||
|                                    "64\0"; | ||||
|  | ||||
|     void updateOffset() { | ||||
|         if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; } | ||||
|         else if (offsetMode == OFFSET_MODE_SPYVERTER) { | ||||
|             effectiveOffset = 120000000; | ||||
|         } // 120MHz Up-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_HAM_IT_UP) { | ||||
|             effectiveOffset = 125000000; | ||||
|         } // 125MHz Up-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_MMDS_SB_1998) { | ||||
|             effectiveOffset = -1998000000; | ||||
|         } // 1.998GHz Down-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_DK5AV_XB) { | ||||
|             effectiveOffset = -6800000000; | ||||
|         } // 6.8GHz Down-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_KU_LNB_9750) { | ||||
|             effectiveOffset = -9750000000; | ||||
|         } // 9.750GHz Down-conversion | ||||
|         else if (offsetMode == OFFSET_MODE_KU_LNB_10700) { | ||||
|             effectiveOffset = -10700000000; | ||||
|         } // 10.7GHz Down-conversion | ||||
|         else { | ||||
|             effectiveOffset = 0; | ||||
|         } | ||||
|         sigpath::sourceManager.setTuningOffset(effectiveOffset); | ||||
|     } | ||||
|  | ||||
|     void selectOffsetById(int id) { | ||||
|         // Update the offset mode | ||||
|         offsetId = id; | ||||
|         selectedOffset = offsets.name(id); | ||||
|  | ||||
|         // Update the offset | ||||
|         updateOffset(); | ||||
|     } | ||||
|  | ||||
|     void selectOffsetByName(const std::string& name) { | ||||
|         // If the name doesn't exist, select 'None' | ||||
|         if (!offsets.nameExists(name)) { | ||||
|             selectOffsetById(OFFSET_ID_NONE); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Select using the ID associated with the name | ||||
|         selectOffsetById(offsets.nameId(name)); | ||||
|     } | ||||
|  | ||||
|     void refreshSources() { | ||||
|         // Get sources | ||||
|         auto sourceNames = sigpath::sourceManager.getSourceNames(); | ||||
|  | ||||
|         // Define source options | ||||
|         sources.clear(); | ||||
|         sourceNames = sigpath::sourceManager.getSourceNames(); | ||||
|         sourceNamesTxt.clear(); | ||||
|         for (auto name : sourceNames) { | ||||
|             sources.define(name, name, name); | ||||
|             sourceNamesTxt += name; | ||||
|             sourceNamesTxt += '\0'; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void selectSource(std::string name) { | ||||
|         // If there is no source, give up | ||||
|         if (sources.empty()) { | ||||
|             sourceId = 0; | ||||
|         if (sourceNames.empty()) { | ||||
|             selectedSource.clear(); | ||||
|             return; | ||||
|         } | ||||
|         auto it = std::find(sourceNames.begin(), sourceNames.end(), name); | ||||
|         if (it == sourceNames.end()) { | ||||
|             selectSource(sourceNames[0]); | ||||
|             return; | ||||
|         } | ||||
|         sourceId = std::distance(sourceNames.begin(), it); | ||||
|         selectedSource = sourceNames[sourceId]; | ||||
|         sigpath::sourceManager.selectSource(sourceNames[sourceId]); | ||||
|     } | ||||
|  | ||||
|         // If a source with the given name doesn't exist, select the first source instead | ||||
|         if (!sources.valueExists(name)) { | ||||
|             selectSource(sources.value(0)); | ||||
|     void onSourceRegistered(std::string name, void* ctx) { | ||||
|         refreshSources(); | ||||
|  | ||||
|         if (selectedSource.empty()) { | ||||
|             sourceId = 0; | ||||
|             selectSource(sourceNames[0]); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Update the GUI variables | ||||
|         sourceId = sources.valueId(name); | ||||
|         selectedSource = name; | ||||
|  | ||||
|         // Select the source module | ||||
|         sigpath::sourceManager.selectSource(name); | ||||
|     } | ||||
|  | ||||
|     void onSourcesChanged(std::string name, void* ctx) { | ||||
|         // Update the source list | ||||
|         refreshSources(); | ||||
|  | ||||
|         // Reselect the current source | ||||
|         selectSource(selectedSource); | ||||
|         sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); | ||||
|     } | ||||
|  | ||||
|     void onSourceUnregister(std::string name, void* ctx) { | ||||
| @@ -127,173 +120,60 @@ namespace sourcemenu { | ||||
|         // TODO: Stop everything | ||||
|     } | ||||
|  | ||||
|     void reloadOffsets() { | ||||
|         // Clear list | ||||
|         offsets.clear(); | ||||
|         namedOffsets.clear(); | ||||
|     void onSourceUnregistered(std::string name, void* ctx) { | ||||
|         refreshSources(); | ||||
|  | ||||
|         // Define special offset modes | ||||
|         offsets.define("None", OFFSET_ID_NONE); | ||||
|         offsets.define("Manual", OFFSET_ID_MANUAL); | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|  | ||||
|         // Load custom offsets | ||||
|         auto ofs = core::configManager.conf["offsets"].items(); | ||||
|         for (auto& o : ofs) { | ||||
|             namedOffsets[o.key()] = (double)o.value(); | ||||
|         if (sourceNames.empty()) { | ||||
|             selectedSource = ""; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Define custom offsets | ||||
|         for (auto& [name, offset] : namedOffsets) { | ||||
|             offsets.define(name, offsets.size()); | ||||
|         if (name == selectedSource) { | ||||
|             sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1); | ||||
|             selectSource(sourceNames[sourceId]); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Release the config file | ||||
|         core::configManager.release(); | ||||
|         sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); | ||||
|     } | ||||
|  | ||||
|     void init() { | ||||
|         // Load offset modes | ||||
|         reloadOffsets(); | ||||
|  | ||||
|         // Define decimation values | ||||
|         decimations.define(1, "None", 1); | ||||
|         decimations.define(2, "2x", 2); | ||||
|         decimations.define(4, "4x", 4); | ||||
|         decimations.define(8, "8x", 8); | ||||
|         decimations.define(16, "16x", 16); | ||||
|         decimations.define(32, "32x", 32); | ||||
|         decimations.define(64, "64x", 64); | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|  | ||||
|         // Load other settings | ||||
|         std::string selectedSource = core::configManager.conf["source"]; | ||||
|         manualOffset = core::configManager.conf["manualOffset"]; | ||||
|         std::string selectedOffset = core::configManager.conf["selectedOffset"]; | ||||
|         std::string selected = core::configManager.conf["source"]; | ||||
|         customOffset = core::configManager.conf["offset"]; | ||||
|         offsetMode = core::configManager.conf["offsetMode"]; | ||||
|         decimationPower = core::configManager.conf["decimationPower"]; | ||||
|         iqCorrection = core::configManager.conf["iqCorrection"]; | ||||
|         invertIQ = core::configManager.conf["invertIQ"]; | ||||
|         int decimation = core::configManager.conf["decimation"]; | ||||
|         if (decimations.keyExists(decimation)) { | ||||
|             decimId = decimations.keyId(decimation); | ||||
|         } | ||||
|  | ||||
|         // Release the config file | ||||
|         core::configManager.release(); | ||||
|  | ||||
|         // Select the source module | ||||
|         refreshSources(); | ||||
|         selectSource(selectedSource); | ||||
|  | ||||
|         // Update frontend settings | ||||
|         sigpath::iqFrontEnd.setDCBlocking(iqCorrection); | ||||
|         sigpath::iqFrontEnd.setInvertIQ(invertIQ); | ||||
|         sigpath::iqFrontEnd.setDecimation(decimations.value(decimId)); | ||||
|         selectOffsetByName(selectedOffset); | ||||
|         updateOffset(); | ||||
|  | ||||
|         // Register handlers | ||||
|         sourcesChangedHandler.handler = onSourcesChanged; | ||||
|         refreshSources(); | ||||
|         selectSource(selected); | ||||
|         sigpath::iqFrontEnd.setDecimation(1 << decimationPower); | ||||
|  | ||||
|         sourceRegisteredHandler.handler = onSourceRegistered; | ||||
|         sourceUnregisterHandler.handler = onSourceUnregister; | ||||
|         sigpath::sourceManager.onSourceRegistered.bindHandler(&sourcesChangedHandler); | ||||
|         sourceUnregisteredHandler.handler = onSourceUnregistered; | ||||
|         sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler); | ||||
|         sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler); | ||||
|         sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourcesChangedHandler); | ||||
|     } | ||||
|         sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler); | ||||
|  | ||||
|     void addOffset(const std::string& name, double offset) { | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|  | ||||
|         // Define a new offset | ||||
|         core::configManager.conf["offsets"][name] = offset; | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.release(true); | ||||
|  | ||||
|         // Reload the offsets | ||||
|         reloadOffsets(); | ||||
|  | ||||
|         // Attempt to re-select the same one | ||||
|         selectOffsetByName(selectedOffset); | ||||
|     } | ||||
|  | ||||
|     void delOffset(const std::string& name) { | ||||
|         // Acquire the config file | ||||
|         core::configManager.acquire(); | ||||
|  | ||||
|         // Define a new offset | ||||
|         core::configManager.conf["offsets"].erase(name); | ||||
|  | ||||
|         // Acquire the config file | ||||
|         core::configManager.release(true); | ||||
|  | ||||
|         // Reload the offsets | ||||
|         reloadOffsets(); | ||||
|  | ||||
|         // Attempt to re-select the same one | ||||
|         selectOffsetByName(selectedOffset); | ||||
|     } | ||||
|  | ||||
|     bool addOffsetDialog() { | ||||
|         bool open = true; | ||||
|         gui::mainWindow.lockWaterfallControls = true; | ||||
|  | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         const char* id = "Add offset##sdrpp_add_offset_dialog_"; | ||||
|         ImGui::OpenPopup(id); | ||||
|  | ||||
|         if (ImGui::BeginPopup(id, ImGuiWindowFlags_NoResize)) { | ||||
|             ImGui::LeftLabel("Name"); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             ImGui::InputText("##sdrpp_add_offset_name", newOffsetName, 1023); | ||||
|  | ||||
|             ImGui::LeftLabel("Offset"); | ||||
|             ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|             ImGui::InputDouble("##sdrpp_add_offset_offset", &newOffset); | ||||
|  | ||||
|             bool nameExists = offsets.nameExists(newOffsetName); | ||||
|             bool reservedName = !strcmp(newOffsetName, "None") || !strcmp(newOffsetName, "Manual"); | ||||
|             bool denyApply = !newOffsetName[0] || nameExists || reservedName; | ||||
|  | ||||
|             if (nameExists) { | ||||
|                 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "An offset with the given name already exists."); | ||||
|             } | ||||
|             else if (reservedName) { | ||||
|                 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "The given name is reserved."); | ||||
|             } | ||||
|  | ||||
|             if (denyApply) { style::beginDisabled(); } | ||||
|             if (ImGui::Button("Apply")) { | ||||
|                 addOffset(newOffsetName, newOffset); | ||||
|                 open = false; | ||||
|             } | ||||
|             if (denyApply) { style::endDisabled(); } | ||||
|             ImGui::SameLine(); | ||||
|             if (ImGui::Button("Cancel")) { | ||||
|                 open = false; | ||||
|             } | ||||
|             ImGui::EndPopup(); | ||||
|         } | ||||
|         return open; | ||||
|         core::configManager.release(); | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         float itemWidth = ImGui::GetContentRegionAvail().x; | ||||
|         float lineHeight = ImGui::GetTextLineHeightWithSpacing(); | ||||
|         float spacing = lineHeight - ImGui::GetTextLineHeight(); | ||||
|         bool running = gui::mainWindow.sdrIsRunning(); | ||||
|  | ||||
|         if (running) { style::beginDisabled(); } | ||||
|  | ||||
|         ImGui::SetNextItemWidth(itemWidth); | ||||
|         if (ImGui::Combo("##source", &sourceId, sources.txt)) { | ||||
|             std::string newSource = sources.value(sourceId); | ||||
|             selectSource(newSource); | ||||
|         if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) { | ||||
|             selectSource(sourceNames[sourceId]); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["source"] = newSource; | ||||
|             core::configManager.conf["source"] = sourceNames[sourceId]; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|  | ||||
| @@ -316,45 +196,21 @@ namespace sourcemenu { | ||||
|         } | ||||
|  | ||||
|         ImGui::LeftLabel("Offset mode"); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX() - 2.0f*(lineHeight + 1.5f*spacing)); | ||||
|         if (ImGui::Combo("##_sdrpp_offset", &offsetId, offsets.txt)) { | ||||
|             selectOffsetById(offsetId); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) { | ||||
|             updateOffset(); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["selectedOffset"] = offsets.key(offsetId); | ||||
|             core::configManager.conf["offsetMode"] = offsetMode; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing); | ||||
|         if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::BeginDisabled(); } | ||||
|         if (ImGui::Button("-##_sdrpp_offset_del_", ImVec2(lineHeight + 0.5f*spacing, 0))) { | ||||
|             delOffsetName = selectedOffset; | ||||
|             showDelOffsetDialog = true; | ||||
|         } | ||||
|         if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::EndDisabled(); } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing); | ||||
|         if (ImGui::Button("+##_sdrpp_offset_add_", ImVec2(lineHeight + 0.5f*spacing, 0))) { | ||||
|             strcpy(newOffsetName, "New Offset"); | ||||
|             showAddOffsetDialog = true; | ||||
|         } | ||||
|  | ||||
|         // Offset delete confirmation | ||||
|         if (ImGui::GenericDialog("sdrpp_del_offset_confirm", showDelOffsetDialog, GENERIC_DIALOG_BUTTONS_YES_NO, []() { | ||||
|             ImGui::Text("Deleting offset named \"%s\". Are you sure?", delOffsetName.c_str()); | ||||
|         }) == GENERIC_DIALOG_BUTTON_YES) { | ||||
|             delOffset(delOffsetName); | ||||
|         } | ||||
|  | ||||
|         // Offset add diaglog | ||||
|         if (showAddOffsetDialog) { showAddOffsetDialog = addOffsetDialog(); } | ||||
|  | ||||
|         ImGui::LeftLabel("Offset"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (offsetId == OFFSET_ID_MANUAL) { | ||||
|             if (ImGui::InputDouble("##freq_offset", &manualOffset, 1.0, 100.0)) { | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (offsetMode == OFFSET_MODE_CUSTOM) { | ||||
|             if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) { | ||||
|                 updateOffset(); | ||||
|                 core::configManager.acquire(); | ||||
|                 core::configManager.conf["manualOffset"] = manualOffset; | ||||
|                 core::configManager.conf["offset"] = customOffset; | ||||
|                 core::configManager.release(true); | ||||
|             } | ||||
|         } | ||||
| @@ -366,11 +222,11 @@ namespace sourcemenu { | ||||
|  | ||||
|         if (running) { style::beginDisabled(); } | ||||
|         ImGui::LeftLabel("Decimation"); | ||||
|         ImGui::FillWidth(); | ||||
|         if (ImGui::Combo("##source_decim", &decimId, decimations.txt)) { | ||||
|             sigpath::iqFrontEnd.setDecimation(decimations.value(decimId)); | ||||
|         ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) { | ||||
|             sigpath::iqFrontEnd.setDecimation(1 << decimationPower); | ||||
|             core::configManager.acquire(); | ||||
|             core::configManager.conf["decimation"] = decimations.key(decimId); | ||||
|             core::configManager.conf["decimationPower"] = decimationPower; | ||||
|             core::configManager.release(true); | ||||
|         } | ||||
|         if (running) { style::endDisabled(); } | ||||
|   | ||||
							
								
								
									
										177
									
								
								core/src/gui/menus/streams.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								core/src/gui/menus/streams.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| #include "streams.h" | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <imgui.h> | ||||
| #include <utils/flog.h> | ||||
| #include <gui/style.h> | ||||
| #include <gui/icons.h> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| namespace streamsmenu { | ||||
|     std::vector<SinkID> sinksToBeRemoved; | ||||
|  | ||||
|     std::recursive_mutex sinkTypesMtx; | ||||
|     OptionList<std::string, std::string> sinkTypes; | ||||
|  | ||||
|     std::map<std::string, int> selectedSinkTypeId; | ||||
|     std::map<std::string, int> addSinkTypeId; | ||||
|  | ||||
|     int addType = 0; | ||||
|  | ||||
|     void updateSinkTypeList(const std::string& removed = "") { | ||||
|         std::lock_guard<std::recursive_mutex> lck1(sinkTypesMtx); | ||||
|         auto lck2 = sigpath::streamManager.getSinkTypesLock(); | ||||
|         const auto& types = sigpath::streamManager.getSinkTypes(); | ||||
|         sinkTypes.clear(); | ||||
|         for (const auto& type : types) { | ||||
|             if (type == removed) { continue; } | ||||
|             sinkTypes.define(type, type, type); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void onSinkProviderRegistered(const std::string& type) { | ||||
|         // Update the list | ||||
|         updateSinkTypeList(); | ||||
|  | ||||
|         // Update the ID of the Add dropdown | ||||
|         // TODO | ||||
|  | ||||
|         // Update the selected ID of each drop down | ||||
|         // TODO | ||||
|     } | ||||
|  | ||||
|     void onSinkProviderUnregister(const std::string& type) { | ||||
|         // Update the list | ||||
|         updateSinkTypeList(type); | ||||
|  | ||||
|         // Update the ID of the Add dropdown | ||||
|         // TODO | ||||
|  | ||||
|         // Update the selected ID of each drop down | ||||
|         // TODO | ||||
|     } | ||||
|  | ||||
|     void init() { | ||||
|         sigpath::streamManager.onSinkProviderRegistered.bind(onSinkProviderRegistered); | ||||
|         sigpath::streamManager.onSinkProviderUnregister.bind(onSinkProviderUnregister); | ||||
|         updateSinkTypeList(); | ||||
|     } | ||||
|  | ||||
|     void draw(void* ctx) { | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|         auto lck = sigpath::streamManager.getStreamsLock(); | ||||
|         const auto& streams = sigpath::streamManager.getStreams(); | ||||
|  | ||||
|         int count = 0; | ||||
|         int maxCount = streams.size(); | ||||
|         for (auto& [name, stream] : streams) { | ||||
|             // Stream name  | ||||
|             ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f)); | ||||
|             ImGui::Text("%s", name.c_str()); | ||||
|  | ||||
|             // Display ever sink | ||||
|             if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders)) { | ||||
|                 auto lck2 = stream->getSinksLock(); | ||||
|                 auto sinks = stream->getSinks(); | ||||
|                 for (auto& [id, sink] : sinks) { | ||||
|                     std::string sid = sink->getStringID(); | ||||
|                     ImGui::TableNextRow(); | ||||
|                     ImGui::TableSetColumnIndex(0); | ||||
|                     float tableWidth = ImGui::GetContentRegionAvail().x; | ||||
|                      | ||||
|                     ImGui::Spacing(); | ||||
|  | ||||
|                     // Sink type | ||||
|                     int ttttt = 0; | ||||
|                     ImGui::FillWidth(); | ||||
|                     if (ImGui::Combo(CONCAT("##sdrpp_streams_type_", sid), &ttttt, sinkTypes.txt)) { | ||||
|                          | ||||
|                     } | ||||
|  | ||||
|                     sink->showMenu(); | ||||
|                     float vol = sink->getVolume(); | ||||
|                     bool muted = sink->getMuted(); | ||||
|                     float pan = sink->getPanning(); | ||||
|                     bool linked = true; | ||||
|  | ||||
|                     float height = ImGui::GetTextLineHeightWithSpacing() + 2; | ||||
|                     ImGui::PushID(ImGui::GetID(("sdrpp_streams_center_btn_" + sid).c_str())); | ||||
|                     if (ImGui::ImageButton(icons::ALIGN_CENTER, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) { | ||||
|                         sink->setPanning(0.0f); | ||||
|                     } | ||||
|                     ImGui::PopID(); | ||||
|                     ImGui::SameLine(); | ||||
|                     ImGui::FillWidth(); | ||||
|                     if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_pan_", sid), &pan, -1, 1, "")) { | ||||
|                         sink->setPanning(pan); | ||||
|                     } | ||||
|                      | ||||
|                     if (muted) { | ||||
|                         ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + sid).c_str())); | ||||
|                         if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) { | ||||
|                             sink->setMuted(false); | ||||
|                         } | ||||
|                         ImGui::PopID(); | ||||
|                     } | ||||
|                     else { | ||||
|                         ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + sid).c_str())); | ||||
|                         if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) { | ||||
|                             sink->setMuted(true); | ||||
|                         } | ||||
|                         ImGui::PopID(); | ||||
|                     } | ||||
|                     ImGui::SameLine(); | ||||
|                     ImGui::FillWidth(); | ||||
|                     if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_vol_", sid), &vol, 0, 1, "")) { | ||||
|                         sink->setVolume(vol); | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                     int startCur = ImGui::GetCursorPosX(); | ||||
|                     if (ImGui::Checkbox(CONCAT("Link volume##sdrpp_streams_vol_", sid), &linked)) { | ||||
|                         // TODO | ||||
|                     } | ||||
|                     ImGui::SameLine(); | ||||
|                     if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", sid), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) { | ||||
|                         sinksToBeRemoved.push_back(id); | ||||
|                     } | ||||
|                     ImGui::Spacing(); | ||||
|                 } | ||||
|                 lck2.unlock(); | ||||
|  | ||||
|                 ImGui::TableNextRow(); | ||||
|                 ImGui::TableSetColumnIndex(0); | ||||
|                 float tableWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|                 ImGui::Spacing(); | ||||
|                 int startCur = ImGui::GetCursorPosX(); | ||||
|                 { | ||||
|                     std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx); | ||||
|                     ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &addType, sinkTypes.txt); | ||||
|                     ImGui::SameLine(); | ||||
|                     if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) { | ||||
|                         stream->addSink(sinkTypes.value(addType)); | ||||
|                     } | ||||
|                     ImGui::Spacing(); | ||||
|                 } | ||||
|  | ||||
|                 ImGui::EndTable(); | ||||
|  | ||||
|                 // Remove sinks that need to be removed | ||||
|                 if (!sinksToBeRemoved.empty()) { | ||||
|                     for (auto& id : sinksToBeRemoved) { | ||||
|                         stream->removeSink(id); | ||||
|                     } | ||||
|                     sinksToBeRemoved.clear(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             count++; | ||||
|             if (count < maxCount) { | ||||
|                 ImGui::Spacing(); | ||||
|             } | ||||
|             ImGui::Spacing(); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										6
									
								
								core/src/gui/menus/streams.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/src/gui/menus/streams.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| namespace streamsmenu { | ||||
|     void init(); | ||||
|     void draw(void* ctx); | ||||
| }; | ||||
| @@ -3,7 +3,6 @@ | ||||
| #include <gui/style.h> | ||||
| #include <gui/gui.h> | ||||
| #include <backend.h> | ||||
| #include <utils/hrfreq.h> | ||||
|  | ||||
| #ifndef IMGUI_DEFINE_MATH_OPERATORS | ||||
| #define IMGUI_DEFINE_MATH_OPERATORS | ||||
| @@ -91,7 +90,6 @@ void FrequencySelect::moveCursorToDigit(int i) { | ||||
|  | ||||
| void FrequencySelect::draw() { | ||||
|     auto window = ImGui::GetCurrentWindow(); | ||||
|     auto io = ImGui::GetIO(); | ||||
|     widgetPos = ImGui::GetWindowContentRegionMin(); | ||||
|     ImVec2 cursorPos = ImGui::GetCursorPos(); | ||||
|     widgetPos.x += window->Pos.x + cursorPos.x; | ||||
| @@ -134,7 +132,7 @@ void FrequencySelect::draw() { | ||||
|         ImVec2 mousePos = ImGui::GetMousePos(); | ||||
|         bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); | ||||
|         bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right); | ||||
|         int mw = io.MouseWheel; | ||||
|         int mw = ImGui::GetIO().MouseWheel; | ||||
|         bool onDigit = false; | ||||
|         bool hovered = false; | ||||
|  | ||||
| @@ -176,7 +174,7 @@ void FrequencySelect::draw() { | ||||
|                     moveCursorToDigit(i + 1); | ||||
|                 } | ||||
|  | ||||
|                 auto chars = io.InputQueueCharacters; | ||||
|                 auto chars = ImGui::GetIO().InputQueueCharacters; | ||||
|  | ||||
|                 // For each keyboard characters, type it | ||||
|                 for (int j = 0; j < chars.Size; j++) { | ||||
| @@ -196,34 +194,6 @@ void FrequencySelect::draw() { | ||||
|             } | ||||
|         } | ||||
|         digitHovered = hovered; | ||||
|  | ||||
|         if (isInArea(mousePos, digitTopMins[0], digitBottomMaxs[11])) { | ||||
|             bool shortcutKey = io.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl); | ||||
|             bool ctrlOnly = (io.KeyMods == ImGuiKeyModFlags_Ctrl); | ||||
|             bool shiftOnly = (io.KeyMods == ImGuiKeyModFlags_Shift); | ||||
|             bool copy  = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_C)) || (ctrlOnly  && ImGui::IsKeyPressed(ImGuiKey_Insert))); | ||||
|             bool paste = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_V)) || (shiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert))); | ||||
|             if (copy) { | ||||
|                 // Convert the freqency to a string | ||||
|                 std::string freqStr = hrfreq::toString(frequency); | ||||
|  | ||||
|                 // Write it to the clipboard | ||||
|                 ImGui::SetClipboardText(freqStr.c_str()); | ||||
|             } | ||||
|             if (paste) { | ||||
|                 // Attempt to parse the clipboard as a number | ||||
|                 const char* clip = ImGui::GetClipboardText(); | ||||
|  | ||||
|                 // If the clipboard is not empty, attempt to parse it | ||||
|                 if (clip) { | ||||
|                     double newFreq; | ||||
|                     if (hrfreq::fromString(clip, newFreq)) { | ||||
|                         setFrequency(abs(newFreq)); | ||||
|                         frequencyChanged = true; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     uint64_t freq = 0; | ||||
|   | ||||
| @@ -42,7 +42,6 @@ public: | ||||
|  | ||||
|     class Instance { | ||||
|     public: | ||||
|         virtual ~Instance() {} | ||||
|         virtual void postInit() = 0; | ||||
|         virtual void enable() = 0; | ||||
|         virtual void disable() = 0; | ||||
|   | ||||
| @@ -5,4 +5,5 @@ namespace sigpath { | ||||
|     VFOManager vfoManager; | ||||
|     SourceManager sourceManager; | ||||
|     SinkManager sinkManager; | ||||
|     StreamManager streamManager; | ||||
| }; | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "vfo_manager.h" | ||||
| #include "source.h" | ||||
| #include "sink.h" | ||||
| #include "stream.h" | ||||
| #include <module.h> | ||||
|  | ||||
| namespace sigpath { | ||||
| @@ -10,4 +11,5 @@ namespace sigpath { | ||||
|     SDRPP_EXPORT VFOManager vfoManager; | ||||
|     SDRPP_EXPORT SourceManager sourceManager; | ||||
|     SDRPP_EXPORT SinkManager sinkManager; | ||||
|     SDRPP_EXPORT StreamManager streamManager; | ||||
| }; | ||||
							
								
								
									
										519
									
								
								core/src/signal_path/stream.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								core/src/signal_path/stream.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,519 @@ | ||||
| #include "stream.h" | ||||
| #include <utils/flog.h> | ||||
|  | ||||
| Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream,  const std::string& name, SinkID id, const std::string& stringId) : | ||||
|     entry(entry), | ||||
|     stream(stream), | ||||
|     streamName(name), | ||||
|     id(id), | ||||
|     stringId(stringId) | ||||
| {} | ||||
|  | ||||
| void Sink::showMenu() {} | ||||
|  | ||||
| SinkEntry::SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate) : | ||||
|     manager(manager), | ||||
|     parentStream(parentStream), | ||||
|     id(id) | ||||
| { | ||||
|     this->type = type; | ||||
|     this->inputSamplerate = inputSamplerate; | ||||
|  | ||||
|     // Generate string ID | ||||
|     stringId = parentStream->getName(); | ||||
|     char buf[16]; | ||||
|     sprintf(buf, "%d", (int)id); | ||||
|     stringId += buf; | ||||
|      | ||||
|     // Initialize DSP | ||||
|     resamp.init(&input, inputSamplerate, inputSamplerate); | ||||
|     volumeAdjust.init(&resamp.out, 1.0f, false); | ||||
|  | ||||
|     // Initialize the sink | ||||
|     setType(type); | ||||
| } | ||||
|  | ||||
| std::string SinkEntry::getType() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return type; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setType(const std::string& type) { | ||||
|     // Get unique lock on the entry | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|  | ||||
|     // Delete existing sink | ||||
|     if (sink) { | ||||
|         provider->destroySink(std::move(sink)); | ||||
|     } | ||||
|  | ||||
|     // Get shared lock on sink types | ||||
|     auto lck2 = manager->getSinkTypesLock(); | ||||
|  | ||||
|     // Get the provider or throw error | ||||
|     const auto& types = manager->getSinkTypes(); | ||||
|     if (std::find(types.begin(), types.end(), type) == types.end()) { | ||||
|         this->type.clear(); | ||||
|         throw SinkEntryCreateException("Invalid sink type"); | ||||
|     } | ||||
|  | ||||
|     // Create sink | ||||
|     this->type = type; | ||||
|     provider = manager->providers[type]; | ||||
|     sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id, stringId); | ||||
| } | ||||
|  | ||||
| SinkID SinkEntry::getID() const { | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| float SinkEntry::getVolume() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return volume; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setVolume(float volume) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     this->volume = volume; | ||||
|     volumeAdjust.setVolume(volume); | ||||
|     onVolumeChanged(volume); | ||||
| } | ||||
|  | ||||
| bool SinkEntry::getMuted() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return muted; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setMuted(bool muted) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     this->muted = muted; | ||||
|     volumeAdjust.setMuted(muted); | ||||
|     onMutedChanged(muted); | ||||
| } | ||||
|  | ||||
| float SinkEntry::getPanning() const { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     return panning; | ||||
| } | ||||
|  | ||||
| void SinkEntry::setPanning(float panning) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     this->panning = panning; | ||||
|     // TODO | ||||
|     onPanningChanged(panning); | ||||
| } | ||||
|  | ||||
| void SinkEntry::showMenu() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     sink->showMenu(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::startSink() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     sink->start(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::stopSink() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     sink->stop(); | ||||
| } | ||||
|  | ||||
| std::lock_guard<std::recursive_mutex> SinkEntry::getLock() const { | ||||
|     return std::lock_guard<std::recursive_mutex>(mtx); | ||||
| } | ||||
|  | ||||
| void SinkEntry::setSamplerate(double samplerate) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.setOutSamplerate(samplerate); | ||||
| } | ||||
|  | ||||
| void SinkEntry::startDSP() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.start(); | ||||
|     volumeAdjust.start(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::stopDSP() { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.stop(); | ||||
|     volumeAdjust.stop(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::destroy(bool forgetSettings) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     if (sink) { | ||||
|         provider->destroySink(std::move(sink)); | ||||
|     } | ||||
|     type.clear(); | ||||
| } | ||||
|  | ||||
| void SinkEntry::setInputSamplerate(double samplerate) { | ||||
|     std::lock_guard<std::recursive_mutex> lck(mtx); | ||||
|     resamp.setInSamplerate(samplerate); | ||||
| } | ||||
|  | ||||
| std::string SinkEntry::getStringID() const { | ||||
|     return stringId; | ||||
| } | ||||
|  | ||||
| Stream::Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) : | ||||
|     manager(manager), | ||||
|     name(name) | ||||
| { | ||||
|     this->samplerate = samplerate; | ||||
|  | ||||
|     // Initialize DSP | ||||
|     split.init(stream); | ||||
| } | ||||
|  | ||||
| Stream::~Stream() { | ||||
|     // Copy sink IDs | ||||
|     std::vector<SinkID> ids; | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         ids.push_back(id); | ||||
|     } | ||||
|  | ||||
|     // Remove them all | ||||
|     for (auto& id : ids) { | ||||
|         removeSink(id, false); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const std::string& Stream::getName() const { | ||||
|     return name; | ||||
| } | ||||
|  | ||||
| SinkID Stream::addSink(const std::string& type, SinkID id) { | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Find a free ID if not provided | ||||
|     if (id < 0) { | ||||
|         for (id = 0; sinks.find(id) != sinks.end(); id++); | ||||
|     } | ||||
|     else { | ||||
|         // Check that the provided ID is valid | ||||
|         if (sinks.find(id) != sinks.end()) { | ||||
|             flog::error("Tried to create sink for stream '{}' with existing ID: {}", name, id); | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Create sink entry | ||||
|     std::shared_ptr<SinkEntry> sink; | ||||
|     try { | ||||
|         sink = std::make_shared<SinkEntry>(manager, this, type, id, samplerate); | ||||
|     } | ||||
|     catch (SinkEntryCreateException e) { | ||||
|         flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what()); | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // Start the sink and DSP | ||||
|     sink->startSink(); | ||||
|     if (running) { sink->startDSP(); } | ||||
|  | ||||
|     // Bind the sinks's input | ||||
|     split.bindStream(&sink->input); | ||||
|  | ||||
|     // Add sink to list | ||||
|     sinks[id] = sink; | ||||
|  | ||||
|     // Release lock and emit event | ||||
|     lck.unlock(); | ||||
|     onSinkAdded(sink); | ||||
|  | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| void Stream::removeSink(SinkID id, bool forgetSettings) { | ||||
|     // Acquire shared lock | ||||
|     std::shared_ptr<SinkEntry> sink; | ||||
|     { | ||||
|         std::shared_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|         // Check that the ID exists | ||||
|         if (sinks.find(id) == sinks.end()) { | ||||
|             flog::error("Tried to remove sink with unknown ID: {}", id); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get sink | ||||
|         sink = sinks[id]; | ||||
|     } | ||||
|      | ||||
|     // Emit event | ||||
|     onSinkRemove(sink); | ||||
|  | ||||
|     // Acquire unique lock | ||||
|     { | ||||
|         std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|         // Check that it's still in the list | ||||
|         if (sinks.find(id) == sinks.end()) { | ||||
|             flog::error("Tried to remove sink with unknown ID: {}", id); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Remove from list | ||||
|         sinks.erase(id); | ||||
|  | ||||
|         // Unbind the sink's steam | ||||
|         split.unbindStream(&sink->input); | ||||
|          | ||||
|         // Stop the sink and DSP | ||||
|         sink->stopDSP(); | ||||
|         sink->stopSink(); | ||||
|  | ||||
|         // Delete instance | ||||
|         sink->destroy(forgetSettings); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::shared_lock<std::shared_mutex> Stream::getSinksLock() { | ||||
|     return std::shared_lock<std::shared_mutex>(sinksMtx); | ||||
| } | ||||
|  | ||||
| const std::map<SinkID, std::shared_ptr<SinkEntry>>& Stream::getSinks() const { | ||||
|     return sinks; | ||||
| } | ||||
|  | ||||
| MasterStream::MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) : | ||||
|     Stream(manager, name, stream, samplerate) | ||||
| {} | ||||
|  | ||||
| void MasterStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) { | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // If all that's needed is to set the input, do it and return | ||||
|     if (samplerate == 0.0) { | ||||
|         split.setInput(stream); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Update samplerate | ||||
|     this->samplerate = samplerate; | ||||
|  | ||||
|     // Stop DSP | ||||
|     if (running) { | ||||
|         split.stop(); | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->stopDSP(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // Set input and samplerate | ||||
|     split.setInput(stream); | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->setInputSamplerate(samplerate); | ||||
|     } | ||||
|  | ||||
|     // Start DSP | ||||
|     if (running) { | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->startDSP(); | ||||
|         } | ||||
|         split.start(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MasterStream::setSamplerate(double samplerate) { | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Update samplerate | ||||
|     this->samplerate = samplerate; | ||||
|  | ||||
|     // TODO: Maybe simply disallow while running? | ||||
|  | ||||
|     // Stop DSP if it was running | ||||
|     if (running) { | ||||
|         split.stop(); | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->stopDSP(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Set samplerate | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->setInputSamplerate(samplerate); | ||||
|     } | ||||
|  | ||||
|     // Start DSP if it was running | ||||
|     if (running) { | ||||
|         for (auto& [id, sink] : sinks) { | ||||
|             sink->startDSP(); | ||||
|         } | ||||
|         split.start(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MasterStream::startDSP() { | ||||
|     // TODO: Maybe add a different mutex for the stream? | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Check if already running | ||||
|     if (running) { return; } | ||||
|  | ||||
|     // Start all DSP | ||||
|     split.start(); | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->startDSP(); | ||||
|     } | ||||
|     running = true; | ||||
| } | ||||
|  | ||||
| void MasterStream::stopDSP() { | ||||
|     // TODO: Maybe add a different mutex for the stream? | ||||
|     std::unique_lock<std::shared_mutex> lck(sinksMtx); | ||||
|  | ||||
|     // Check if already running | ||||
|     if (!running) { return; } | ||||
|  | ||||
|     // Start all DSP | ||||
|     split.stop(); | ||||
|     for (auto& [id, sink] : sinks) { | ||||
|         sink->stopDSP(); | ||||
|     } | ||||
|     running = false; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<MasterStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) { | ||||
|     std::unique_lock<std::shared_mutex> lck(streamsMtx); | ||||
|  | ||||
|     // Check that no stream with that name already exists | ||||
|     if (streams.find(name) != streams.end()) { | ||||
|         flog::error("Tried to created stream with an existing name: {}", name); | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     // Create and save stream | ||||
|     std::shared_ptr<MasterStream> newStream(new MasterStream(this, name, stream, samplerate)); | ||||
|     streams[name] = newStream; | ||||
|  | ||||
|     // Release lock and emit event | ||||
|     lck.unlock(); | ||||
|     onStreamCreated(newStream); | ||||
|  | ||||
|     return newStream; | ||||
| } | ||||
|  | ||||
| void StreamManager::destroyStream(std::shared_ptr<MasterStream>& stream) { | ||||
|     // Emit event | ||||
|     onStreamDestroy(stream); | ||||
|  | ||||
|     // Aquire complete lock on the stream list | ||||
|     { | ||||
|         std::unique_lock<std::shared_mutex> lck(streamsMtx); | ||||
|  | ||||
|         // Get iterator of the stream | ||||
|         auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<Stream>> e) { | ||||
|             return e.second == stream; | ||||
|         }); | ||||
|         if (it == streams.end()) { | ||||
|             flog::error("Tried to delete a stream using an invalid pointer. Stream not found in list"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Delete entry from list | ||||
|         flog::debug("Stream pointer uses, should be 2 and is {}", (int)stream.use_count()); | ||||
|         streams.erase(it); | ||||
|     } | ||||
|  | ||||
|     // Reset passed pointer | ||||
|     stream.reset(); | ||||
| } | ||||
|  | ||||
| std::shared_lock<std::shared_mutex> StreamManager::getStreamsLock() { | ||||
|     return std::shared_lock<std::shared_mutex>(streamsMtx); | ||||
| } | ||||
|  | ||||
| const std::map<std::string, std::shared_ptr<Stream>>& StreamManager::getStreams() const { | ||||
|     return streams; | ||||
| } | ||||
|  | ||||
| void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) { | ||||
|     std::unique_lock<std::shared_mutex> lck(providersMtx); | ||||
|  | ||||
|     // Check that a provider with that name doesn't already exist | ||||
|     if (providers.find(name) != providers.end()) { | ||||
|         flog::error("Tried to register a sink provider with an existing name: {}", name); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Add provider to the list and sort name list | ||||
|     providers[name] = provider; | ||||
|     sinkTypes.push_back(name); | ||||
|     std::sort(sinkTypes.begin(), sinkTypes.end()); | ||||
|  | ||||
|     // Release lock and emit event | ||||
|     lck.unlock(); | ||||
|     onSinkProviderRegistered(name); | ||||
| } | ||||
|  | ||||
| void StreamManager::unregisterSinkProvider(SinkProvider* provider) { | ||||
|     // Get provider name for event | ||||
|     std::string type; | ||||
|     { | ||||
|         std::shared_lock<std::shared_mutex> lck(providersMtx); | ||||
|         auto it = std::find_if(providers.begin(), providers.end(), [&provider](std::pair<const std::string, SinkProvider *> e) { | ||||
|             return e.second == provider; | ||||
|         }); | ||||
|         if (it == providers.end()) { | ||||
|             flog::error("Tried to unregister sink provider using invalid pointer"); | ||||
|             return; | ||||
|         } | ||||
|         type = (*it).first; | ||||
|     } | ||||
|  | ||||
|     // Emit event | ||||
|     onSinkProviderUnregister(type); | ||||
|  | ||||
|     // Acquire shared lock on streams | ||||
|     { | ||||
|         std::unique_lock<std::shared_mutex> lck1(providersMtx); | ||||
|         std::shared_lock<std::shared_mutex> lck2(streamsMtx); | ||||
|         for (auto& [name, stream] : streams) { | ||||
|             // Aquire lock on sink list | ||||
|             auto sLock = stream->getSinksLock(); | ||||
|             const auto& sinks = stream->getSinks(); | ||||
|  | ||||
|             // Find all sinks with the type that is about to be removed | ||||
|             std::vector<SinkID> toRemove; | ||||
|             for (auto& [id, sink] : sinks) { | ||||
|                 if (sink->getType() != type) { continue; } | ||||
|                 toRemove.push_back(id); | ||||
|             } | ||||
|  | ||||
|             // Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING) | ||||
|             sLock.unlock(); | ||||
|             for (auto& id : toRemove) { | ||||
|                 stream->removeSink(id); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Remove from the lists | ||||
|         if (providers.find(type) != providers.end()) { | ||||
|             providers.erase(type); | ||||
|         } | ||||
|         else { | ||||
|             flog::error("Could not remove sink provider from list"); | ||||
|         } | ||||
|  | ||||
|         auto it = std::find(sinkTypes.begin(), sinkTypes.end(), type); | ||||
|         if (it != sinkTypes.end()) { | ||||
|             sinkTypes.erase(it); | ||||
|         } | ||||
|         else { | ||||
|             flog::error("Could not remove sink provider from sink type list"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::shared_lock<std::shared_mutex> StreamManager::getSinkTypesLock() { | ||||
|     return std::shared_lock<std::shared_mutex>(providersMtx); | ||||
| } | ||||
|  | ||||
| const std::vector<std::string>& StreamManager::getSinkTypes() const { | ||||
|     // TODO: This allows code to modify the names... | ||||
|     return sinkTypes; | ||||
| } | ||||
							
								
								
									
										334
									
								
								core/src/signal_path/stream.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								core/src/signal_path/stream.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,334 @@ | ||||
| #pragma once | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/types.h> | ||||
| #include <dsp/routing/splitter.h> | ||||
| #include <dsp/multirate/rational_resampler.h> | ||||
| #include <dsp/audio/volume.h> | ||||
| #include <utils/new_event.h> | ||||
| #include <shared_mutex> | ||||
| #include <stdexcept> | ||||
|  | ||||
| class SinkEntry; | ||||
| class Stream; | ||||
| class MasterStream; | ||||
| class StreamManager; | ||||
|  | ||||
| using SinkID = int; | ||||
|  | ||||
| class Sink { | ||||
| public: | ||||
|     Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId); | ||||
|     virtual ~Sink() {} | ||||
|  | ||||
|     virtual void start() = 0; | ||||
|     virtual void stop() = 0; | ||||
|     virtual void showMenu(); | ||||
|  | ||||
| protected: | ||||
|     SinkEntry* const entry; | ||||
|     dsp::stream<dsp::stereo_t>* const stream; | ||||
|     const std::string streamName; | ||||
|     const SinkID id; | ||||
|     const std::string stringId; | ||||
| }; | ||||
|  | ||||
| class SinkProvider { | ||||
|     friend Sink; | ||||
| public: | ||||
|     /** | ||||
|      * Create a sink instance. | ||||
|      * @param name Name of the audio stream. | ||||
|      * @param index Index of the sink in the menu. Should be use to keep settings. | ||||
|     */ | ||||
|     virtual std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) = 0; | ||||
|  | ||||
|     /** | ||||
|      * Destroy a sink instance. This function is so that the provide knows at all times how many instances there are. | ||||
|      * @param sink Instance of the sink. | ||||
|     */ | ||||
|     virtual void destroySink(std::unique_ptr<Sink> sink) { | ||||
|         sink.reset(); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class SinkEntryCreateException : public std::runtime_error { | ||||
| public: | ||||
|     SinkEntryCreateException(const char* what) : std::runtime_error(what) {} | ||||
| }; | ||||
|  | ||||
| // TODO: Would be cool to have data and audio sinks instead of just audio. | ||||
| class SinkEntry { | ||||
|     friend Sink; | ||||
|     friend Stream; | ||||
|     friend MasterStream; | ||||
| public: | ||||
|     SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate); | ||||
|      | ||||
|     /** | ||||
|      * Get the type of the sink. | ||||
|      * @return Type of the sink. | ||||
|     */ | ||||
|     std::string getType() const; | ||||
|  | ||||
|     /** | ||||
|      * Change the type of the sink. | ||||
|      * @param type New sink type. | ||||
|     */ | ||||
|     void setType(const std::string& type); | ||||
|  | ||||
|     /** | ||||
|      * Get the ID of the sink. | ||||
|      * @return ID of the sink. | ||||
|     */ | ||||
|     SinkID getID() const; | ||||
|  | ||||
|     /** | ||||
|      * Get sink volume. | ||||
|      * @return Volume as value between 0.0 and 1.0. | ||||
|     */ | ||||
|     float getVolume() const; | ||||
|  | ||||
|     /** | ||||
|      * Set sink volume. | ||||
|      * @param volume Volume as value between 0.0 and 1.0. | ||||
|     */ | ||||
|     void setVolume(float volume); | ||||
|  | ||||
|     /** | ||||
|      * Check if the sink is muted. | ||||
|      * @return True if muted, false if not. | ||||
|     */ | ||||
|     bool getMuted() const; | ||||
|  | ||||
|     /** | ||||
|      * Set wether or not the sink is muted | ||||
|      * @param muted True to mute, false to unmute. | ||||
|     */ | ||||
|     void setMuted(bool muted); | ||||
|  | ||||
|     /** | ||||
|      * Get sink panning. | ||||
|      * @return Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively. | ||||
|     */ | ||||
|     float getPanning() const; | ||||
|  | ||||
|     /** | ||||
|      * Set sink panning. | ||||
|      * @param panning Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively. | ||||
|     */ | ||||
|     void setPanning(float panning); | ||||
|  | ||||
|     /** | ||||
|      * Show the sink type-specific menu. | ||||
|     */ | ||||
|     void showMenu(); | ||||
|  | ||||
|     /** | ||||
|      * Get the string form ID unique to both the sink and stream. Be used to reference settings. | ||||
|      * @return Unique string ID. | ||||
|     */ | ||||
|     std::string getStringID() const; | ||||
|  | ||||
|     // Emitted when the type of the sink was changed | ||||
|     NewEvent<const std::string&> onTypeChanged; | ||||
|     // Emmited when volume of the sink was changed | ||||
|     NewEvent<float> onVolumeChanged; | ||||
|     // Emitted when the muted state of the sink was changed | ||||
|     NewEvent<bool> onMutedChanged; | ||||
|     // Emitted when the panning of the sink was changed | ||||
|     NewEvent<float> onPanningChanged; | ||||
|  | ||||
|     // TODO: Need to allow the sink to change the entry samplerate and start/stop the DSP | ||||
|     //       This will also require allowing it to get a lock on the sink so others don't attempt to mess with it. | ||||
|     std::lock_guard<std::recursive_mutex> getLock() const; | ||||
|     void startDSP(); | ||||
|     void stopDSP(); | ||||
|     void setSamplerate(double samplerate); | ||||
|  | ||||
| private: | ||||
|     void startSink(); | ||||
|     void stopSink(); | ||||
|      | ||||
|     void destroy(bool forgetSettings); | ||||
|     void setInputSamplerate(double samplerate); | ||||
|  | ||||
|     mutable std::recursive_mutex mtx; | ||||
|     dsp::stream<dsp::stereo_t> input; | ||||
|     dsp::multirate::RationalResampler<dsp::stereo_t> resamp; | ||||
|     dsp::audio::Volume volumeAdjust; | ||||
|  | ||||
|     SinkProvider* provider = NULL; | ||||
|     std::unique_ptr<Sink> sink; | ||||
|     std::string type; | ||||
|     const SinkID id; | ||||
|     double inputSamplerate; | ||||
|     Stream* const parentStream; | ||||
|     StreamManager* const manager; | ||||
|  | ||||
|     std::string stringId; | ||||
|      | ||||
|     float volume = 1.0f; | ||||
|     bool muted = false; | ||||
|     float panning = 0.0f; | ||||
| }; | ||||
|  | ||||
| class Stream { | ||||
| protected: | ||||
|     Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate); | ||||
| public: | ||||
|     ~Stream(); | ||||
|  | ||||
|     /** | ||||
|      * Get the name of the stream. | ||||
|      * @return Name of the stream. | ||||
|     */ | ||||
|     const std::string& getName() const; | ||||
|  | ||||
|     /** | ||||
|      * Add a sink to the stream. | ||||
|      * @param type Type of the sink. | ||||
|      * @param id ID of the sink. Optional, -1 if automatic. | ||||
|      * @return ID of the new sink or -1 on error. | ||||
|     */ | ||||
|     SinkID addSink(const std::string& type, SinkID id = -1); | ||||
|  | ||||
|     /** | ||||
|      * Remove a sink from a stream. | ||||
|      * @param id ID of the sink. | ||||
|      * @param forgetSettings Forget the settings for the sink. | ||||
|     */ | ||||
|     void removeSink(SinkID id, bool forgetSettings = true); | ||||
|  | ||||
|     /** | ||||
|      * Aquire a lock for the sink list. | ||||
|      * @return Shared lock for the sink list. | ||||
|     */ | ||||
|     std::shared_lock<std::shared_mutex> getSinksLock(); | ||||
|  | ||||
|     /** | ||||
|      * Get the list of all sinks belonging to this stream. | ||||
|      * @return Sink list. | ||||
|     */ | ||||
|     const std::map<SinkID, std::shared_ptr<SinkEntry>>& getSinks() const; | ||||
|  | ||||
|     // Emitted when the samplerate of the stream was changed | ||||
|     NewEvent<double> onSamplerateChanged; | ||||
|     // Emitted when a sink was added | ||||
|     NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded; | ||||
|     // Emitted when a sink is being removed | ||||
|     NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove; | ||||
|  | ||||
| protected: | ||||
|     StreamManager* const manager; | ||||
|     const std::string name; | ||||
|     double samplerate; | ||||
|     dsp::routing::Splitter<dsp::stereo_t> split; | ||||
|     bool running = false; | ||||
|  | ||||
|     std::map<SinkID, std::shared_ptr<SinkEntry>> sinks; | ||||
|     std::shared_mutex sinksMtx; | ||||
| }; | ||||
|  | ||||
| class MasterStream : public Stream { | ||||
|     friend StreamManager; | ||||
|     MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate); | ||||
| public: | ||||
|     /** | ||||
|      * Set DSP stream input. | ||||
|      * @param stream DSP stream. | ||||
|      * @param samplerate New samplerate (optional, 0.0 if not used). | ||||
|     */ | ||||
|     void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0); | ||||
|  | ||||
|     /** | ||||
|      * Set the samplerate of the input stream. | ||||
|      * @param samplerate Samplerate in Hz. | ||||
|     */ | ||||
|     void setSamplerate(double samplerate); | ||||
|  | ||||
|     /** | ||||
|      * Start the DSP. | ||||
|     */ | ||||
|     void startDSP(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the DSP. | ||||
|     */ | ||||
|     void stopDSP(); | ||||
| }; | ||||
|  | ||||
| class StreamManager { | ||||
|     friend SinkEntry; | ||||
| public: | ||||
|     /** | ||||
|      * Create an audio stream. | ||||
|      * @param name Name of the stream. | ||||
|      * @param stream DSP stream that outputs the audio. | ||||
|      * @param samplerate Samplerate of the audio data. | ||||
|      * @return Audio stream instance. | ||||
|     */ | ||||
|     std::shared_ptr<MasterStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate); | ||||
|  | ||||
|     /** | ||||
|      * Destroy an audio stream. | ||||
|      * @param stream Stream to destroy. The passed shared pointer will be automatically reset. | ||||
|     */ | ||||
|     void destroyStream(std::shared_ptr<MasterStream>& stream); | ||||
|  | ||||
|     /** | ||||
|      * Aquire a lock for the stream list. | ||||
|      * @return Shared lock for the stream list. | ||||
|     */ | ||||
|     std::shared_lock<std::shared_mutex> getStreamsLock(); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of streams and their associated names. | ||||
|      * @return Map of names to stream instance. | ||||
|     */ | ||||
|     const std::map<std::string, std::shared_ptr<Stream>>& getStreams() const; | ||||
|  | ||||
|     /** | ||||
|      * Register a sink provider. | ||||
|      * @param name Name of the sink type. | ||||
|      * @param provider Sink provider instance. | ||||
|     */ | ||||
|     void registerSinkProvider(const std::string& name, SinkProvider* provider); | ||||
|  | ||||
|     /** | ||||
|      * Unregister a sink provider. | ||||
|      * @param name Name of the sink type. | ||||
|     */ | ||||
|     void unregisterSinkProvider(SinkProvider* provider); | ||||
|  | ||||
|     /** | ||||
|      * Aquire a lock for the sink type list. | ||||
|      * @return Shared lock for the sink type list. | ||||
|     */ | ||||
|     std::shared_lock<std::shared_mutex> getSinkTypesLock(); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of sink types. | ||||
|      * @return List of sink type names in alphabetical order. | ||||
|     */ | ||||
|     const std::vector<std::string>& getSinkTypes() const; | ||||
|  | ||||
|     // Emitted when a stream was created | ||||
|     NewEvent<std::shared_ptr<Stream>> onStreamCreated; | ||||
|     // Emitted when a stream is about to be destroyed | ||||
|     NewEvent<std::shared_ptr<Stream>> onStreamDestroy; | ||||
|     // Emitted when a sink provider was registered | ||||
|     NewEvent<const std::string&> onSinkProviderRegistered; | ||||
|     // Emitted when a sink provider is about to be unregistered | ||||
|     NewEvent<const std::string&> onSinkProviderUnregister; | ||||
|  | ||||
| private: | ||||
|     std::map<std::string, std::shared_ptr<Stream>> streams; | ||||
|     std::shared_mutex streamsMtx; | ||||
|  | ||||
|     std::map<std::string, SinkProvider*> providers; | ||||
|     std::vector<std::string> sinkTypes; | ||||
|     std::shared_mutex providersMtx; | ||||
| }; | ||||
| @@ -1,120 +0,0 @@ | ||||
| #include "hrfreq.h" | ||||
| #include <utils/flog.h> | ||||
|  | ||||
| namespace hrfreq { | ||||
|  | ||||
|  | ||||
|     std::string toString(double freq) { | ||||
|         // Determine the scale | ||||
|         int maxDecimals = 0; | ||||
|         const char* suffix = "Hz"; | ||||
|         if (freq >= 1e9) { | ||||
|             freq /= 1e9; | ||||
|             maxDecimals = 9; | ||||
|             suffix = "GHz"; | ||||
|         } | ||||
|         else if (freq >= 1e6) { | ||||
|             freq /= 1e6; | ||||
|             maxDecimals = 6; | ||||
|             suffix = "MHz"; | ||||
|         } | ||||
|         else if (freq >= 1e3) { | ||||
|             freq /= 1e3; | ||||
|             maxDecimals = 3; | ||||
|             suffix = "KHz"; | ||||
|         } | ||||
|  | ||||
|         // Convert to string (TODO: Not sure if limiting the decimals rounds) | ||||
|         char numBuf[128]; | ||||
|         int numLen = sprintf(numBuf, "%0.*lf", maxDecimals, freq); | ||||
|  | ||||
|         // If there is a decimal point, remove the useless zeros | ||||
|         if (maxDecimals) { | ||||
|             for (int i = numLen-1; i >= 0; i--) { | ||||
|                 bool dot = (numBuf[i] == '.'); | ||||
|                 if (numBuf[i] != '0' && !dot) { break; } | ||||
|                 numBuf[i] = 0; | ||||
|                 if (dot) { break; } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Concat the suffix | ||||
|         char finalBuf[128]; | ||||
|         sprintf(finalBuf, "%s%s", numBuf, suffix); | ||||
|  | ||||
|         // Return the final string | ||||
|         return finalBuf; | ||||
|     } | ||||
|  | ||||
|     bool isNumeric(char c) { | ||||
|         return std::isdigit(c) || c == '+' || c == '-' || c == '.' || c == ','; | ||||
|     } | ||||
|  | ||||
|     bool fromString(const std::string& str, double& freq) { | ||||
|         // Skip non-numeric characters | ||||
|         int i = 0; | ||||
|         char c; | ||||
|         for (; i < str.size(); i++) { | ||||
|             if (isNumeric(str[i])) { break; } | ||||
|         } | ||||
|  | ||||
|         // Extract the numeric part | ||||
|         std::string numeric; | ||||
|         for (; i < str.size(); i++) { | ||||
|             // Get the character | ||||
|             c = str[i]; | ||||
|  | ||||
|             // If it's a letter, stop | ||||
|             if (std::isalpha(c)) { break; } | ||||
|  | ||||
|             // If isn't numeric, skip it | ||||
|             if (!isNumeric(c)) { continue; } | ||||
|  | ||||
|             // If it's a comma, skip it for now. This enforces a dot as a decimal point | ||||
|             if (c == ',') { continue; } | ||||
|  | ||||
|             // Add the character to the numeric string | ||||
|             numeric += c; | ||||
|         } | ||||
|  | ||||
|         // Attempt to parse the numeric part | ||||
|         double num; | ||||
|         try { | ||||
|             num = std::stod(numeric); | ||||
|         } | ||||
|         catch (const std::exception& e) { | ||||
|             flog::error("Failed to parse numeric part: '{}'", numeric); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // If no more text is available, assume the numeric part gives a frequency in Hz | ||||
|         if (i == str.size()) { | ||||
|             flog::warn("No unit given, assuming it's Hz"); | ||||
|             freq = num; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // Scale the numeric value depending on the first scale character | ||||
|         char scale = std::toupper(str[i]); | ||||
|         switch (scale) { | ||||
|         case 'G': | ||||
|             num *= 1e9; | ||||
|             break; | ||||
|         case 'M': | ||||
|             num *= 1e6; | ||||
|             break; | ||||
|         case 'K': | ||||
|             num *= 1e3; | ||||
|             break; | ||||
|         case 'H': | ||||
|             break; | ||||
|         default: | ||||
|             flog::warn("Unknown frequency scale: '{}'", scale); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         // Return the frequency | ||||
|         freq = num; | ||||
|         return true; // TODO | ||||
|     } | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
|  | ||||
| namespace hrfreq { | ||||
|     /** | ||||
|      * Convert a frequency to a human-readable string. | ||||
|      * @param freq Frequency in Hz. | ||||
|      * @return Human-readable representation of the frequency. | ||||
|     */ | ||||
|     std::string toString(double freq); | ||||
|  | ||||
|     /** | ||||
|      * Convert a human-readable representation of a frequency to a frequency value. | ||||
|      * @param str String containing the human-readable frequency. | ||||
|      * @param freq Value to write the decoded frequency to. | ||||
|      * @return True on success, false otherwise. | ||||
|     */ | ||||
|     bool fromString(const std::string& str, double& freq); | ||||
| } | ||||
| @@ -63,6 +63,7 @@ namespace net::http { | ||||
|  | ||||
|     std::string MessageHeader::getField(const std::string name) { | ||||
|         // TODO: Check if exists | ||||
|         // TODO: Maybe declare the set/get field functions to do type conversions automatically? | ||||
|         return fields[name]; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| #pragma once | ||||
|  | ||||
| #define VERSION_STR "1.2.1" | ||||
| #define VERSION_STR "1.1.0" | ||||
| @@ -1,37 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(dab_decoder) | ||||
|  | ||||
| file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| target_include_directories(dab_decoder PRIVATE "src/") | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_include_directories(dab_decoder PRIVATE "C:/Program Files/codec2/include/") | ||||
|     target_link_directories(dab_decoder PRIVATE "C:/Program Files/codec2/lib") | ||||
|  | ||||
|     target_link_libraries(dab_decoder PRIVATE libcodec2) | ||||
| elseif (ANDROID) | ||||
|     target_include_directories(dab_decoder PUBLIC | ||||
|         /sdr-kit/${ANDROID_ABI}/include/codec2 | ||||
|     ) | ||||
|  | ||||
|     target_link_libraries(dab_decoder PUBLIC | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/libcodec2.so | ||||
|     ) | ||||
| else () | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBCODEC2 REQUIRED codec2) | ||||
|  | ||||
|     target_include_directories(dab_decoder PRIVATE ${LIBCODEC2_INCLUDE_DIRS}) | ||||
|     target_link_directories(dab_decoder PRIVATE ${LIBCODEC2_LIBRARY_DIRS}) | ||||
|     target_link_libraries(dab_decoder PRIVATE ${LIBCODEC2_LIBRARIES}) | ||||
|  | ||||
|     # Include it because for some reason pkgconfig doesn't look here? | ||||
|     if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") | ||||
|         target_include_directories(dab_decoder PRIVATE "/usr/local/include") | ||||
|     endif() | ||||
| endif () | ||||
| @@ -1,280 +0,0 @@ | ||||
| #pragma once | ||||
| #include <dsp/processor.h> | ||||
| #include <utils/flog.h> | ||||
| #include <fftw3.h> | ||||
| #include "dab_phase_sym.h" | ||||
|  | ||||
| namespace dab { | ||||
|     class CyclicSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> { | ||||
|         using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>; | ||||
|     public: | ||||
|         CyclicSync() {} | ||||
|  | ||||
|         // TODO: The default AGC rate is probably way too fast, plot out the avgCorr to see how much it moves | ||||
|         CyclicSync(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { init(in, symbolLength, cyclicPrefixLength, samplerate, agcRate); } | ||||
|  | ||||
|         void init(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { | ||||
|             // Computer the number of samples for the symbol and its cyclic prefix | ||||
|             symbolSamps = round(samplerate * symbolLength); | ||||
|             prefixSamps = round(samplerate * cyclicPrefixLength); | ||||
|  | ||||
|             // Allocate and clear the delay buffer | ||||
|             delayBuf = dsp::buffer::alloc<dsp::complex_t>(STREAM_BUFFER_SIZE + 64000); | ||||
|             dsp::buffer::clear(delayBuf, symbolSamps); | ||||
|  | ||||
|             // Allocate and clear the history buffer | ||||
|             histBuf = dsp::buffer::alloc<dsp::complex_t>(prefixSamps); | ||||
|             dsp::buffer::clear(histBuf, prefixSamps); | ||||
|  | ||||
|             // Compute the delay input addresses | ||||
|             delayBufInput = &delayBuf[symbolSamps]; | ||||
|  | ||||
|             // Compute the correlation AGC configuration | ||||
|             this->agcRate = agcRate; | ||||
|             agcRateInv = 1.0f - agcRate; | ||||
|              | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|              | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             // Copy the data into the normal delay buffer | ||||
|             memcpy(delayBufInput, base_type::_in->readBuf, count * sizeof(dsp::complex_t)); | ||||
|  | ||||
|             // Flush the input stream | ||||
|             base_type::_in->flush(); | ||||
|  | ||||
|             // Do cross-correlation | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 // Get the current history slot | ||||
|                 dsp::complex_t* slot = &histBuf[histId++]; | ||||
|  | ||||
|                 // Wrap around the history slot index (TODO: Check that the history buffer's length is correct) | ||||
|                 histId %= prefixSamps; | ||||
|  | ||||
|                 // Kick out last value from the correlation | ||||
|                 corr -= *slot; | ||||
|  | ||||
|                 // Save input value and compute the new prodct | ||||
|                 dsp::complex_t val = delayBuf[i]; | ||||
|                 dsp::complex_t prod = val.conj()*delayBuf[i+symbolSamps]; | ||||
|  | ||||
|                 // Add the new value to the correlation | ||||
|                 *slot = prod; | ||||
|  | ||||
|                 // Add the new value to the history buffer | ||||
|                 corr += prod; | ||||
|  | ||||
|                 // Compute sample amplitude | ||||
|                 float rcorr = corr.amplitude(); | ||||
|  | ||||
|                 // If a high enough peak is reached, reset the symbol counter | ||||
|                 if (rcorr > avgCorr && rcorr > peakCorr) { // Note keeping an average level might not be needed | ||||
|                     peakCorr = rcorr; | ||||
|                     peakLCorr = lastCorr; | ||||
|                     samplesSincePeak = 0; | ||||
|                 } | ||||
|  | ||||
|                 // If this is the sample right after the peak, save it | ||||
|                 if (samplesSincePeak == 1) { | ||||
|                     peakRCorr = rcorr; | ||||
|                 } | ||||
|  | ||||
|                 // Write the sample to the output | ||||
|                 out.writeBuf[samplesSincePeak++] = val; | ||||
|  | ||||
|                 // If the end of the symbol is reached, send it off | ||||
|                 if (samplesSincePeak >= symbolSamps) { | ||||
|                     if (!out.swap(symbolSamps)) { | ||||
|                         return -1; | ||||
|                     } | ||||
|                     samplesSincePeak = 0; | ||||
|                     peakCorr = 0; | ||||
|                 } | ||||
|  | ||||
|                 // Update the average correlation | ||||
|                 lastCorr = rcorr; | ||||
|  | ||||
|                 // Update the average correlation value | ||||
|                 avgCorr = agcRate*rcorr + agcRateInv*avgCorr; | ||||
|             } | ||||
|  | ||||
|             // Move unused data | ||||
|             memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t)); | ||||
|  | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         int symbolSamps; | ||||
|         int prefixSamps; | ||||
|  | ||||
|         int histId = 0; | ||||
|         dsp::complex_t* histBuf; | ||||
|  | ||||
|         dsp::complex_t* delayBuf; | ||||
|         dsp::complex_t* delayBufInput; | ||||
|  | ||||
|         dsp::complex_t corr = { 0.0f, 0.0f }; | ||||
|  | ||||
|         int samplesSincePeak = 0; | ||||
|         float lastCorr = 0.0f; | ||||
|         float peakCorr = 0.0f; | ||||
|         float peakLCorr = 0.0f; | ||||
|         float peakRCorr = 0.0f; | ||||
|  | ||||
|         // Note only required for DAB | ||||
|         float avgCorr = 0.0f; | ||||
|         float agcRate; | ||||
|         float agcRateInv; | ||||
|     }; | ||||
|  | ||||
|     class FrameFreqSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> { | ||||
|         using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>; | ||||
|     public: | ||||
|         FrameFreqSync() {} | ||||
|  | ||||
|         FrameFreqSync(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { init(in, agcRate); } | ||||
|  | ||||
|         void init(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { | ||||
|             // Allocate buffers | ||||
|             amps = dsp::buffer::alloc<float>(2048); | ||||
|             conjRef = dsp::buffer::alloc<dsp::complex_t>(2048); | ||||
|             corrIn = (dsp::complex_t*)fftwf_alloc_complex(2048); | ||||
|             corrOut = (dsp::complex_t*)fftwf_alloc_complex(2048); | ||||
|  | ||||
|             // Copy the phase reference | ||||
|             memcpy(conjRef, DAB_PHASE_SYM_CONJ, 2048 * sizeof(dsp::complex_t)); | ||||
|  | ||||
|             // Plan the FFT computation | ||||
|             plan = fftwf_plan_dft_1d(2048, (fftwf_complex*)corrIn, (fftwf_complex*)corrOut, FFTW_FORWARD, FFTW_ESTIMATE); | ||||
|  | ||||
|             // Compute the correlation AGC configuration | ||||
|             this->agcRate = agcRate; | ||||
|             agcRateInv = 1.0f - agcRate; | ||||
|              | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|              | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int run() { | ||||
|             int count = base_type::_in->read(); | ||||
|             if (count < 0) { return -1; } | ||||
|  | ||||
|             // Apply frequency shift | ||||
|             lv_32fc_t phase = lv_cmake(1.0f, 0.0f); | ||||
|             lv_32fc_t phaseDelta = lv_cmake(cos(offset), sin(offset)); | ||||
| #if VOLK_VERSION >= 030100 | ||||
|             volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count); | ||||
| #else | ||||
|             volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count); | ||||
| #endif | ||||
|  | ||||
|             // Compute the amplitude amplitude of all samples | ||||
|             volk_32fc_magnitude_32f(amps, (lv_32fc_t*)_in->readBuf, 2048); | ||||
|  | ||||
|             // Compute the average signal level by adding up all values | ||||
|             float level = 0.0f; | ||||
|             volk_32f_accumulator_s32f(&level, amps, 2048); | ||||
|  | ||||
|             // Detect a frame sync condition | ||||
|             if (level < avgLvl * 0.5f) { | ||||
|                 // Reset symbol counter | ||||
|                 sym = 1; | ||||
|  | ||||
|                 // Update the average level | ||||
|                 avgLvl = agcRate*level + agcRateInv*avgLvl; | ||||
|  | ||||
|                 // Flush the input stream and return | ||||
|                 base_type::_in->flush(); | ||||
|                 return count; | ||||
|             } | ||||
|  | ||||
|             // Update the average level | ||||
|             avgLvl = agcRate*level + agcRateInv*avgLvl; | ||||
|  | ||||
|             // Handle phase reference | ||||
|             if (sym == 1) { | ||||
|                 // Output the symbols (DEBUG ONLY) | ||||
|                 memcpy(corrIn, _in->readBuf, 2048 * sizeof(dsp::complex_t)); | ||||
|                 fftwf_execute(plan); | ||||
|                 volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048); | ||||
|                 int outCount = 0; | ||||
|                 dsp::complex_t pi4 = { cos(3.1415926535*0.25), sin(3.1415926535*0.25) }; | ||||
|                 for (int i = -767; i < 768; i++) { | ||||
|                     if (!i) { continue; } | ||||
|                     int cid0 = ((i-1) >= 0) ? (i-1) : 2048+(i-1); | ||||
|                     int cid1 = (i >= 0) ? i : 2048+i;; | ||||
|                     out.writeBuf[outCount++] = pi4 * (corrOut[cid1] * corrOut[cid0].conj()) * (1.0f/(amps[cid0]*amps[cid0])); | ||||
|                 } | ||||
|                 out.swap(outCount); | ||||
|  | ||||
|                 // Multiply the samples with the conjugated phase reference signal | ||||
|                 volk_32fc_x2_multiply_32fc((lv_32fc_t*)corrIn, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)conjRef, 2048); | ||||
|              | ||||
|                 // Compute the FFT of the product | ||||
|                 fftwf_execute(plan); | ||||
|  | ||||
|                 // Compute the amplitude of the bins | ||||
|                 volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048); | ||||
|  | ||||
|                 // Locate highest power bin | ||||
|                 uint32_t peakId; | ||||
|                 volk_32f_index_max_32u(&peakId, amps, 2048); | ||||
|  | ||||
|                 // Obtain the value of the bins next to the peak | ||||
|                 float peakL = amps[(peakId + 2047) % 2048]; | ||||
|                 float peakR = amps[(peakId + 1) % 2048]; | ||||
|  | ||||
|                 // Compute the integer frequency offset | ||||
|                 float offInt = (peakId < 1024) ? (float)peakId : ((float)peakId - 2048.0f); | ||||
|  | ||||
|                 // Compute the frequency offset in rad/samp | ||||
|                 float off = 3.1415926535f * (offInt + ((peakR - peakL) / (peakR + peakL))) * (1.0f / 1024.0f); | ||||
|  | ||||
|                 // Run control loop | ||||
|                 offset -= 0.1f*off; | ||||
|                 flog::debug("Offset: {} Hz, Error: {} Hz, Avg Level: {}", offset * (0.5f/3.1415926535f)*2.048e6, off * (0.5f/3.1415926535f)*2.048e6, avgLvl); | ||||
|             } | ||||
|  | ||||
|             // Increment the symbol counter | ||||
|             sym++; | ||||
|  | ||||
|             // Flush the input stream and return | ||||
|             base_type::_in->flush(); | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         fftwf_plan plan; | ||||
|  | ||||
|         float* amps; | ||||
|         dsp::complex_t* conjRef; | ||||
|         dsp::complex_t* corrIn; | ||||
|         dsp::complex_t* corrOut; | ||||
|  | ||||
|         int sym; | ||||
|         float offset = 0.0f; | ||||
|  | ||||
|         float avgLvl = 0.0f; | ||||
|         float agcRate; | ||||
|         float agcRateInv; | ||||
|     }; | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,163 +0,0 @@ | ||||
| #include <imgui.h> | ||||
| #include <config.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <module.h> | ||||
| #include <filesystem> | ||||
| #include <dsp/stream.h> | ||||
| #include <dsp/buffer/reshaper.h> | ||||
| #include <dsp/multirate/rational_resampler.h> | ||||
| #include <dsp/sink/handler_sink.h> | ||||
| #include <fstream> | ||||
| #include <chrono> | ||||
| #include "dab_dsp.h" | ||||
| #include <gui/widgets/constellation_diagram.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "dab_decoder", | ||||
|     /* Description:     */ "DAB/DAB+ Decoder for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| #define INPUT_SAMPLE_RATE   2.048e6 | ||||
| #define VFO_BANDWIDTH       1.6e6 | ||||
|  | ||||
| class M17DecoderModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     M17DecoderModule(std::string name)  { | ||||
|         this->name = name; | ||||
|  | ||||
|         file = std::ofstream("sync4.f32", std::ios::out | std::ios::binary); | ||||
|  | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|          | ||||
|         config.release(true); | ||||
|  | ||||
|         // Initialize VFO | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true); | ||||
|         vfo->setSnapInterval(250); | ||||
|  | ||||
|         // Initialize DSP here | ||||
|         csync.init(vfo->output, 1e-3, 246e-6, INPUT_SAMPLE_RATE); | ||||
|         ffsync.init(&csync.out); | ||||
|         ns.init(&ffsync.out, handler, this); | ||||
|  | ||||
|         // Start DSO Here | ||||
|         csync.start(); | ||||
|         ffsync.start(); | ||||
|         ns.start(); | ||||
|  | ||||
|         gui::menu.registerEntry(name, menuHandler, this, this); | ||||
|     } | ||||
|  | ||||
|     ~M17DecoderModule() { | ||||
|         gui::menu.removeEntry(name); | ||||
|         // Stop DSP Here | ||||
|         if (enabled) { | ||||
|             csync.stop(); | ||||
|             ffsync.stop(); | ||||
|             ns.stop(); | ||||
|             sigpath::vfoManager.deleteVFO(vfo); | ||||
|         } | ||||
|  | ||||
|         sigpath::sinkManager.unregisterStream(name); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         double bw = gui::waterfall.getBandwidth(); | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true); | ||||
|         vfo->setSnapInterval(250); | ||||
|  | ||||
|         // Set Input of demod here | ||||
|         csync.setInput(vfo->output); | ||||
|  | ||||
|         // Start DSP here | ||||
|         csync.start(); | ||||
|         ffsync.start(); | ||||
|         ns.start(); | ||||
|  | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         // Stop DSP here | ||||
|         csync.stop(); | ||||
|         ffsync.stop(); | ||||
|         ns.stop(); | ||||
|  | ||||
|         sigpath::vfoManager.deleteVFO(vfo); | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static void menuHandler(void* ctx) { | ||||
|         M17DecoderModule* _this = (M17DecoderModule*)ctx; | ||||
|  | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         if (!_this->enabled) { style::beginDisabled(); } | ||||
|  | ||||
|         _this->constDiagram.draw(); | ||||
|  | ||||
|         if (!_this->enabled) { style::endDisabled(); } | ||||
|     } | ||||
|  | ||||
|     std::ofstream file; | ||||
|  | ||||
|     static void handler(dsp::complex_t* data, int count, void* ctx) { | ||||
|         M17DecoderModule* _this = (M17DecoderModule*)ctx; | ||||
|         //_this->file.write((char*)data, count * sizeof(dsp::complex_t)); | ||||
|  | ||||
|         dsp::complex_t* buf = _this->constDiagram.acquireBuffer(); | ||||
|         memcpy(buf, data, 1024 * sizeof(dsp::complex_t)); | ||||
|         _this->constDiagram.releaseBuffer(); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|  | ||||
|     dab::CyclicSync csync; | ||||
|     dab::FrameFreqSync ffsync; | ||||
|     dsp::sink::Handler<dsp::complex_t> ns; | ||||
|  | ||||
|     ImGui::ConstellationDiagram constDiagram; | ||||
|  | ||||
|     // DSP Chain | ||||
|     VFOManager::VFO* vfo; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Create default recording directory | ||||
|     json def = json({}); | ||||
|     config.setPath(core::args["root"].s() + "/dab_decoder_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new M17DecoderModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (M17DecoderModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| @@ -1,34 +0,0 @@ | ||||
| 0123456789 | ||||
| ---  --- | ||||
|  | ||||
| 0*4 | ||||
| 1*5 | ||||
| 2*6 | ||||
|  | ||||
| 1*5 | ||||
| 2*6 = L + 3*7 - 0*4 | ||||
| 3*7 | ||||
|  | ||||
| 2*6 | ||||
| 3*7 = L + 4*8 - 1*5 | ||||
| 4*8 | ||||
|  | ||||
| 3*7 | ||||
| 4*8 = L + 5*9 - 2*6 | ||||
| 5*9 | ||||
|  | ||||
|  | ||||
|  | ||||
| 0*5 | ||||
| 1*6 | ||||
| 2*7 | ||||
|  | ||||
| 1*6 | ||||
| 2*7 | ||||
| 3*8 | ||||
|  | ||||
| 2*7 | ||||
| 3*8 | ||||
| 4*9 | ||||
|  | ||||
| => Use same technique to cache the interpolation results | ||||
| @@ -24,13 +24,12 @@ namespace demod { | ||||
|     class Demodulator { | ||||
|     public: | ||||
|         virtual ~Demodulator() {} | ||||
|         virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) = 0; | ||||
|         virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) = 0; | ||||
|         virtual void start() = 0; | ||||
|         virtual void stop() = 0; | ||||
|         virtual void showMenu() = 0; | ||||
|         virtual void setBandwidth(double bandwidth) = 0; | ||||
|         virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0; | ||||
|         virtual void AFSampRateChanged(double newSR) = 0; | ||||
|         virtual const char* getName() = 0; | ||||
|         virtual double getIFSampleRate() = 0; | ||||
|         virtual double getAFSampleRate() = 0; | ||||
|   | ||||
| @@ -7,13 +7,13 @@ namespace demod { | ||||
|     public: | ||||
|         AM() {} | ||||
|  | ||||
|         AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~AM() { stop(); } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -68,8 +68,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "AM"; } | ||||
|   | ||||
| @@ -7,15 +7,15 @@ namespace demod { | ||||
|     public: | ||||
|         CW() {} | ||||
|  | ||||
|         CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~CW() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             this->_config = config; | ||||
|             this->afbwChangeHandler = afbwChangeHandler; | ||||
| @@ -74,8 +74,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "CW"; } | ||||
|   | ||||
| @@ -7,15 +7,15 @@ namespace demod { | ||||
|     public: | ||||
|         DSB() {} | ||||
|  | ||||
|         DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~DSB() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -61,8 +61,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "DSB"; } | ||||
|   | ||||
| @@ -7,15 +7,15 @@ namespace demod { | ||||
|     public: | ||||
|         LSB() {} | ||||
|  | ||||
|         LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~LSB() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -61,8 +61,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "LSB"; } | ||||
|   | ||||
| @@ -7,13 +7,13 @@ namespace demod { | ||||
|     public: | ||||
|         NFM() {} | ||||
|  | ||||
|         NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~NFM() { stop(); } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             this->_config = config; | ||||
|  | ||||
| @@ -57,8 +57,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "FM"; } | ||||
|   | ||||
| @@ -7,17 +7,18 @@ namespace demod { | ||||
|     public: | ||||
|         RAW() {} | ||||
|  | ||||
|         RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~RAW() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             audioSampleRate = audioSR; | ||||
|             audioSampleRate = 48000; | ||||
|             // TODO: This needs to be selectable | ||||
|  | ||||
|             // Define structure | ||||
|             c2s.init(input); | ||||
| @@ -39,10 +40,6 @@ namespace demod { | ||||
|             c2s.setInput(input); | ||||
|         } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) { | ||||
|             audioSampleRate = newSR; | ||||
|         } | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "RAW"; } | ||||
|   | ||||
| @@ -8,15 +8,15 @@ namespace demod { | ||||
|     public: | ||||
|         USB() {} | ||||
|  | ||||
|         USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~USB() { | ||||
|             stop(); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -62,8 +62,6 @@ namespace demod { | ||||
|  | ||||
|         void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "USB"; } | ||||
|   | ||||
| @@ -16,8 +16,8 @@ namespace demod { | ||||
|     public: | ||||
|         WFM() : diag(0.5, 4096)  {} | ||||
|  | ||||
|         WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) { | ||||
|             init(name, config, input, bandwidth, audioSR); | ||||
|         WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) { | ||||
|             init(name, config, input, bandwidth); | ||||
|         } | ||||
|  | ||||
|         ~WFM() { | ||||
| @@ -25,7 +25,7 @@ namespace demod { | ||||
|             gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler); | ||||
|         } | ||||
|  | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { | ||||
|         void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) { | ||||
|             this->name = name; | ||||
|             _config = config; | ||||
|  | ||||
| @@ -252,8 +252,6 @@ namespace demod { | ||||
|             demod.setInput(input); | ||||
|         } | ||||
|  | ||||
|         void AFSampRateChanged(double newSR) {} | ||||
|  | ||||
|         // ============= INFO ============= | ||||
|  | ||||
|         const char* getName() { return "WFM"; } | ||||
|   | ||||
| @@ -81,30 +81,16 @@ public: | ||||
|         // Initialize audio DSP chain | ||||
|         afChain.init(&dummyAudioStream); | ||||
|  | ||||
|         resamp.init(NULL, 250000.0, 48000.0); | ||||
|         deemp.init(NULL, 50e-6, 48000.0); | ||||
|  | ||||
|         afChain.addBlock(&resamp, true); | ||||
|         afChain.addBlock(&deemp, false); | ||||
|  | ||||
|         // Initialize the sink | ||||
|         srChangeHandler.ctx = this; | ||||
|         srChangeHandler.handler = sampleRateChangeHandler; | ||||
|         stream.init(afChain.out, &srChangeHandler, audioSampleRate); | ||||
|         sigpath::sinkManager.registerStream(name, &stream); | ||||
|         stream = sigpath::streamManager.createStream(name, afChain.out, 48000); | ||||
|  | ||||
|         // Select the demodulator | ||||
|         selectDemodByID((DemodID)selectedDemodID); | ||||
|  | ||||
|         // Start IF chain | ||||
|         ifChain.start(); | ||||
|  | ||||
|         // Start AF chain | ||||
|         afChain.start(); | ||||
|  | ||||
|         // Start stream, the rest was started when selecting the demodulator | ||||
|         stream.start(); | ||||
|  | ||||
|         // Register the menu | ||||
|         gui::menu.registerEntry(name, menuHandler, this, this); | ||||
|  | ||||
| @@ -115,11 +101,10 @@ public: | ||||
|     ~RadioModule() { | ||||
|         core::modComManager.unregisterInterface(name); | ||||
|         gui::menu.removeEntry(name); | ||||
|         stream.stop(); | ||||
|         if (enabled) { | ||||
|             disable(); | ||||
|         } | ||||
|         sigpath::sinkManager.unregisterStream(name); | ||||
|         sigpath::streamManager.destroyStream(stream); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
| @@ -131,9 +116,7 @@ public: | ||||
|             vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler); | ||||
|         } | ||||
|         ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); }); | ||||
|         ifChain.start(); | ||||
|         selectDemodByID((DemodID)selectedDemodID); | ||||
|         afChain.start(); | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
| @@ -141,6 +124,7 @@ public: | ||||
|         ifChain.stop(); | ||||
|         if (selectedDemod) { selectedDemod->stop(); } | ||||
|         afChain.stop(); | ||||
|         stream->stopDSP(); | ||||
|         if (vfo) { sigpath::vfoManager.deleteVFO(vfo); } | ||||
|         vfo = NULL; | ||||
|     } | ||||
| @@ -313,7 +297,7 @@ private: | ||||
|         bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth()); | ||||
|  | ||||
|         // Initialize | ||||
|         demod->init(name, &config, ifChain.out, bw, stream.getSampleRate()); | ||||
|         demod->init(name, &config, ifChain.out, bw); | ||||
|  | ||||
|         return demod; | ||||
|     } | ||||
| @@ -337,22 +321,34 @@ private: | ||||
|     } | ||||
|  | ||||
|     void selectDemod(demod::Demodulator* demod) { | ||||
|         // Stopcurrently selected demodulator and select new | ||||
|         afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|         // Stop the IF chain | ||||
|         ifChain.stop(); | ||||
|  | ||||
|         // Stop the current demodulator | ||||
|         if (selectedDemod) { | ||||
|             selectedDemod->stop(); | ||||
|         } | ||||
|  | ||||
|         // Stop AF chain | ||||
|         afChain.stop(); | ||||
|  | ||||
|         // Stop audio stream's DSP | ||||
|         stream->stopDSP(); | ||||
|  | ||||
|         // Destroy the old demodulator | ||||
|         afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|         if (selectedDemod) { | ||||
|             delete selectedDemod; | ||||
|         } | ||||
|         selectedDemod = demod; | ||||
|  | ||||
|         // Give the demodulator the most recent audio SR | ||||
|         selectedDemod->AFSampRateChanged(audioSampleRate); | ||||
|         // Select the new demodulator | ||||
|         selectedDemod = demod; | ||||
|  | ||||
|         // Set the demodulator's input | ||||
|         selectedDemod->setInput(ifChain.out); | ||||
|  | ||||
|         // Set AF chain's input | ||||
|         afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|         afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|  | ||||
|         // Load config | ||||
|         bandwidth = selectedDemod->getDefaultBandwidth(); | ||||
| @@ -440,21 +436,30 @@ private: | ||||
|         // Configure AF chain | ||||
|         if (postProcEnabled) { | ||||
|             // Configure resampler | ||||
|             afChain.stop(); | ||||
|             resamp.setInSamplerate(selectedDemod->getAFSampleRate()); | ||||
|             setAudioSampleRate(audioSampleRate); | ||||
|             afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|             deemp.setSamplerate(selectedDemod->getAFSampleRate()); | ||||
|  | ||||
|             // Configure deemphasis | ||||
|             setDeemphasisMode(deempModes[deempId]); | ||||
|         } | ||||
|         else { | ||||
|             // Disable everything if post processing is disabled | ||||
|             afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|             afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|         } | ||||
|  | ||||
|         // Update audo samplerate | ||||
|         stream->setSamplerate(selectedDemod->getAFSampleRate()); | ||||
|  | ||||
|         // Start the IF chain | ||||
|         ifChain.start(); | ||||
|  | ||||
|         // Start new demodulator | ||||
|         selectedDemod->start(); | ||||
|  | ||||
|         // Start the AF chain | ||||
|         afChain.start(); | ||||
|  | ||||
|         // Start the audio stream | ||||
|         stream->startDSP(); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -470,37 +475,12 @@ private: | ||||
|         config.release(true); | ||||
|     } | ||||
|  | ||||
|     void setAudioSampleRate(double sr) { | ||||
|         audioSampleRate = sr; | ||||
|         if (!selectedDemod) { return; } | ||||
|         selectedDemod->AFSampRateChanged(audioSampleRate); | ||||
|         if (!postProcEnabled && vfo) { | ||||
|             // If postproc is disabled, IF SR = AF SR | ||||
|             minBandwidth = selectedDemod->getMinBandwidth(); | ||||
|             maxBandwidth = selectedDemod->getMaxBandwidth(); | ||||
|             bandwidth = selectedDemod->getIFSampleRate(); | ||||
|             vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked()); | ||||
|             vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         afChain.stop(); | ||||
|  | ||||
|         // Configure resampler | ||||
|         resamp.setOutSamplerate(audioSampleRate); | ||||
|  | ||||
|         // Configure deemphasis sample rate | ||||
|         deemp.setSamplerate(audioSampleRate); | ||||
|  | ||||
|         afChain.start(); | ||||
|     } | ||||
|  | ||||
|     void setDeemphasisMode(DeemphasisMode mode) { | ||||
|         deempId = deempModes.valueId(mode); | ||||
|         if (!postProcEnabled || !selectedDemod) { return; } | ||||
|         bool deempEnabled = (mode != DEEMP_MODE_NONE); | ||||
|         if (deempEnabled) { deemp.setTau(deempTaus[mode]); } | ||||
|         afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|         afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); }); | ||||
|  | ||||
|         // Save config | ||||
|         config.acquire(); | ||||
| @@ -584,11 +564,6 @@ private: | ||||
|         _this->setBandwidth(newBw); | ||||
|     } | ||||
|  | ||||
|     static void sampleRateChangeHandler(float sampleRate, void* ctx) { | ||||
|         RadioModule* _this = (RadioModule*)ctx; | ||||
|         _this->setAudioSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
|     static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) { | ||||
|         RadioModule* _this = (RadioModule*)ctx; | ||||
|         if (!_this->selectedDemod) { return; } | ||||
| @@ -597,16 +572,14 @@ private: | ||||
|  | ||||
|     static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) { | ||||
|         RadioModule* _this = (RadioModule*)ctx; | ||||
|  | ||||
|         // If no demod is selected, reject the command | ||||
|         if (!_this->selectedDemod) { return; } | ||||
|         if (!_this->enabled || !_this->selectedDemod) { return; } | ||||
|  | ||||
|         // Execute commands | ||||
|         if (code == RADIO_IFACE_CMD_GET_MODE && out) { | ||||
|             int* _out = (int*)out; | ||||
|             *_out = _this->selectedDemodID; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_MODE && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_MODE && in) { | ||||
|             int* _in = (int*)in; | ||||
|             _this->selectDemodByID((DemodID)*_in); | ||||
|         } | ||||
| @@ -614,7 +587,7 @@ private: | ||||
|             float* _out = (float*)out; | ||||
|             *_out = _this->bandwidth; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in) { | ||||
|             float* _in = (float*)in; | ||||
|             if (_this->bandwidthLocked) { return; } | ||||
|             _this->setBandwidth(*_in); | ||||
| @@ -623,7 +596,7 @@ private: | ||||
|             bool* _out = (bool*)out; | ||||
|             *_out = _this->squelchEnabled; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in) { | ||||
|             bool* _in = (bool*)in; | ||||
|             _this->setSquelchEnabled(*_in); | ||||
|         } | ||||
| @@ -631,7 +604,7 @@ private: | ||||
|             float* _out = (float*)out; | ||||
|             *_out = _this->squelchLevel; | ||||
|         } | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in && _this->enabled) { | ||||
|         else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in) { | ||||
|             float* _in = (float*)in; | ||||
|             _this->setSquelchLevel(*_in); | ||||
|         } | ||||
| @@ -645,7 +618,6 @@ private: | ||||
|  | ||||
|     // Handlers | ||||
|     EventHandler<double> onUserChangedBandwidthHandler; | ||||
|     EventHandler<float> srChangeHandler; | ||||
|     EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged; | ||||
|     EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged; | ||||
|  | ||||
| @@ -660,10 +632,9 @@ private: | ||||
|     // Audio chain | ||||
|     dsp::stream<dsp::stereo_t> dummyAudioStream; | ||||
|     dsp::chain<dsp::stereo_t> afChain; | ||||
|     dsp::multirate::RationalResampler<dsp::stereo_t> resamp; | ||||
|     dsp::filter::Deemphasis<dsp::stereo_t> deemp; | ||||
|  | ||||
|     SinkManager::Stream stream; | ||||
|     std::shared_ptr<MasterStream> stream; | ||||
|  | ||||
|     demod::Demodulator* selectedDemod = NULL; | ||||
|  | ||||
|   | ||||
| @@ -1,8 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(ryfi_decoder) | ||||
|  | ||||
| file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| target_include_directories(ryfi_decoder PRIVATE "src/") | ||||
| @@ -1,139 +0,0 @@ | ||||
| #include <imgui.h> | ||||
| #include <config.h> | ||||
| #include <core.h> | ||||
| #include <gui/style.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <module.h> | ||||
| #include <filesystem> | ||||
| #include <dsp/routing/splitter.h> | ||||
| #include <dsp/buffer/reshaper.h> | ||||
| #include <dsp/sink/handler_sink.h> | ||||
| #include <gui/widgets/folder_select.h> | ||||
| #include <gui/widgets/constellation_diagram.h> | ||||
| #include "ryfi/receiver.h" | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "ryfi_decoder", | ||||
|     /* Description:     */ "RyFi decoder for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| #define INPUT_BANDWIDTH     600e3 | ||||
| #define INPUT_SAMPLE_RATE   1000e3 | ||||
| #define INPUT_BAUDRATE      500e3 | ||||
|  | ||||
| #define SYMBOL_DIAG_RATE    30 | ||||
| #define SYMBOL_DIAG_COUNT   1024 | ||||
|  | ||||
| class RyFiDecoderModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     RyFiDecoderModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true); | ||||
|         rx.init(vfo->output, INPUT_BAUDRATE, INPUT_SAMPLE_RATE); | ||||
|         reshape.init(rx.softOut, SYMBOL_DIAG_COUNT, (INPUT_BAUDRATE / SYMBOL_DIAG_RATE) - SYMBOL_DIAG_COUNT); | ||||
|         symSink.init(&reshape.out, symSinkHandler, this); | ||||
|         rx.onPacket.bind(&RyFiDecoderModule::packetHandler, this); | ||||
|  | ||||
|         rx.start(); | ||||
|         reshape.start(); | ||||
|         symSink.start(); | ||||
|  | ||||
|         gui::menu.registerEntry(name, menuHandler, this, this); | ||||
|     } | ||||
|  | ||||
|     ~RyFiDecoderModule() { | ||||
|         rx.stop(); | ||||
|         reshape.stop(); | ||||
|         symSink.stop(); | ||||
|         sigpath::vfoManager.deleteVFO(vfo); | ||||
|         gui::menu.removeEntry(name); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         double bw = gui::waterfall.getBandwidth(); | ||||
|         vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true); | ||||
|  | ||||
|         rx.setInput(vfo->output); | ||||
|  | ||||
|         rx.start(); | ||||
|         reshape.start(); | ||||
|         symSink.start(); | ||||
|  | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         rx.stop(); | ||||
|         reshape.stop(); | ||||
|         symSink.stop(); | ||||
|  | ||||
|         sigpath::vfoManager.deleteVFO(vfo); | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void packetHandler(ryfi::Packet pkt) { | ||||
|         flog::debug("Got a {} byte packet!", pkt.size()); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx; | ||||
|  | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         if (!_this->enabled) { style::beginDisabled(); } | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         _this->constDiagram.draw(); | ||||
|  | ||||
|         if (!_this->enabled) { style::endDisabled(); } | ||||
|     } | ||||
|  | ||||
|     static void symSinkHandler(dsp::complex_t* data, int count, void* ctx) { | ||||
|         RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx; | ||||
|  | ||||
|         dsp::complex_t* buf = _this->constDiagram.acquireBuffer(); | ||||
|         memcpy(buf, data, 1024 * sizeof(dsp::complex_t)); | ||||
|         _this->constDiagram.releaseBuffer(); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|  | ||||
|     // DSP Chain | ||||
|     VFOManager::VFO* vfo; | ||||
|     ryfi::Receiver rx; | ||||
|     dsp::buffer::Reshaper<dsp::complex_t> reshape; | ||||
|     dsp::sink::Handler<dsp::complex_t> symSink; | ||||
|  | ||||
|     ImGui::ConstellationDiagram constDiagram; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|  | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new RyFiDecoderModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (RyFiDecoderModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|  | ||||
| } | ||||
| @@ -1,74 +0,0 @@ | ||||
| #include "conv_codec.h" | ||||
|  | ||||
| namespace ryfi { | ||||
|     ConvEncoder::ConvEncoder(dsp::stream<uint8_t>* in) { | ||||
|         // Create the convolutional encoder instance | ||||
|         conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial); | ||||
|          | ||||
|         // Init the base class | ||||
|         base_type::init(in); | ||||
|     } | ||||
|  | ||||
|     ConvEncoder::~ConvEncoder() { | ||||
|         // Destroy the convolutional encoder instance | ||||
|         correct_convolutional_destroy(conv); | ||||
|     } | ||||
|  | ||||
|     int ConvEncoder::encode(const uint8_t* in, uint8_t* out, int count) { | ||||
|         // Run convolutional encoder on the data | ||||
|         return correct_convolutional_encode(conv, in, count, out); | ||||
|     } | ||||
|  | ||||
|     int ConvEncoder::run() { | ||||
|         int count = base_type::_in->read(); | ||||
|         if (count < 0) { return -1; } | ||||
|  | ||||
|         count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count); | ||||
|  | ||||
|         base_type::_in->flush(); | ||||
|         if (!out.swap(count)) { return -1; } | ||||
|         return count; | ||||
|     } | ||||
|  | ||||
|     ConvDecoder::ConvDecoder(dsp::stream<dsp::complex_t>* in) { | ||||
|         // Create the convolutional encoder instance | ||||
|         conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial); | ||||
|  | ||||
|         // Allocate the soft symbol buffer | ||||
|         soft = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE); | ||||
|          | ||||
|         // Init the base class | ||||
|         base_type::init(in); | ||||
|     } | ||||
|  | ||||
|     ConvDecoder::~ConvDecoder() { | ||||
|         // Destroy the convolutional encoder instance | ||||
|         correct_convolutional_destroy(conv); | ||||
|  | ||||
|         // Free the soft symbol buffer | ||||
|         dsp::buffer::free(soft); | ||||
|     } | ||||
|  | ||||
|     int ConvDecoder::decode(const dsp::complex_t* in, uint8_t* out, int count) { | ||||
|         // Convert to uint8 | ||||
|         const float* _in = (const float*)in; | ||||
|         count *= 2; | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             soft[i] = std::clamp<int>((_in[i] * 127.0f) + 128.0f, 0, 255); | ||||
|         } | ||||
|          | ||||
|         // Run convolutional decoder on the data | ||||
|         return correct_convolutional_decode_soft(conv, soft, count, out); | ||||
|     } | ||||
|  | ||||
|     int ConvDecoder::run() { | ||||
|         int count = base_type::_in->read(); | ||||
|         if (count < 0) { return -1; } | ||||
|  | ||||
|         count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count); | ||||
|  | ||||
|         base_type::_in->flush(); | ||||
|         if (!out.swap(count)) { return -1; } | ||||
|         return count; | ||||
|     } | ||||
| } | ||||
| @@ -1,71 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
| #include "dsp/processor.h" | ||||
|  | ||||
| extern "C" { | ||||
|     #include "correct.h" | ||||
| } | ||||
|  | ||||
| namespace ryfi { | ||||
|     /** | ||||
|      * RyFi Convolutional Encoder. | ||||
|     */ | ||||
|     class ConvEncoder : public dsp::Processor<uint8_t, uint8_t> { | ||||
|         using base_type = dsp::Processor<uint8_t, uint8_t>; | ||||
|     public: | ||||
|         /** | ||||
|          * Create a convolutional encoder specifying an input stream. | ||||
|          * @param in Input stream. | ||||
|         */ | ||||
|         ConvEncoder(dsp::stream<uint8_t>* in = NULL); | ||||
|  | ||||
|         // Destructor | ||||
|         ~ConvEncoder(); | ||||
|  | ||||
|         /** | ||||
|          * Encode data. | ||||
|          * @param in Input bytes. | ||||
|          * @param out Output bits. | ||||
|          * @param count Number of input bytes. | ||||
|          * @return Number of output bits. | ||||
|         */ | ||||
|         int encode(const uint8_t* in, uint8_t* out, int count); | ||||
|  | ||||
|     private: | ||||
|         int run(); | ||||
|  | ||||
|         correct_convolutional* conv; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * RyFi Convolutional Decoder. | ||||
|     */ | ||||
|     class ConvDecoder : public dsp::Processor<dsp::complex_t, uint8_t> { | ||||
|         using base_type = dsp::Processor<dsp::complex_t, uint8_t>; | ||||
|     public: | ||||
|         /** | ||||
|          * Create a convolutional encoder specifying an input stream. | ||||
|          * @param in Input stream. | ||||
|         */ | ||||
|         ConvDecoder(dsp::stream<dsp::complex_t>* in = NULL); | ||||
|  | ||||
|         // Destructor | ||||
|         ~ConvDecoder(); | ||||
|  | ||||
|         /** | ||||
|          * Decode soft symbols. | ||||
|          * @param in Input soft symbols. | ||||
|          * @param out Output bytes. | ||||
|          * @param count Number of input bytes. | ||||
|          * @return Number of output bits. | ||||
|         */ | ||||
|         int decode(const dsp::complex_t* in, uint8_t* out, int count); | ||||
|  | ||||
|     private: | ||||
|         int run(); | ||||
|  | ||||
|         correct_convolutional* conv; | ||||
|         uint8_t* soft = NULL; | ||||
|     }; | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| #include "frame.h" | ||||
|  | ||||
| namespace ryfi { | ||||
|     int Frame::serialize(uint8_t* bytes) const { | ||||
|         // Write the counter | ||||
|         bytes[0] = (counter >> 8) & 0xFF; | ||||
|         bytes[1] = counter & 0xFF; | ||||
|  | ||||
|         // Write the first packet pointer | ||||
|         bytes[2] = (firstPacket >> 8) & 0xFF; | ||||
|         bytes[3] = firstPacket & 0xFF; | ||||
|  | ||||
|         // Write the last packet pointer | ||||
|         bytes[4] = (lastPacket >> 8) & 0xFF; | ||||
|         bytes[5] = lastPacket & 0xFF; | ||||
|  | ||||
|         // Write the data | ||||
|         memcpy(&bytes[6], content, FRAME_DATA_SIZE); | ||||
|  | ||||
|         // Return the length of a serialized frame | ||||
|         return FRAME_SIZE; | ||||
|     } | ||||
|  | ||||
|     void Frame::deserialize(const uint8_t* bytes, Frame& frame) { | ||||
|         // Read the counter | ||||
|         frame.counter = (((uint16_t)bytes[0]) << 8) | ((uint16_t)bytes[1]); | ||||
|  | ||||
|         // Read the first packet pointer | ||||
|         frame.firstPacket = (((uint16_t)bytes[2]) << 8) | ((uint16_t)bytes[3]); | ||||
|  | ||||
|         // Read the last packet pointer | ||||
|         frame.lastPacket = (((uint16_t)bytes[4]) << 8) | ((uint16_t)bytes[5]); | ||||
|  | ||||
|         // Read the data | ||||
|         memcpy(frame.content, &bytes[6], FRAME_DATA_SIZE); | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include "rs_codec.h" | ||||
|  | ||||
| namespace ryfi { | ||||
|     enum PacketOffset { | ||||
|         PKT_OFFS_NONE   = 0xFFFF | ||||
|     }; | ||||
|      | ||||
|     struct Frame { | ||||
|         /** | ||||
|          * Serialize the frame to bytes. | ||||
|          * @param bytes Buffer to write the serialized frame to. | ||||
|         */ | ||||
|         int serialize(uint8_t* bytes) const; | ||||
|  | ||||
|         /** | ||||
|          * Deserialize a frame from bytes. | ||||
|          * @param bytes Buffer to deserialize the frame from. | ||||
|          * @param frame Object that will contain the deserialize frame. | ||||
|         */ | ||||
|         static void deserialize(const uint8_t* bytes, Frame& frame); | ||||
|  | ||||
|         // Size of a serialized frame | ||||
|         static inline const int FRAME_SIZE      = RS_BLOCK_DEC_SIZE*RS_BLOCK_COUNT; | ||||
|  | ||||
|         // Size of the data area of the frame | ||||
|         static inline const int FRAME_DATA_SIZE = FRAME_SIZE - 6; | ||||
|  | ||||
|         // Steadily increasing counter. | ||||
|         uint16_t counter = 0; | ||||
|  | ||||
|         // Byte offset of the first packet in the frame. | ||||
|         uint16_t firstPacket = 0; | ||||
|  | ||||
|         // Byte offset of the last packet in the frame. | ||||
|         uint16_t lastPacket = 0; | ||||
|  | ||||
|         // Data area of the frame. | ||||
|         uint8_t content[FRAME_DATA_SIZE]; | ||||
|     }; | ||||
| } | ||||
| @@ -1,137 +0,0 @@ | ||||
| #include "framing.h" | ||||
|  | ||||
| namespace ryfi { | ||||
|     dsp::complex_t QPSK_SYMBOLS[4] = { | ||||
|         { -0.070710678118f, -0.070710678118f }, | ||||
|         { -0.070710678118f,  0.070710678118f }, | ||||
|         {  0.070710678118f, -0.070710678118f }, | ||||
|         {  0.070710678118f,  0.070710678118f }, | ||||
|     }; | ||||
|  | ||||
|     Framer::Framer(dsp::stream<uint8_t>* in) { | ||||
|         // Generate the sync symbols | ||||
|         int k = 0; | ||||
|         for (int i = 62; i >= 0; i -= 2) { | ||||
|             syncSyms[k++] = QPSK_SYMBOLS[(SYNC_WORD >> i) & 0b11]; | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // Initialize base class | ||||
|         base_type::init(in); | ||||
|     } | ||||
|  | ||||
|     int Framer::encode(const uint8_t* in, dsp::complex_t* out, int count) { | ||||
|         // Copy sync symbols | ||||
|         memcpy(out, syncSyms, SYNC_SYMS*sizeof(dsp::complex_t)); | ||||
|  | ||||
|         // Modulate the rest of the bits | ||||
|         dsp::complex_t* dataOut = &out[SYNC_SYMS]; | ||||
|         int dataSyms = count / 2; | ||||
|         for (int i = 0; i < dataSyms; i++) { | ||||
|             uint8_t bits = (in[i >> 2] >> (6 - 2*(i & 0b11))) & 0b11; | ||||
|             dataOut[i] = QPSK_SYMBOLS[bits]; | ||||
|         } | ||||
|  | ||||
|         // Compute and return the total number of symbols | ||||
|         return SYNC_SYMS + dataSyms; | ||||
|     } | ||||
|  | ||||
|     int Framer::run() { | ||||
|         int count = base_type::_in->read(); | ||||
|         if (count < 0) { return -1; } | ||||
|  | ||||
|         count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count); | ||||
|  | ||||
|         base_type::_in->flush(); | ||||
|         if (!out.swap(count)) { return -1; } | ||||
|         return count; | ||||
|     } | ||||
|  | ||||
|     Deframer::Deframer(dsp::stream<dsp::complex_t> *in) { | ||||
|         // Compute sync word rotations | ||||
|         //   0: 00 01 11 10 | ||||
|         //  90: 10 00 01 11 | ||||
|         // 180: 11 10 00 01 | ||||
|         // 270: 01 11 10 00 | ||||
|  | ||||
|         // For 0 and 180 it's the sync and its complement | ||||
|         syncRots[ROT_0_DEG] = SYNC_WORD; | ||||
|         syncRots[ROT_180_DEG] = ~SYNC_WORD; | ||||
|          | ||||
|         // For 90 and 270 its the quadrature and its complement | ||||
|         uint64_t quad; | ||||
|         for (int i = 62; i >= 0; i -= 2) { | ||||
|             // Get the symbol | ||||
|             uint8_t sym = (SYNC_WORD >> i) & 0b11; | ||||
|  | ||||
|             // Rotate it 90 degrees | ||||
|             uint8_t rsym; | ||||
|             switch (sym) { | ||||
|                 case 0b00:  rsym = 0b10; break; | ||||
|                 case 0b01:  rsym = 0b00; break; | ||||
|                 case 0b11:  rsym = 0b01; break; | ||||
|                 case 0b10:  rsym = 0b11; break; | ||||
|             } | ||||
|  | ||||
|             // Push it into the quadrature | ||||
|             quad = (quad << 2) | rsym; | ||||
|         } | ||||
|         syncRots[ROT_90_DEG] = quad; | ||||
|         syncRots[ROT_270_DEG] = ~quad; | ||||
|  | ||||
|         base_type::init(in); | ||||
|     } | ||||
|  | ||||
|     int Deframer::run() { | ||||
|         int count = base_type::_in->read(); | ||||
|         if (count < 0) { return -1; } | ||||
|  | ||||
|         dsp::complex_t* in = base_type::_in->readBuf; | ||||
|  | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             if (recv) { | ||||
|                 // Copy the symbol to the output and rotate it approprieate | ||||
|                 base_type::out.writeBuf[outCount++] = in[i] * symRot; | ||||
|  | ||||
|                 // Check if we're done receiving the frame, send it out | ||||
|                 if (!(--recv)) { | ||||
|                     if (!base_type::out.swap(outCount)) { | ||||
|                         base_type::_in->flush(); | ||||
|                         return -1; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 // Get the raw symbol | ||||
|                 dsp::complex_t fsym = in[i]; | ||||
|  | ||||
|                 // Decode the symbol | ||||
|                 uint8_t sym = ((fsym.re > 0) ? 0b10 : 0b00) | ((fsym.im > 0) ? 0b01 : 0b00); | ||||
|  | ||||
|                 // Push it to the shift register | ||||
|                 shift = (shift << 2) | sym; | ||||
|  | ||||
|                 // Find the rotation starting with the last known one | ||||
|                 for (int i = 0; i < 4; i++) { | ||||
|                     // Get the test rotation | ||||
|                     int testRot = (knownRot+i) & 0b11; | ||||
|  | ||||
|                     // Check if the hamming distance is close enough | ||||
|                     int dist; | ||||
|                     if (distance(shift, syncRots[testRot]) < 6) { | ||||
|                         // Save the new rotation | ||||
|                         knownRot = testRot; | ||||
|  | ||||
|                         // Start reading in symbols for the frame | ||||
|                         symRot = symRots[knownRot]; | ||||
|                         recv = 8168; // TODO: Don't hardcode! | ||||
|                         outCount = 0; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         base_type::_in->flush(); | ||||
|         return count; | ||||
|     } | ||||
| } | ||||
| @@ -1,87 +0,0 @@ | ||||
| #pragma once | ||||
| #include "dsp/processor.h" | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| namespace ryfi { | ||||
|     // Synchronization word. | ||||
|     inline const uint64_t SYNC_WORD = 0x341CC540819D8963; | ||||
|  | ||||
|     // Number of synchronization bits. | ||||
|     inline const int SYNC_BITS      = 64; | ||||
|  | ||||
|     // Number of synchronization symbols. | ||||
|     inline const int SYNC_SYMS      = SYNC_BITS / 2; | ||||
|  | ||||
|     // Possible constellation rotations | ||||
|     enum { | ||||
|         ROT_0_DEG       = 0, | ||||
|         ROT_90_DEG      = 1, | ||||
|         ROT_180_DEG     = 2, | ||||
|         ROT_270_DEG     = 3 | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * RyFi Framer. | ||||
|     */ | ||||
|     class Framer : public dsp::Processor<uint8_t, dsp::complex_t> { | ||||
|         using base_type = dsp::Processor<uint8_t, dsp::complex_t>; | ||||
|     public: | ||||
|         /** | ||||
|          * Create a framer specifying an input stream. | ||||
|          * @param in Input stream. | ||||
|         */ | ||||
|         Framer(dsp::stream<uint8_t>* in = NULL); | ||||
|  | ||||
|         /** | ||||
|          * Encode a frame to symbols adding a sync word. | ||||
|         */ | ||||
|         int encode(const uint8_t* in, dsp::complex_t* out, int count); | ||||
|  | ||||
|     private: | ||||
|         int run(); | ||||
|  | ||||
|         dsp::complex_t syncSyms[SYNC_SYMS]; | ||||
|     }; | ||||
|  | ||||
|     class Deframer : public dsp::Processor<dsp::complex_t, dsp::complex_t> { | ||||
|         using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>; | ||||
|     public: | ||||
|         /** | ||||
|          * Create a deframer specifying an input stream. | ||||
|          * @param in Input stream. | ||||
|         */ | ||||
|         Deframer(dsp::stream<dsp::complex_t> *in = NULL); | ||||
|  | ||||
|     private: | ||||
|         int run(); | ||||
|  | ||||
|         inline static constexpr int distance(uint64_t a, uint64_t b) { | ||||
|             int dist = 0; | ||||
|             for (int i = 0; i < 64; i++) { | ||||
|                 dist += ((a & 1) != (b & 1)); | ||||
|                 a >>= 1; | ||||
|                 b >>= 1; | ||||
|             } | ||||
|             return dist; | ||||
|         } | ||||
|  | ||||
|         // Frame reading counters | ||||
|         int recv = 0; | ||||
|         int outCount = 0; | ||||
|  | ||||
|         // Rotation handling | ||||
|         int knownRot = 0; | ||||
|         uint64_t syncRots[4]; | ||||
|         dsp::complex_t symRot; | ||||
|         const dsp::complex_t symRots[4] = { | ||||
|             {  1.0f,  0.0f }, //   0 deg | ||||
|             {  0.0f, -1.0f }, //  90 deg | ||||
|             { -1.0f,  0.0f }, // 180 deg | ||||
|             {  0.0f,  1.0f }, // 270 deg | ||||
|         }; | ||||
|  | ||||
|         // Shift register | ||||
|         uint64_t shift; | ||||
|     }; | ||||
| } | ||||
| @@ -1,126 +0,0 @@ | ||||
| #include "packet.h" | ||||
| #include "string.h" | ||||
| #include <stdexcept> | ||||
|  | ||||
| namespace ryfi { | ||||
|     Packet::Packet() {} | ||||
|  | ||||
|     Packet::Packet(uint8_t* content, int size) { | ||||
|         // Check that the size isn't too large | ||||
|         if (size > MAX_CONTENT_SIZE) { | ||||
|             throw std::runtime_error("Content size is too large to fit in a packet"); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         // Allocate the buffer | ||||
|         allocate(size); | ||||
|  | ||||
|         // Copy over the content | ||||
|         memcpy(_content, content, size); | ||||
|     } | ||||
|  | ||||
|     Packet::Packet(const Packet& b) { | ||||
|         // Reallocate the buffer | ||||
|         allocate(b._size); | ||||
|  | ||||
|         // Copy over the content | ||||
|         memcpy(_content, b._content, b._size); | ||||
|     } | ||||
|  | ||||
|     Packet::Packet(Packet&& b) { | ||||
|         // Move members | ||||
|         _content = b._content; | ||||
|         _size = b._size; | ||||
|  | ||||
|         // Destroy old object | ||||
|         b._content = NULL; | ||||
|         b._size = 0; | ||||
|     } | ||||
|  | ||||
|     Packet::~Packet() { | ||||
|         // Delete the content | ||||
|         if (_content) { delete[] _content; } | ||||
|     } | ||||
|  | ||||
|     Packet& Packet::operator=(const Packet& b) { | ||||
|         // Reallocate the buffer | ||||
|         allocate(b._size); | ||||
|  | ||||
|         // Copy over the content | ||||
|         memcpy(_content, b._content, b._size); | ||||
|  | ||||
|         // Return self | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
|     Packet& Packet::operator=(Packet&& b) { | ||||
|         // Move members | ||||
|         _content = b._content; | ||||
|         _size = b._size; | ||||
|  | ||||
|         // Destroy old object | ||||
|         b._content = NULL; | ||||
|         b._size = 0; | ||||
|  | ||||
|         // Return self | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
|     Packet::operator bool() const { | ||||
|         return _size > 0; | ||||
|     } | ||||
|  | ||||
|     int Packet::size() const { | ||||
|         // Return the size | ||||
|         return _size; | ||||
|     } | ||||
|  | ||||
|     const uint8_t* Packet::data() const { | ||||
|         // Return the size | ||||
|         return _content; | ||||
|     } | ||||
|  | ||||
|     void Packet::setContent(uint8_t* content, int size) { | ||||
|         // Check that the size isn't too large | ||||
|         if (size > MAX_CONTENT_SIZE) { | ||||
|             throw std::runtime_error("Content size is too large to fit in a packet"); | ||||
|         } | ||||
|          | ||||
|         // Reallocate the buffer | ||||
|         allocate(size); | ||||
|  | ||||
|         // Copy over the content | ||||
|         memcpy(_content, content, size); | ||||
|     } | ||||
|  | ||||
|     int Packet::serializedSize() const { | ||||
|         // Two size bytes + Size of the content | ||||
|         return _size + 2; | ||||
|     } | ||||
|  | ||||
|     int Packet::serialize(uint8_t* bytes) const { | ||||
|         // Write the size in big-endian | ||||
|         bytes[0] = (_size >> 8) & 0xFF; | ||||
|         bytes[1] = _size & 0xFF; | ||||
|  | ||||
|         // Copy the content of the packet | ||||
|         memcpy(&bytes[2], _content, _size); | ||||
|  | ||||
|         // Return the serialized size | ||||
|         return serializedSize(); | ||||
|     } | ||||
|  | ||||
|     void Packet::allocate(int newSize) { | ||||
|         // If the size hasn't changed, do nothing | ||||
|         if (newSize == _size) { return; } | ||||
|  | ||||
|         // Free the old buffer | ||||
|         if (_content) { delete[] _content; }; | ||||
|  | ||||
|         // Update the size | ||||
|         _size = newSize; | ||||
|  | ||||
|         // Allocate the buffer | ||||
|         _content = new uint8_t[newSize]; | ||||
|     } | ||||
| } | ||||
| @@ -1,89 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| namespace ryfi { | ||||
|     /** | ||||
|      * RyFi Protocol Packet. | ||||
|     */ | ||||
|     class Packet { | ||||
|     public: | ||||
|         // Default constructor | ||||
|         Packet(); | ||||
|  | ||||
|         /** | ||||
|          * Create a packet from its content. | ||||
|          * @param content Content of the packet. | ||||
|          * @param size Number of bytes of content. | ||||
|         */ | ||||
|         Packet(uint8_t* content, int size); | ||||
|  | ||||
|         // Copy constructor | ||||
|         Packet(const Packet& b); | ||||
|  | ||||
|         // Move constructor | ||||
|         Packet(Packet&& b); | ||||
|  | ||||
|         // Destructor | ||||
|         ~Packet(); | ||||
|  | ||||
|         // Copy assignment operator | ||||
|         Packet& operator=(const Packet& b); | ||||
|  | ||||
|         // Move assignment operator | ||||
|         Packet& operator=(Packet&& b); | ||||
|  | ||||
|         // Cast to bool operator | ||||
|         operator bool() const; | ||||
|  | ||||
|         /** | ||||
|          * Get the size of the content of the packet. | ||||
|          * @return Size of the content of the packet. | ||||
|         */ | ||||
|         int size() const; | ||||
|  | ||||
|         /** | ||||
|          * Get the content of the packet. The pointer is only valid until reallocation or deletion. | ||||
|          * @return Content of the packet. | ||||
|         */ | ||||
|         const uint8_t* data() const; | ||||
|  | ||||
|         /** | ||||
|          * Set the content of the packet. | ||||
|          * @param content Content of the packet. | ||||
|          * @param size Number of bytes of content. | ||||
|         */ | ||||
|         void setContent(uint8_t* content, int size); | ||||
|  | ||||
|         /** | ||||
|          * Get the size of the serialized packet. | ||||
|          * @return Size of the serialized packet. | ||||
|         */ | ||||
|         int serializedSize() const; | ||||
|  | ||||
|         /** | ||||
|          * Serialize the packet to bytes. | ||||
|          * @param bytes Buffer to which to write the serialized packet. | ||||
|          * @return Size of the serialized packet. | ||||
|         */ | ||||
|         int serialize(uint8_t* bytes) const; | ||||
|  | ||||
|         /** | ||||
|          * Deserialize a packet from bytes. | ||||
|          * TODO | ||||
|         */ | ||||
|         static bool deserialize(uint8_t* bytes, int size, Packet& pkt); | ||||
|  | ||||
|         // Maximum size of the content of the packet. | ||||
|         static inline const int MAX_CONTENT_SIZE    = 0xFFFF; | ||||
|  | ||||
|         // Maximum size of the serialized packet. | ||||
|         static inline const int MAX_SERIALIZED_SIZE = MAX_CONTENT_SIZE + 2; | ||||
|  | ||||
|     private: | ||||
|         void allocate(int newSize); | ||||
|  | ||||
|         uint8_t* _content = NULL; | ||||
|         int _size = 0; | ||||
|     }; | ||||
| } | ||||
| @@ -1,194 +0,0 @@ | ||||
| #include "receiver.h" | ||||
|  | ||||
| #include "utils/flog.h" | ||||
|  | ||||
| namespace ryfi { | ||||
|     Receiver::Receiver() {} | ||||
|  | ||||
|     Receiver::Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) { | ||||
|         init(in, baudrate, samplerate); | ||||
|     } | ||||
|  | ||||
|     Receiver::~Receiver() { | ||||
|         // Stop everything | ||||
|         stop(); | ||||
|     } | ||||
|  | ||||
|     void Receiver::init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) { | ||||
|         // Initialize the DSP | ||||
|         demod.init(in, baudrate, samplerate, 31, 0.6, 0.1f, 0.005f, 1e-6, 0.01); | ||||
|         doubler.init(&demod.out); | ||||
|         softOut = &doubler.outA; | ||||
|         deframer.setInput(&doubler.outB); | ||||
|         conv.setInput(&deframer.out); | ||||
|         rs.setInput(&conv.out); | ||||
|     } | ||||
|  | ||||
|     void Receiver::setInput(dsp::stream<dsp::complex_t>* in) { | ||||
|         demod.setInput(in); | ||||
|     } | ||||
|  | ||||
|     void Receiver::start() { | ||||
|         // Do nothing if already running | ||||
|         if (running) { return; } | ||||
|  | ||||
|         // Start the worker thread | ||||
|         workerThread = std::thread(&Receiver::worker, this); | ||||
|  | ||||
|         // Start the DSP | ||||
|         demod.start(); | ||||
|         doubler.start(); | ||||
|         deframer.start(); | ||||
|         conv.start(); | ||||
|         rs.start(); | ||||
|  | ||||
|         // Update the running state | ||||
|         running = true; | ||||
|     } | ||||
|  | ||||
|     void Receiver::stop() { | ||||
|         // Do nothing if not running | ||||
|         if (!running) { return; } | ||||
|  | ||||
|         // Stop the worker thread | ||||
|         rs.out.stopReader(); | ||||
|         if (workerThread.joinable()) { workerThread.join(); } | ||||
|         rs.out.clearReadStop(); | ||||
|  | ||||
|         // Stop the DSP | ||||
|         demod.stop(); | ||||
|         doubler.stop(); | ||||
|         deframer.stop(); | ||||
|         conv.stop(); | ||||
|         rs.stop(); | ||||
|  | ||||
|         // Update the running state | ||||
|         running = false; | ||||
|     } | ||||
|      | ||||
|     void Receiver::worker() { | ||||
|         Frame frame; | ||||
|         uint16_t lastCounter = 0; | ||||
|         uint8_t* pktBuffer = new uint8_t[Packet::MAX_CONTENT_SIZE]; | ||||
|         int pktExpected = 0; | ||||
|         int pktRead = 0; | ||||
|         int valid = 0; | ||||
|  | ||||
|         while (true) { | ||||
|             // Read a frame | ||||
|             int count = rs.out.read(); | ||||
|             if (count <= 0) { break; } | ||||
|  | ||||
|             // Deserialize the frame | ||||
|             Frame::deserialize(rs.out.readBuf, frame); | ||||
|             valid++; | ||||
|  | ||||
|             // Flush the stream | ||||
|             rs.out.flush(); | ||||
|  | ||||
|             //flog::info("Frame[{}]: FirstPacket={}, LastPacket={}", frame.counter, frame.firstPacket, frame.lastPacket); | ||||
|  | ||||
|             // Compute the expected frame counter | ||||
|             uint16_t expectedCounter = lastCounter + 1; | ||||
|             lastCounter = frame.counter; | ||||
|  | ||||
|             // If the frames aren't consecutive | ||||
|             int frameRead = 0; | ||||
|             if (frame.counter != expectedCounter) { | ||||
|                 flog::warn("Lost at least {} frames after {} valid frames", ((int)frame.counter - (int)expectedCounter + 0x10000) % 0x10000, valid); | ||||
|  | ||||
|                 // Cancel the partial packet if there was one | ||||
|                 pktExpected = 0; | ||||
|                 pktRead = 0; | ||||
|                 valid = 1; | ||||
|  | ||||
|                 // If this frame is not an idle frame or continuation frame | ||||
|                 if (frame.firstPacket != PKT_OFFS_NONE) { | ||||
|                     // If the offset of the first packet is not plausible | ||||
|                     if (frame.firstPacket > Frame::FRAME_DATA_SIZE-2) { | ||||
|                         flog::warn("Packet had non-plausible offset: {}", frameRead); | ||||
|  | ||||
|                         // Skip the frame | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     // Skip to the end of the packet | ||||
|                     frameRead = frame.firstPacket; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // If there is no partial packet and the frame doesn't contain a packet start, skip it | ||||
|             if (!pktExpected && frame.firstPacket == PKT_OFFS_NONE) { continue; } | ||||
|  | ||||
|             // Extract packets from the frame | ||||
|             bool firstPacket = true; | ||||
|             bool lastPacket = false; | ||||
|             while (frameRead < Frame::FRAME_DATA_SIZE) { | ||||
|                 // If there is a partial packet read as much as possible from it | ||||
|                 if (pktExpected) { | ||||
|                     // Compute how many bytes of the packet are available in the frame | ||||
|                     int readable = std::min<int>(pktExpected - pktRead, Frame::FRAME_DATA_SIZE - frameRead); | ||||
|                     //flog::debug("Reading {} bytes", readable); | ||||
|  | ||||
|                     // Write them to the packet | ||||
|                     memcpy(&pktBuffer[pktRead], &frame.content[frameRead], readable); | ||||
|                     pktRead += readable; | ||||
|                     frameRead += readable; | ||||
|  | ||||
|                     // If the packet is read entirely | ||||
|                     if (pktRead >= pktExpected) { | ||||
|                         // Create the packet object | ||||
|                         Packet pkt(pktBuffer, pktExpected); | ||||
|  | ||||
|                         // Send off the packet | ||||
|                         onPacket(pkt); | ||||
|  | ||||
|                         // Prepare for the next packet | ||||
|                         pktRead = 0; | ||||
|                         pktExpected = 0; | ||||
|  | ||||
|                         // If this was the last packet of the frame | ||||
|                         if (lastPacket || frame.firstPacket == PKT_OFFS_NONE) { | ||||
|                             // Skip the rest of the frame | ||||
|                             frameRead = Frame::FRAME_DATA_SIZE; | ||||
|                             continue; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     // Go to next packet | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 // If the packet offset is not plausible | ||||
|                 if (Frame::FRAME_DATA_SIZE - frameRead < 2) { | ||||
|                     flog::warn("Packet had non-plausible offset: {}", frameRead); | ||||
|  | ||||
|                     // Skip the rest of the frame and the packet | ||||
|                     frameRead = Frame::FRAME_DATA_SIZE; | ||||
|                     pktExpected = 0; | ||||
|                     pktRead = 0; | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 // If this is the first packet, use the frame info to skip possible left over data | ||||
|                 if (firstPacket) { | ||||
|                     frameRead = frame.firstPacket; | ||||
|                     firstPacket = false; | ||||
|                 } | ||||
|  | ||||
|                 // Check if this is the last packet | ||||
|                 lastPacket = (frameRead == frame.lastPacket); | ||||
|  | ||||
|                 // Parse the packet size | ||||
|                 pktExpected = ((uint16_t)frame.content[frameRead]) << 8; | ||||
|                 pktExpected |= (uint16_t)frame.content[frameRead+1]; | ||||
|                 //flog::debug("Starting to read a {} byte packet at offset {}", pktExpected, frameRead); | ||||
|  | ||||
|                 // Skip to the packet content | ||||
|                 frameRead += 2; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         delete[] pktBuffer; | ||||
|     } | ||||
| } | ||||
| @@ -1,69 +0,0 @@ | ||||
| #pragma once | ||||
| #include "utils/new_event.h" | ||||
| #include "dsp/demod/psk.h" | ||||
| #include "dsp/routing/doubler.h" | ||||
| #include "packet.h" | ||||
| #include "frame.h" | ||||
| #include "rs_codec.h" | ||||
| #include "conv_codec.h" | ||||
| #include "framing.h" | ||||
| #include <mutex> | ||||
|  | ||||
| namespace ryfi { | ||||
|     class Receiver { | ||||
|     public: | ||||
|         Receiver(); | ||||
|  | ||||
|         /** | ||||
|          * Create a transmitter. | ||||
|          * @param in Baseband input. | ||||
|          * @param baudrate Baudrate to use over the air. | ||||
|          * @param samplerate Samplerate of the baseband. | ||||
|         */ | ||||
|         Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate); | ||||
|  | ||||
|         /** | ||||
|          * Create a transmitter. | ||||
|          * @param in Baseband input. | ||||
|          * @param baudrate Baudrate to use over the air. | ||||
|          * @param samplerate Samplerate of the baseband. | ||||
|         */ | ||||
|         void init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate); | ||||
|  | ||||
|         /** | ||||
|          * Set the input stream. | ||||
|          * @param in Baseband input. | ||||
|         */ | ||||
|         void setInput(dsp::stream<dsp::complex_t>* in); | ||||
|  | ||||
|         // Destructor | ||||
|         ~Receiver(); | ||||
|  | ||||
|         /** | ||||
|          * Start the transmitter's DSP. | ||||
|         */ | ||||
|         void start(); | ||||
|  | ||||
|         /** | ||||
|          * Stop the transmitter's DSP. | ||||
|         */ | ||||
|         void stop(); | ||||
|          | ||||
|         dsp::stream<dsp::complex_t>* softOut; | ||||
|  | ||||
|         NewEvent<Packet> onPacket; | ||||
|  | ||||
|     private: | ||||
|         void worker(); | ||||
|  | ||||
|         // DSP | ||||
|         dsp::demod::PSK<4> demod; | ||||
|         dsp::routing::Doubler<dsp::complex_t> doubler; | ||||
|         Deframer deframer; | ||||
|         ConvDecoder conv; | ||||
|         RSDecoder rs; | ||||
|  | ||||
|         bool running = false; | ||||
|         std::thread workerThread; | ||||
|     }; | ||||
| } | ||||
| @@ -1,169 +0,0 @@ | ||||
| #include "rs_codec.h" | ||||
|  | ||||
| namespace ryfi { | ||||
|     RSEncoder::RSEncoder(dsp::stream<uint8_t>* in) { | ||||
|         // Create the convolutional encoder instance | ||||
|         rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32); | ||||
|          | ||||
|         // Init the base class | ||||
|         base_type::init(in); | ||||
|     } | ||||
|  | ||||
|     RSEncoder::~RSEncoder() { | ||||
|         // Destroy the convolutional encoder instance | ||||
|         correct_reed_solomon_destroy(rs); | ||||
|     } | ||||
|  | ||||
|     int RSEncoder::encode(const uint8_t* in, uint8_t* out, int count) { | ||||
|         // Check the size | ||||
|         assert(count == RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE); | ||||
|  | ||||
|         // Go through each block | ||||
|         uint8_t block[RS_BLOCK_ENC_SIZE]; | ||||
|         for (int i = 0; i < RS_BLOCK_COUNT; i++) { | ||||
|             // Encode block | ||||
|             correct_reed_solomon_encode(rs, &in[i*RS_BLOCK_DEC_SIZE], RS_BLOCK_DEC_SIZE, block); | ||||
|  | ||||
|             // Interleave into the frame | ||||
|             int k = 0; | ||||
|             for (int j = i; j < RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT; j += RS_BLOCK_COUNT) { | ||||
|                 out[j] = block[k++]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Scramble | ||||
|         for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) { | ||||
|             out[i] ^= RS_SCRAMBLER_SEQ[i]; | ||||
|         } | ||||
|  | ||||
|         return RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; | ||||
|     } | ||||
|  | ||||
|     int RSEncoder::run() { | ||||
|         int count = base_type::_in->read(); | ||||
|         if (count < 0) { return -1; } | ||||
|  | ||||
|         count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count); | ||||
|  | ||||
|         base_type::_in->flush(); | ||||
|         if (!out.swap(count)) { return -1; } | ||||
|         return count; | ||||
|     } | ||||
|  | ||||
|     RSDecoder::RSDecoder(dsp::stream<uint8_t>* in) { | ||||
|         // Create the convolutional encoder instance | ||||
|         rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32); | ||||
|          | ||||
|         // Init the base class | ||||
|         base_type::init(in); | ||||
|     } | ||||
|  | ||||
|     RSDecoder::~RSDecoder() { | ||||
|         // Destroy the convolutional encoder instance | ||||
|         correct_reed_solomon_destroy(rs); | ||||
|     } | ||||
|  | ||||
|     int RSDecoder::decode(uint8_t* in, uint8_t* out, int count) { | ||||
|         // Check the size | ||||
|         assert(count == RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE); | ||||
|  | ||||
|         // Descramble (TODO: Don't do it in-place) | ||||
|         for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) { | ||||
|             in[i] ^= RS_SCRAMBLER_SEQ[i]; | ||||
|         } | ||||
|  | ||||
|         // Go through each block | ||||
|         uint8_t block[RS_BLOCK_ENC_SIZE]; | ||||
|         for (int i = 0; i < RS_BLOCK_COUNT; i++) { | ||||
|             // Deinterleave out of the frame | ||||
|             int k = 0; | ||||
|             for (int j = i; j < count; j += RS_BLOCK_COUNT) { | ||||
|                 block[k++] = in[j]; | ||||
|             } | ||||
|  | ||||
|             // Decode block and return if decoding fails | ||||
|             int res = correct_reed_solomon_decode(rs, block, RS_BLOCK_ENC_SIZE, &out[i*RS_BLOCK_DEC_SIZE]); | ||||
|             if (res < 0) { return 0; } | ||||
|         } | ||||
|  | ||||
|         return RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE; | ||||
|     } | ||||
|  | ||||
|     int RSDecoder::run() { | ||||
|         int count = base_type::_in->read(); | ||||
|         if (count < 0) { return -1; } | ||||
|  | ||||
|         count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count); | ||||
|  | ||||
|         base_type::_in->flush(); | ||||
|         if (count && !out.swap(count)) { return -1; } | ||||
|         return count; | ||||
|     } | ||||
|  | ||||
|     const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT] = { | ||||
|         0x75, 0x05, 0x7C, 0xCE, 0xF1, 0xD0, 0x6C, 0xF6, 0xFA, 0x65, 0xF6, 0xFC, 0xE0, 0x0A, 0x82, 0x17, | ||||
|         0x6C, 0xBE, 0x76, 0xA0, 0xD6, 0x46, 0x12, 0x2E, 0xDE, 0xB5, 0xF7, 0xAD, 0xCB, 0x51, 0x63, 0x47, | ||||
|         0x27, 0x30, 0x7E, 0x43, 0xD1, 0xA1, 0xCB, 0x10, 0x08, 0x49, 0xDF, 0x86, 0xD4, 0xC4, 0xD7, 0x3C, | ||||
|         0x6D, 0x03, 0x07, 0x37, 0x5B, 0xB3, 0xCD, 0x79, 0x6F, 0x1E, 0xBA, 0xC5, 0x6E, 0xC3, 0x8C, 0x7A, | ||||
|         0x25, 0x99, 0x61, 0x54, 0x5A, 0x96, 0x57, 0x9B, 0xE0, 0x60, 0x5B, 0x09, 0x6D, 0x8B, 0x2D, 0x9D, | ||||
|         0x15, 0x9D, 0x0E, 0xBF, 0x57, 0xFB, 0x9C, 0x49, 0x82, 0x2C, 0x48, 0x59, 0x92, 0x47, 0x79, 0x17, | ||||
|         0x16, 0x74, 0xEA, 0xEA, 0xBB, 0xC5, 0x72, 0x32, 0x17, 0xD1, 0xB3, 0xDE, 0xEB, 0x15, 0xC7, 0x55, | ||||
|         0x8A, 0xF2, 0x88, 0xC2, 0x33, 0xA6, 0x17, 0x8B, 0xD4, 0x77, 0x22, 0x00, 0x63, 0x47, 0x45, 0x5F, | ||||
|         0x36, 0x35, 0x58, 0x8B, 0x88, 0xEC, 0xCA, 0xC4, 0x60, 0x53, 0x9E, 0xBD, 0xB2, 0xF5, 0x51, 0x46, | ||||
|         0x34, 0x9A, 0x07, 0x25, 0x3F, 0xF5, 0x65, 0x63, 0x77, 0x3C, 0x5A, 0xFA, 0x4E, 0x0C, 0xF7, 0x1B, | ||||
|         0x82, 0xAB, 0x73, 0x06, 0x7F, 0xB7, 0xC6, 0x6B, 0xBF, 0xB1, 0x46, 0xF3, 0x01, 0x91, 0xB1, 0xFF, | ||||
|         0x5C, 0x6F, 0xF9, 0x43, 0x0E, 0x6A, 0x70, 0x89, 0x0B, 0xEA, 0x8C, 0xD4, 0x1B, 0x51, 0x01, 0x31, | ||||
|         0x71, 0x2E, 0xDF, 0x24, 0xC1, 0xD5, 0xDB, 0x0E, 0xF5, 0xEB, 0x78, 0x79, 0x39, 0x5B, 0xAD, 0xC3, | ||||
|         0xA9, 0xA6, 0x60, 0x30, 0xA2, 0x9A, 0x7B, 0xA0, 0xF4, 0xAA, 0xC5, 0x57, 0xB3, 0x16, 0xF9, 0xB5, | ||||
|         0x79, 0x20, 0xC1, 0x88, 0x9A, 0x00, 0x43, 0xB2, 0xC6, 0x84, 0x8D, 0x03, 0xF2, 0xD8, 0x90, 0x7A, | ||||
|         0x21, 0x37, 0x7E, 0xF7, 0x75, 0xE5, 0xFB, 0xC9, 0xDC, 0xAB, 0x4B, 0xBC, 0x35, 0x38, 0xB9, 0x3A, | ||||
|         0x53, 0x89, 0x7E, 0xD5, 0x94, 0x12, 0x2D, 0x9B, 0x91, 0x90, 0x1D, 0x4D, 0x0E, 0xE0, 0x93, 0xF3, | ||||
|         0xC1, 0xA1, 0x9B, 0x73, 0x27, 0x22, 0x41, 0x27, 0xEE, 0x2A, 0xD7, 0x45, 0xBC, 0x8F, 0x9B, 0xA2, | ||||
|         0x36, 0x11, 0x16, 0x37, 0x1A, 0xF1, 0x2E, 0x71, 0xCF, 0x86, 0x89, 0x83, 0x5A, 0xF1, 0x24, 0x6C, | ||||
|         0x56, 0x71, 0x53, 0xE4, 0xD2, 0xCB, 0xCA, 0x86, 0x1E, 0xA0, 0xD5, 0x83, 0x3B, 0xEF, 0x09, 0x09, | ||||
|         0xC2, 0x07, 0x53, 0x86, 0xE6, 0x8A, 0xC6, 0x70, 0xFB, 0x91, 0x43, 0xCB, 0x91, 0x6E, 0xA9, 0xBC, | ||||
|         0x31, 0x42, 0x61, 0x0C, 0x88, 0xB8, 0x2C, 0xED, 0xD8, 0xE6, 0xA3, 0xEC, 0xAC, 0xB9, 0x45, 0x5E, | ||||
|         0x2C, 0x73, 0x3F, 0x2E, 0x06, 0xE0, 0xBF, 0x73, 0xDD, 0x2E, 0x45, 0x50, 0x6C, 0x53, 0x55, 0xF0, | ||||
|         0x7F, 0x6E, 0x61, 0xFA, 0xA0, 0x7A, 0x1C, 0xF0, 0xBD, 0xAC, 0x48, 0x61, 0x03, 0x6B, 0xED, 0x54, | ||||
|         0x2A, 0x27, 0x94, 0xF6, 0xF9, 0x6A, 0x04, 0x08, 0x0B, 0x3C, 0xC3, 0x30, 0x66, 0x01, 0xFB, 0xDC, | ||||
|         0xC9, 0x65, 0x03, 0x83, 0x7D, 0x0A, 0xDF, 0xA5, 0x04, 0x14, 0xE4, 0xF2, 0x4C, 0x01, 0xDF, 0x04, | ||||
|         0xD2, 0x80, 0xB9, 0x9B, 0xD9, 0x5E, 0xF8, 0x2A, 0x93, 0x8D, 0x8C, 0x09, 0x9B, 0x38, 0xEC, 0x3B, | ||||
|         0xC4, 0x29, 0x90, 0x7C, 0x65, 0x3A, 0xF2, 0x4B, 0x69, 0xD3, 0x63, 0x9B, 0x40, 0x95, 0xC3, 0xFB, | ||||
|         0x67, 0x54, 0x40, 0x9B, 0x26, 0x9F, 0x52, 0xFE, 0xD8, 0xD0, 0x24, 0x9C, 0x5C, 0xD4, 0xEF, 0xDE, | ||||
|         0x28, 0x66, 0x75, 0x04, 0xCB, 0xA4, 0xC0, 0xB9, 0x4B, 0xC9, 0x20, 0x4B, 0x56, 0xC7, 0x86, 0xC5, | ||||
|         0x39, 0x45, 0x18, 0xA7, 0x48, 0x14, 0x1A, 0x51, 0xCA, 0xD0, 0xC0, 0x15, 0xDD, 0xC1, 0x28, 0x4A, | ||||
|         0x7A, 0xD2, 0x10, 0xEA, 0x83, 0xD3, 0x3A, 0xEF, 0x48, 0x29, 0x41, 0xA4, 0xD4, 0x57, 0xA6, 0x1D, | ||||
|         0x76, 0x24, 0x93, 0x58, 0x7E, 0xB7, 0xDD, 0x0B, 0xF2, 0xCE, 0x71, 0x55, 0xF5, 0xAB, 0x8C, 0xC8, | ||||
|         0x70, 0x59, 0x73, 0x69, 0x9D, 0x29, 0x5E, 0x59, 0xF4, 0xB2, 0xC4, 0x97, 0x75, 0xF0, 0x65, 0x1B, | ||||
|         0x66, 0x5F, 0xA4, 0x33, 0x5C, 0xC7, 0xBF, 0x45, 0xE6, 0x20, 0xC0, 0xBD, 0xAD, 0xAE, 0x9F, 0x97, | ||||
|         0x05, 0xD8, 0x04, 0x2B, 0x0A, 0x46, 0xE8, 0xB8, 0xCB, 0x00, 0xE2, 0x7C, 0x70, 0x1B, 0x49, 0xDE, | ||||
|         0x81, 0xEB, 0x24, 0xAC, 0x1B, 0x3E, 0x09, 0xFB, 0xAC, 0xB7, 0xF2, 0xD1, 0xB2, 0x78, 0xF3, 0xAC, | ||||
|         0xC7, 0x6A, 0xA2, 0x07, 0x4C, 0xED, 0x61, 0xAD, 0x04, 0x7F, 0x45, 0x83, 0x59, 0x31, 0x27, 0xF0, | ||||
|         0x16, 0x6B, 0x0C, 0xAA, 0xD4, 0xD1, 0xCB, 0x1C, 0x51, 0x41, 0x0D, 0x2F, 0x8F, 0xF9, 0xF9, 0x7F, | ||||
|         0x22, 0x89, 0x46, 0xF4, 0xB8, 0x93, 0x98, 0x9E, 0x3E, 0x23, 0xF1, 0x6E, 0x64, 0x08, 0xB6, 0xC9, | ||||
|         0x6E, 0x53, 0x53, 0xED, 0xAD, 0x21, 0xCD, 0x1A, 0xF0, 0x45, 0xFC, 0x14, 0x00, 0xEA, 0xF7, 0x42, | ||||
|         0xEE, 0xDA, 0x58, 0x0D, 0x85, 0xBC, 0x74, 0xFB, 0x73, 0x78, 0xB5, 0x5E, 0x5E, 0x6F, 0x6F, 0x7E, | ||||
|         0x39, 0xC2, 0x05, 0x50, 0xDB, 0x3D, 0xB8, 0xF3, 0x8F, 0x80, 0xEC, 0x46, 0x29, 0x39, 0x89, 0xF3, | ||||
|         0x55, 0x9C, 0x6A, 0x5F, 0x7C, 0xD9, 0x7C, 0x13, 0xE4, 0x56, 0x5E, 0xE9, 0x60, 0x19, 0xE2, 0x7D, | ||||
|         0xC4, 0x41, 0x92, 0x8D, 0xDA, 0x21, 0x58, 0x20, 0xE9, 0xA8, 0x4C, 0x16, 0x34, 0x99, 0xAC, 0xB7, | ||||
|         0x30, 0xBD, 0x39, 0x19, 0xAC, 0x9B, 0x4B, 0x27, 0xFA, 0x32, 0xC1, 0x48, 0xA1, 0x80, 0x34, 0x36, | ||||
|         0x1E, 0xFB, 0x92, 0x43, 0x35, 0x72, 0x2D, 0xEF, 0xD2, 0xF2, 0xFC, 0xC2, 0x85, 0xAB, 0x59, 0x40, | ||||
|         0x8D, 0x9D, 0x1A, 0x1F, 0xE2, 0x92, 0x87, 0xA2, 0xF9, 0x2C, 0x78, 0xE4, 0xC3, 0x26, 0x56, 0x07, | ||||
|         0xB3, 0x78, 0xAF, 0x79, 0x3D, 0x88, 0xF4, 0xAD, 0x66, 0x7C, 0x07, 0x58, 0x98, 0x82, 0x1A, 0x26, | ||||
|         0xF7, 0xFD, 0xCE, 0xFF, 0x75, 0xED, 0xAB, 0xBD, 0xAE, 0x6D, 0x5C, 0x28, 0x91, 0xF3, 0xB7, 0x5C, | ||||
|         0x27, 0x05, 0xEC, 0x3B, 0xE3, 0xDD, 0x93, 0x24, 0x7F, 0xAD, 0x14, 0xAA, 0x49, 0x61, 0x8F, 0x96, | ||||
|         0x1F, 0xAA, 0xB2, 0xEE, 0xA8, 0x24, 0x41, 0x7C, 0xDC, 0xF1, 0x28, 0x26, 0xE6, 0x7F, 0x98, 0x20, | ||||
|         0x50, 0x5F, 0x90, 0x21, 0x8A, 0x09, 0x26, 0x59, 0xD0, 0x07, 0x2F, 0xE1, 0x35, 0x4D, 0x0B, 0x20, | ||||
|         0xB2, 0xD5, 0xDD, 0xB5, 0xAC, 0x1B, 0xFE, 0xD9, 0xE3, 0x35, 0xF1, 0xB8, 0x3F, 0x3D, 0xFC, 0x0B, | ||||
|         0x5A, 0x57, 0xA9, 0x92, 0x2B, 0xC8, 0x3E, 0xC2, 0xAA, 0xEF, 0xB9, 0x98, 0x2C, 0xA8, 0xAB, 0xF6, | ||||
|         0xA1, 0xBF, 0xBC, 0x8D, 0x97, 0xA2, 0x74, 0xD9, 0xE5, 0x99, 0x85, 0x81, 0x15, 0xB0, 0xE7, 0x8B, | ||||
|         0x48, 0x86, 0xF4, 0x94, 0x9C, 0x62, 0x82, 0xD1, 0x2C, 0x24, 0x4B, 0xAC, 0x7A, 0xB8, 0x4E, 0x4A, | ||||
|         0xD2, 0xF6, 0xAA, 0xED, 0xE0, 0x9C, 0x98, 0xD2, 0xDF, 0xC1, 0xBC, 0xBF, 0x55, 0x7D, 0x40, 0xB5, | ||||
|         0xDE, 0xD4, 0x25, 0xBB, 0x81, 0xF4, 0x07, 0x1D, 0xE7, 0x3C, 0xB4, 0x62, 0xC9, 0x55, 0x0A, 0x3A, | ||||
|         0xD5, 0xCE, 0x97, 0xED, 0x30, 0x76, 0x76, 0x51, 0xBC, 0x8C, 0xE4, 0x54, 0xBE, 0xB7, 0xB5, 0xCD, | ||||
|         0xF8, 0x76, 0x37, 0x53, 0x2C, 0x9F, 0xE4, 0xC7, 0xEB, 0xF5, 0x8D, 0x23, 0x8A, 0xDA, 0xD1, 0xA9, | ||||
|         0xD8, 0x4C, 0x53, 0xF3, 0x49, 0xA7, 0x1A, 0x5D, 0xE5, 0x03, 0x49, 0x52, 0xD3, 0xE2, 0x1F, 0xA5, | ||||
|         0x35, 0x9C, 0xBB, 0x0B, 0xC7, 0x0D, 0xA4, 0x65, 0x54, 0x8B, 0x39, 0xF1, 0x3B, 0x67, 0x21, 0x71, | ||||
|         0x10, 0xE7, 0x76, 0xC4, 0xA8, 0xC2, 0x9D, 0x93, 0xC6, 0x51, 0xBA, 0x23 | ||||
|     }; | ||||
| } | ||||
| @@ -1,82 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
| #include "dsp/processor.h" | ||||
|  | ||||
| extern "C" { | ||||
|     #include "correct.h" | ||||
| } | ||||
|  | ||||
| namespace ryfi { | ||||
|     // Size of an encoded reed-solomon block. | ||||
|     inline const int RS_BLOCK_ENC_SIZE  = 255; | ||||
|  | ||||
|     // Size of a decoded reed-solomon block. | ||||
|     inline const int RS_BLOCK_DEC_SIZE  = 223; | ||||
|  | ||||
|     // Number of reed-solomon blocks. | ||||
|     inline const int RS_BLOCK_COUNT     = 4; | ||||
|  | ||||
|     // Scrambler sequence | ||||
|     extern const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT]; | ||||
|  | ||||
|     /** | ||||
|      * RyFi Reed-Solomon Encoder. | ||||
|     */ | ||||
|     class RSEncoder : public dsp::Processor<uint8_t, uint8_t> { | ||||
|         using base_type = dsp::Processor<uint8_t, uint8_t>; | ||||
|     public: | ||||
|         /** | ||||
|          * Create a reed-solomon encoder specifying an input stream. | ||||
|          * @param in Input stream | ||||
|         */ | ||||
|         RSEncoder(dsp::stream<uint8_t>* in = NULL); | ||||
|  | ||||
|         // Destructor | ||||
|         ~RSEncoder(); | ||||
|  | ||||
|         /** | ||||
|          * Encode data. | ||||
|          * @param in Input bytes. | ||||
|          * @param out Output bytes. | ||||
|          * @param count Number of input bytes. | ||||
|          * @return Number of output bytes. | ||||
|         */ | ||||
|         int encode(const uint8_t* in, uint8_t* out, int count); | ||||
|  | ||||
|     private: | ||||
|         int run(); | ||||
|  | ||||
|         correct_reed_solomon* rs; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * RyFi Reed-Solomon Decoder. | ||||
|     */ | ||||
|     class RSDecoder : public dsp::Processor<uint8_t, uint8_t> { | ||||
|         using base_type = dsp::Processor<uint8_t, uint8_t>; | ||||
|     public: | ||||
|         /** | ||||
|          * Create a reed-solomon decoder specifying an input stream. | ||||
|          * @param in Input stream | ||||
|         */ | ||||
|         RSDecoder(dsp::stream<uint8_t>* in = NULL); | ||||
|  | ||||
|         // Destructor | ||||
|         ~RSDecoder(); | ||||
|  | ||||
|         /** | ||||
|          * Decode data. | ||||
|          * @param in Input bytes. | ||||
|          * @param out Output bytes. | ||||
|          * @param count Number of input bytes. | ||||
|          * @return Number of output bytes. | ||||
|         */ | ||||
|         int decode(uint8_t* in, uint8_t* out, int count); | ||||
|  | ||||
|     private: | ||||
|         int run(); | ||||
|  | ||||
|         correct_reed_solomon* rs; | ||||
|     }; | ||||
| } | ||||
| @@ -1,177 +0,0 @@ | ||||
| #include "transmitter.h" | ||||
|  | ||||
| namespace ryfi { | ||||
|     Transmitter::Transmitter(double baudrate, double samplerate) { | ||||
|         // Initialize the DSP | ||||
|         rs.setInput(&in); | ||||
|         conv.setInput(&rs.out); | ||||
|         framer.setInput(&conv.out); | ||||
|         resamp.init(&framer.out, baudrate, samplerate); | ||||
|  | ||||
|         rrcTaps = dsp::taps::rootRaisedCosine<float>(511, 0.6, baudrate, samplerate); | ||||
|         // Normalize the taps | ||||
|         float tot = 0.0f; | ||||
|         for (int i = 0; i < rrcTaps.size; i++) { | ||||
|             tot += rrcTaps.taps[i]; | ||||
|         } | ||||
|         for (int i = 0; i < rrcTaps.size; i++) { | ||||
|             rrcTaps.taps[i] /= tot; | ||||
|         } | ||||
|  | ||||
|         rrc.init(&resamp.out, rrcTaps); | ||||
|         out = &rrc.out; | ||||
|     } | ||||
|  | ||||
|     Transmitter::~Transmitter() { | ||||
|         // Stop everything | ||||
|         stop(); | ||||
|     } | ||||
|  | ||||
|     void Transmitter::start() { | ||||
|         // Do nothing if already running | ||||
|         if (running) { return; } | ||||
|  | ||||
|         // Start the worker thread | ||||
|         workerThread = std::thread(&Transmitter::worker, this); | ||||
|  | ||||
|         // Start the DSP | ||||
|         rs.start(); | ||||
|         conv.start(); | ||||
|         framer.start(); | ||||
|         resamp.start(); | ||||
|         rrc.start(); | ||||
|  | ||||
|         // Update the running state | ||||
|         running = true; | ||||
|     } | ||||
|  | ||||
|     void Transmitter::stop() { | ||||
|         // Do nothing if not running | ||||
|         if (!running) { return; } | ||||
|  | ||||
|         // Stop the worker thread | ||||
|         in.stopWriter(); | ||||
|         if (workerThread.joinable()) { workerThread.join(); } | ||||
|         in.clearWriteStop(); | ||||
|  | ||||
|         // Stop the DSP | ||||
|         rs.stop(); | ||||
|         conv.stop(); | ||||
|         framer.stop(); | ||||
|         resamp.stop(); | ||||
|         rrc.stop(); | ||||
|  | ||||
|         // Update the running state | ||||
|         running = false; | ||||
|     } | ||||
|  | ||||
|     bool Transmitter::send(const Packet& pkt) { | ||||
|         // Acquire the packet queue | ||||
|         std::lock_guard<std::mutex> lck(packetsMtx); | ||||
|  | ||||
|         // If there are too many packets queued up, drop the packet | ||||
|         if (packets.size() >= MAX_QUEUE_SIZE) { return false; } | ||||
|  | ||||
|         // Push the packet onto the queue | ||||
|         packets.push(pkt); | ||||
|     } | ||||
|  | ||||
|     bool Transmitter::txFrame(const Frame& frame) { | ||||
|         // Serialize the frame | ||||
|         int count = frame.serialize(in.writeBuf); | ||||
|  | ||||
|         // Send it off | ||||
|         return in.swap(count); | ||||
|     } | ||||
|  | ||||
|     Packet Transmitter::popPacket() { | ||||
|         // Acquire the packet queue | ||||
|         std::unique_lock<std::mutex> lck(packetsMtx); | ||||
|  | ||||
|         // If no packets are available, return empty packet | ||||
|         if (!packets.size()) { return Packet(); } | ||||
|  | ||||
|         // Pop the front packet and return it | ||||
|         Packet pkt = packets.front(); | ||||
|         packets.pop(); | ||||
|         return pkt; | ||||
|     } | ||||
|  | ||||
|     void Transmitter::worker() { | ||||
|         Frame frame; | ||||
|         Packet pkt; | ||||
|         uint16_t counter = 0; | ||||
|         int pktToWrite = 0; | ||||
|         int pktWritten = 0; | ||||
|         uint8_t* pktBuffer = new uint8_t[Packet::MAX_SERIALIZED_SIZE]; | ||||
|  | ||||
|         while (true) { | ||||
|             // Initialize the frame | ||||
|             frame.counter = counter++; | ||||
|             frame.firstPacket = PKT_OFFS_NONE; | ||||
|             frame.lastPacket = PKT_OFFS_NONE; | ||||
|             int frameOffset = 0; | ||||
|  | ||||
|             // Fill the frame with as much packet data as possible | ||||
|             while (frameOffset < sizeof(Frame::content)) { | ||||
|                 // If there is no packet in the process of being sent | ||||
|                 if (!pktWritten) { | ||||
|                     // If there is not enough space for the size of the packet | ||||
|                     if ((sizeof(Frame::content) - frameOffset) < 2) { | ||||
|                         // Fill the rest of the frame with noise and send it | ||||
|                         for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); } | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     // Get the next packet | ||||
|                     pkt = popPacket(); | ||||
|  | ||||
|                     // If there was an available packet | ||||
|                     if (pkt) { | ||||
|                         // Serialize the packet | ||||
|                         pktToWrite = pkt.serializedSize(); | ||||
|                         pkt.serialize(pktBuffer); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // If none was available | ||||
|                 if (!pkt) { | ||||
|                     // Fill the rest of the frame with noise and send it | ||||
|                     for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); } | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 // If this is the beginning of the packet | ||||
|                 if (!pktWritten) { | ||||
|                     //flog::debug("Starting to write a {} byte packet at offset {}", pktToWrite-2, frameOffset); | ||||
|  | ||||
|                     // If this is the first packet of the frame, update its offset | ||||
|                     if (frame.firstPacket == PKT_OFFS_NONE) { frame.firstPacket = frameOffset; } | ||||
|  | ||||
|                     // Update the last packet pointer | ||||
|                     frame.lastPacket = frameOffset; | ||||
|                 } | ||||
|  | ||||
|                 // Compute the amount of data writeable to the frame | ||||
|                 int writeable = std::min<int>(pktToWrite - pktWritten, sizeof(Frame::content) - frameOffset); | ||||
|  | ||||
|                 // Copy the data to the frame | ||||
|                 memcpy(&frame.content[frameOffset], &pktBuffer[pktWritten], writeable); | ||||
|                 pktWritten += writeable; | ||||
|                 frameOffset += writeable; | ||||
|  | ||||
|                 // If the packet is done being sent | ||||
|                 if (pktWritten >= pktToWrite) { | ||||
|                     // Prepare for a new packet | ||||
|                     pktToWrite = 0; | ||||
|                     pktWritten = 0; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Send the frame | ||||
|             if (!txFrame(frame)) { break; } | ||||
|         } | ||||
|  | ||||
|         delete[] pktBuffer; | ||||
|     } | ||||
| } | ||||
| @@ -1,69 +0,0 @@ | ||||
| #pragma once | ||||
| #include "dsp/multirate/rational_resampler.h" | ||||
| #include "dsp/taps/root_raised_cosine.h" | ||||
| #include "dsp/filter/fir.h" | ||||
| #include "packet.h" | ||||
| #include "frame.h" | ||||
| #include "rs_codec.h" | ||||
| #include "conv_codec.h" | ||||
| #include "framing.h" | ||||
| #include <queue> | ||||
| #include <mutex> | ||||
|  | ||||
| namespace ryfi { | ||||
|     class Transmitter { | ||||
|     public: | ||||
|         /** | ||||
|          * Create a transmitter. | ||||
|          * @param baudrate Baudrate to use over the air. | ||||
|          * @param samplerate Samplerate of the baseband. | ||||
|         */ | ||||
|         Transmitter(double baudrate, double samplerate); | ||||
|  | ||||
|         // Destructor | ||||
|         ~Transmitter(); | ||||
|  | ||||
|         /** | ||||
|          * Start the transmitter's DSP. | ||||
|         */ | ||||
|         void start(); | ||||
|  | ||||
|         /** | ||||
|          * Stop the transmitter's DSP. | ||||
|         */ | ||||
|         void stop(); | ||||
|  | ||||
|         /** | ||||
|          * Send a packet. | ||||
|          * @param pkg Packet to send. | ||||
|          * @return True if the packet was send, false if it was dropped. | ||||
|         */ | ||||
|         bool send(const Packet& pkt); | ||||
|  | ||||
|         // Baseband output | ||||
|         dsp::stream<dsp::complex_t>* out; | ||||
|  | ||||
|         static inline const int MAX_QUEUE_SIZE  = 32; | ||||
|  | ||||
|     private: | ||||
|         bool txFrame(const Frame& frame); | ||||
|         Packet popPacket(); | ||||
|         void worker(); | ||||
|  | ||||
|         // Packet queue | ||||
|         std::mutex packetsMtx; | ||||
|         std::queue<Packet> packets; | ||||
|  | ||||
|         // DSP | ||||
|         dsp::stream<uint8_t> in; | ||||
|         RSEncoder rs; | ||||
|         ConvEncoder conv; | ||||
|         Framer framer; | ||||
|         dsp::multirate::RationalResampler<dsp::complex_t> resamp; | ||||
|         dsp::tap<float> rrcTaps; | ||||
|         dsp::filter::FIR<dsp::complex_t, float> rrc; | ||||
|  | ||||
|         bool running = false; | ||||
|         std::thread workerThread; | ||||
|     }; | ||||
| } | ||||
| @@ -6,13 +6,13 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-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 libspdlog-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -25,30 +25,10 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -6,13 +6,13 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-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 libspdlog-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -25,30 +25,10 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -6,13 +6,13 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-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 libspdlog-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -25,30 +25,10 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -6,13 +6,13 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-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 libspdlog-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -25,30 +25,10 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -12,13 +12,13 @@ apt update | ||||
| # Install dependencies and tools | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-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 autoconf libtool xxd libspdlog-dev | ||||
|             libcodec2-dev libudev-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install a more recent libusb version | ||||
| @@ -51,26 +51,6 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Fix missing .pc file for codec2 | ||||
| echo 'prefix=/usr/' >> /usr/share/pkgconfig/codec2.pc | ||||
| echo 'libdir=/usr/include/x86_64-linux-gnu/' >> /usr/share/pkgconfig/codec2.pc | ||||
| @@ -86,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 -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| # Generate package | ||||
|   | ||||
| @@ -6,13 +6,13 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-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 libspdlog-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -25,30 +25,10 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -6,13 +6,13 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-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 libspdlog-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -25,30 +25,10 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -6,13 +6,13 @@ cd /root | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-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 libspdlog-dev | ||||
|             libcodec2-dev autoconf libtool xxd | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.14.0 | ||||
| cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| @@ -25,30 +25,10 @@ make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| FROM ubuntu:noble | ||||
| ENV DEBIAN_FRONTEND=noninteractive  | ||||
| COPY do_build.sh /root | ||||
| RUN chmod +x /root/do_build.sh | ||||
| @@ -1,55 +0,0 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
| cd /root | ||||
|  | ||||
| # Install dependencies and tools | ||||
| apt update | ||||
| apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-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 libspdlog-dev | ||||
|  | ||||
| # Install SDRPlay libraries | ||||
| wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1.run | ||||
| 7z x ./SDRplay_RSP_API-Linux-3.15.1 | ||||
| cp x86_64/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so | ||||
| cp inc/* /usr/include/ | ||||
|  | ||||
| # Install libperseus | ||||
| git clone https://github.com/Microtelecom/libperseus-sdr | ||||
| cd libperseus-sdr | ||||
| autoreconf -i | ||||
| ./configure | ||||
| make | ||||
| make install | ||||
| ldconfig | ||||
| cd .. | ||||
|  | ||||
| # Install librfnm | ||||
| git clone https://github.com/AlexandreRouma/librfnm | ||||
| cd librfnm | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| # Install libfobos | ||||
| git clone https://github.com/AlexandreRouma/libfobos | ||||
| cd libfobos | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| make -j2 | ||||
| make install | ||||
| cd ../../ | ||||
|  | ||||
| cd SDRPlusPlus | ||||
| mkdir build | ||||
| cd build | ||||
| cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON | ||||
| make VERBOSE=1 -j2 | ||||
|  | ||||
| cd .. | ||||
| sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev' | ||||
| @@ -8,7 +8,7 @@ mkdir sdrpp_debian_amd64/DEBIAN | ||||
| # Create package info | ||||
| echo Create package info | ||||
| echo Package: sdrpp >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Version: 1.2.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Version: 1.1.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Maintainer: Ryzerth >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Architecture: all >> sdrpp_debian_amd64/DEBIAN/control | ||||
| echo Description: Bloat-free SDR receiver software >> sdrpp_debian_amd64/DEBIAN/control | ||||
|   | ||||
| @@ -22,7 +22,7 @@ cp -R root/res/* $BUNDLE/Contents/Resources/ | ||||
| bundle_create_icns root/res/icons/sdrpp.macos.png $BUNDLE/Contents/Resources/sdrpp | ||||
|  | ||||
| # Create the property list | ||||
| bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist | ||||
| bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.1.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist | ||||
|  | ||||
| # ========================= Install binaries ========================= | ||||
|  | ||||
| @@ -35,14 +35,11 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/airspyhf_source/airspyhf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/bladerf_source/bladerf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/file_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/fobossdr_source/fobossdr_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/network_source/network_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfnm_source/rfnm_source.dylib | ||||
| 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 | ||||
| bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_tcp_source/rtl_tcp_source.dylib | ||||
|   | ||||
| @@ -24,9 +24,6 @@ cp 'C:/Program Files/PothosSDR/bin/bladeRF.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/file_source/Release/file_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/fobossdr_source/Release/fobossdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/RigExpert/Fobos/bin/fobos.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/hackrf_source/Release/hackrf_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/hackrf.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| @@ -35,8 +32,6 @@ cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windo | ||||
| cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/network_source/Release/network_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| @@ -44,11 +39,6 @@ cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_w | ||||
| cp 'C:/Program Files/PothosSDR/bin/libiio.dll' sdrpp_windows_x64/ | ||||
| cp 'C:/Program Files/PothosSDR/bin/libad9361.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/rfnm_source/Release/rfnm_source.dll sdrpp_windows_x64/modules/ | ||||
| cp 'C:/Program Files/RFNM/bin/rfnm.dll' sdrpp_windows_x64/ | ||||
| cp 'C:/Program Files/RFNM/bin/spdlog.dll' sdrpp_windows_x64/ | ||||
| cp 'C:/Program Files/RFNM/bin/fmt.dll' sdrpp_windows_x64/ | ||||
|  | ||||
| cp $build_dir/source_modules/rfspace_source/Release/rfspace_source.dll sdrpp_windows_x64/modules/ | ||||
|  | ||||
| cp $build_dir/source_modules/rtl_sdr_source/Release/rtl_sdr_source.dll sdrpp_windows_x64/modules/ | ||||
|   | ||||
| @@ -168,9 +168,10 @@ public: | ||||
|         writer.setSamplerate(samplerate); | ||||
|  | ||||
|         // Open file | ||||
|         std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband"; | ||||
|         std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : ""; | ||||
|         std::string extension = ".wav"; | ||||
|         std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, recMode, vfoName) + extension); | ||||
|         std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension); | ||||
|         if (!writer.open(expandedPath)) { | ||||
|             flog::error("Failed to open file for recording: {0}", expandedPath); | ||||
|             return; | ||||
| @@ -248,6 +249,7 @@ private: | ||||
|         } | ||||
|         ImGui::Columns(1, CONCAT("EndRecorderModeColumns##_", _this->name), false); | ||||
|         ImGui::EndGroup(); | ||||
|         if (_this->recording) { style::endDisabled(); } | ||||
|  | ||||
|         // Recording path | ||||
|         if (_this->folderSelect.render("##_recorder_fold_" + _this->name)) { | ||||
| @@ -282,11 +284,8 @@ private: | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (_this->recording) { style::endDisabled(); } | ||||
|  | ||||
|         // Show additional audio options | ||||
|         if (_this->recMode == RECORDER_MODE_AUDIO) { | ||||
|             if (_this->recording) { style::beginDisabled(); } | ||||
|             ImGui::LeftLabel("Stream"); | ||||
|             ImGui::FillWidth(); | ||||
|             if (ImGui::Combo(CONCAT("##_recorder_stream_", _this->name), &_this->streamId, _this->audioStreams.txt)) { | ||||
| @@ -295,7 +294,6 @@ private: | ||||
|                 config.conf[_this->name]["audioStream"] = _this->audioStreams.key(_this->streamId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|             if (_this->recording) { style::endDisabled(); } | ||||
|  | ||||
|             _this->updateAudioMeter(_this->audioLvl); | ||||
|             ImGui::FillWidth(); | ||||
| @@ -451,7 +449,7 @@ private: | ||||
|         { RADIO_IFACE_MODE_RAW, "RAW" } | ||||
|     }; | ||||
|  | ||||
|     std::string genFileName(std::string templ, int mode, std::string name) { | ||||
|     std::string genFileName(std::string templ, std::string type, std::string name) { | ||||
|         // Get data | ||||
|         time_t now = time(0); | ||||
|         tm* ltm = localtime(&now); | ||||
| @@ -461,9 +459,6 @@ private: | ||||
|             freq += gui::waterfall.vfos[name]->generalOffset; | ||||
|         } | ||||
|  | ||||
|         // Select the recording type string | ||||
|         std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband"; | ||||
|  | ||||
|         // Format to string | ||||
|         char freqStr[128]; | ||||
|         char hourStr[128]; | ||||
| @@ -472,7 +467,7 @@ private: | ||||
|         char dayStr[128]; | ||||
|         char monStr[128]; | ||||
|         char yearStr[128]; | ||||
|         const char* modeStr = (recMode == RECORDER_MODE_AUDIO) ? "Unknown" : "IQ"; | ||||
|         const char* modeStr = "Unknown"; | ||||
|         sprintf(freqStr, "%.0lfHz", freq); | ||||
|         sprintf(hourStr, "%02d", ltm->tm_hour); | ||||
|         sprintf(minStr, "%02d", ltm->tm_min); | ||||
| @@ -481,9 +476,9 @@ private: | ||||
|         sprintf(monStr, "%02d", ltm->tm_mon + 1); | ||||
|         sprintf(yearStr, "%02d", ltm->tm_year + 1900); | ||||
|         if (core::modComManager.getModuleName(name) == "radio") { | ||||
|             int mode = -1; | ||||
|             int mode; | ||||
|             core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode); | ||||
|             if (mode >= 0) { modeStr = radioModeToString[mode]; }; | ||||
|             modeStr = radioModeToString[mode]; | ||||
|         } | ||||
|  | ||||
|         // Replace in template | ||||
|   | ||||
							
								
								
									
										26
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								readme.md
									
									
									
									
									
								
							| @@ -41,13 +41,14 @@ To create a desktop shortcut, rightclick the exe and select `Send to -> Desktop | ||||
|  | ||||
| Download the latest release from [the Releases page](https://github.com/AlexandreRouma/SDRPlusPlus/releases) and extract to the directory of your choice. | ||||
|  | ||||
| Then, use apt to install it: | ||||
| Then, run: | ||||
|  | ||||
| ```sh | ||||
| sudo apt install path/to/the/sdrpp_debian_amd64.deb | ||||
| sudo apt install libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev | ||||
| sudo dpkg -i sdrpp_debian_amd64.deb | ||||
| ``` | ||||
|  | ||||
| **IMPORTANT: You must install the drivers for your SDR. Follow instructions from your manufacturer as to how to do this on your particular distro.** | ||||
| If `libvolk2-dev` is not available, use `libvolk1-dev`. | ||||
|  | ||||
| ### Arch-based | ||||
|  | ||||
| @@ -324,16 +325,12 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | audio_source         | Working    | rtaudio           | OPT_BUILD_AUDIO_SOURCE         | ✅              | ✅                     | ✅                         | | ||||
| | bladerf_source       | Working    | libbladeRF        | OPT_BUILD_BLADERF_SOURCE       | ⛔              | ✅ (not Debian Buster) | ✅                         | | ||||
| | file_source          | Working    | -                 | OPT_BUILD_FILE_SOURCE          | ✅              | ✅                     | ✅                         | | ||||
| | fobossdr_source      | Working    | libfobos          | OPT_BUILD_FOBOSSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | hackrf_source        | Working    | libhackrf         | OPT_BUILD_HACKRF_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | harogic_source       | Beta       | htra_api          | OPT_BUILD_HAROGIC_SOURCE       | ⛔              | ⛔                     | ✅                         | | ||||
| | hermes_source        | Beta       | -                 | OPT_BUILD_HERMES_SOURCE        | ✅              | ✅                     | ✅                         | | ||||
| | kcsdr_source         | Unfinished | libkcsdr          | OPT_BUILD_KCSDR_SOURCE         | ⛔              | ⛔                     | ⛔                         | | ||||
| | limesdr_source       | Working    | liblimesuite      | OPT_BUILD_LIMESDR_SOURCE       | ⛔              | ✅                     | ✅                         | | ||||
| | network_source       | Beta       | -                 | OPT_BUILD_NETWORK_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | network_source       | Unfinished | -                 | OPT_BUILD_NETWORK_SOURCE       | ✅              | ✅                     | ⛔                         | | ||||
| | perseus_source       | Beta       | libperseus-sdr    | OPT_BUILD_PERSEUS_SOURCE       | ⛔              | ✅                     | ✅                         | | ||||
| | plutosdr_source      | Working    | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE      | ✅              | ✅                     | ✅                         | | ||||
| | rfnm_source          | Beta       | librfnm           | OPT_BUILD_RFNM_SOURCE          | ⛔              | ✅                     | ✅                         | | ||||
| | rfspace_source       | Working    | -                 | OPT_BUILD_RFSPACE_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | rtl_sdr_source       | Working    | librtlsdr         | OPT_BUILD_RTL_SDR_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| | rtl_tcp_source       | Working    | -                 | OPT_BUILD_RTL_TCP_SOURCE       | ✅              | ✅                     | ✅                         | | ||||
| @@ -341,9 +338,9 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | sdrpp_server_source  | Working    | -                 | OPT_BUILD_SDRPP_SERVER_SOURCE  | ✅              | ✅                     | ✅                         | | ||||
| | soapy_source         | Deprecated | soapysdr          | OPT_BUILD_SOAPY_SOURCE         | ⛔              | ⛔                     | ⛔                         | | ||||
| | spectran_source      | Unfinished | RTSA Suite        | OPT_BUILD_SPECTRAN_SOURCE      | ⛔              | ⛔                     | ⛔                         | | ||||
| | spectran_http_source | Beta       | -                 | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅              | ✅                     | ✅                         | | ||||
| | spectran_http_source | Beta       | -                 | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅              | ✅                     | ⛔                         | | ||||
| | spyserver_source     | Working    | -                 | OPT_BUILD_SPYSERVER_SOURCE     | ✅              | ✅                     | ✅                         | | ||||
| | usrp_source          | Beta       | libuhd            | OPT_BUILD_USRP_SOURCE          | ⛔              | ⛔                     | ✅                         | | ||||
| | usrp_source          | Beta       | libuhd            | OPT_BUILD_USRP_SOURCE          | ⛔              | ⛔                     | ⛔                         | | ||||
|  | ||||
| ## Sinks | ||||
|  | ||||
| @@ -352,15 +349,14 @@ Modules in beta are still included in releases for the most part but not enabled | ||||
| | android_audio_sink | Working    | -            | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔              | ✅              | ✅ (Android only)          | | ||||
| | audio_sink         | Working    | rtaudio      | OPT_BUILD_AUDIO_SINK         | ✅              | ✅              | ✅                         | | ||||
| | network_sink       | Working    | -            | OPT_BUILD_NETWORK_SINK       | ✅              | ✅              | ✅                         | | ||||
| | new_portaudio_sink | Working    | portaudio    | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | portaudio_sink     | Working    | portaudio    | OPT_BUILD_PORTAUDIO_SINK     | ⛔              | ✅              | ⛔                         | | ||||
| | new_portaudio_sink | Beta       | portaudio    | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔              | ✅              | ⛔                         | | ||||
| | portaudio_sink     | Beta       | portaudio    | OPT_BUILD_PORTAUDIO_SINK     | ⛔              | ✅              | ⛔                         | | ||||
|  | ||||
| ## Decoders | ||||
|  | ||||
| | Name                | Stage      | Dependencies | Option                        | Built by default| Built in Release | Enabled in SDR++ by default | | ||||
| |---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:| | ||||
| | atv_decoder         | Unfinished | -            | OPT_BUILD_ATV_DECODER         | ⛔              | ⛔              | ⛔                         | | ||||
| | dab_decoder         | Unfinished | -            | OPT_BUILD_DAB_DECODER         | ⛔              | ⛔              | ⛔                         | | ||||
| | falcon9_decoder     | Unfinished | ffplay       | OPT_BUILD_FALCON9_DECODER     | ⛔              | ⛔              | ⛔                         | | ||||
| | kgsstv_decoder      | Unfinished | -            | OPT_BUILD_KGSSTV_DECODER      | ⛔              | ⛔              | ⛔                         | | ||||
| | m17_decoder         | Working    | -            | OPT_BUILD_M17_DECODER         | ⛔              | ✅              | ⛔                         | | ||||
| @@ -419,8 +415,8 @@ If you still have an issue, please open an issue about it or ask on the discord. | ||||
|  | ||||
| # Contributing | ||||
|  | ||||
| Feel free to submit band plans via the GitHub issue tracker. | ||||
| For code changes, please create a feature request instead. | ||||
| Feel free to submit pull requests and report bugs via the GitHub issue tracker. | ||||
| I will soon publish a contributing.md listing the code style to use. | ||||
|  | ||||
| # Credits | ||||
|  | ||||
|   | ||||
| @@ -1,645 +0,0 @@ | ||||
| { | ||||
|     "name": "Brazilian Ham Bands", | ||||
|     "country_name": "Brazil", | ||||
|     "country_code": "BR", | ||||
|     "author_name": "Rafael Beraldo", | ||||
|     "author_url": "https://github.com/rberaldo/", | ||||
|     "bands": [ | ||||
|         { | ||||
|             "start": 135700, | ||||
|             "end": 137800, | ||||
|             "type": "amateur", | ||||
|             "name": "2200m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 472000, | ||||
|             "end": 479000, | ||||
|             "type": "amateur", | ||||
|             "name": "635m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1800000, | ||||
|             "end": 1810000, | ||||
|             "type": "amateur", | ||||
|             "name": "|160m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1810000, | ||||
|             "end": 1839000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1839000, | ||||
|             "end": 1840000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1840000, | ||||
|             "end": 1843000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1843000, | ||||
|             "end": 1850000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1850000, | ||||
|             "end": 2000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, AM, DV, Digital 160 Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3500000, | ||||
|             "end": 3570000, | ||||
|             "type": "amateur", | ||||
|             "name": "|80m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3570000, | ||||
|             "end": 3590000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3590000, | ||||
|             "end": 3600000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSD, AM, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3600000, | ||||
|             "end": 3775000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSD, AM, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3775000, | ||||
|             "end": 3875000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSD, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3775000, | ||||
|             "end": 3875000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSD, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3875000, | ||||
|             "end": 4000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSD, AM, DV, Digital, 80m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5351500, | ||||
|             "end": 5354000, | ||||
|             "type": "amateur", | ||||
|             "name": "|60m Ham Band CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5354000, | ||||
|             "end": 5366000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5366000, | ||||
|             "end": 5366500, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital 60m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7000000, | ||||
|             "end": 7040000, | ||||
|             "type": "amateur", | ||||
|             "name": "|40m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7040000, | ||||
|             "end": 7047000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7047000, | ||||
|             "end": 7050000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7050000, | ||||
|             "end": 7100000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 7100000, | ||||
|             "end": 7300000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, AM, DV, Digital 40m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 10100000, | ||||
|             "end": 10130000, | ||||
|             "type": "amateur", | ||||
|             "name": "|30m Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 10130000, | ||||
|             "end": 10150000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital 30m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14000000, | ||||
|             "end": 14070000, | ||||
|             "type": "amateur", | ||||
|             "name": "|20m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14070000, | ||||
|             "end": 14099000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14099000, | ||||
|             "end": 14101000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14101000, | ||||
|             "end": 14282000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 14285000, | ||||
|             "end": 14350000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, AM, DV, Digital 20m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18068000, | ||||
|             "end": 18095000, | ||||
|             "type": "amateur", | ||||
|             "name": "|17m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18095000, | ||||
|             "end": 18109000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18109000, | ||||
|             "end": 18111000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 18111000, | ||||
|             "end": 18168000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital 17m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21000000, | ||||
|             "end": 21070000, | ||||
|             "type": "amateur", | ||||
|             "name": "|15m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21070000, | ||||
|             "end": 21149000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21149000, | ||||
|             "end": 21151000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21151000, | ||||
|             "end": 21380000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 21380000, | ||||
|             "end": 21450000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, AM, DV, Digital 15m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24890000, | ||||
|             "end": 24915000, | ||||
|             "type": "amateur", | ||||
|             "name": "|12m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24915000, | ||||
|             "end": 24929000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24929000, | ||||
|             "end": 24931000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24931000, | ||||
|             "end": 24990000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, DV, Digital 12m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28000000, | ||||
|             "end": 28070000, | ||||
|             "type": "amateur", | ||||
|             "name": "|10m Ham Band CW" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28070000, | ||||
|             "end": 28190000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28190000, | ||||
|             "end": 28199000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28199000, | ||||
|             "end": 28201000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW IBP" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28201000, | ||||
|             "end": 28225000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28225000, | ||||
|             "end": 28300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 28300000, | ||||
|             "end": 29000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29000000, | ||||
|             "end": 29300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29300000, | ||||
|             "end": 29510000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29510000, | ||||
|             "end": 29520000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29520000, | ||||
|             "end": 29590000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29590000, | ||||
|             "end": 29620000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, FM, DV - FM calling freq: 29.600 kHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 29620000, | ||||
|             "end": 29700000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output 10m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 50000000, | ||||
|             "end": 54000000, | ||||
|             "type": "amateur", | ||||
|             "name": "6m Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144000000, | ||||
|             "end": 144025000, | ||||
|             "type": "amateur", | ||||
|             "name": "|2m Ham Band All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144025000, | ||||
|             "end": 144110000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144110000, | ||||
|             "end": 144150000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144150000, | ||||
|             "end": 144180000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144180000, | ||||
|             "end": 144275000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB - Calling freq: 144.2 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144275000, | ||||
|             "end": 144300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144300000, | ||||
|             "end": 144360000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB - Calling freq: 144.2 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144360000, | ||||
|             "end": 144400000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144400000, | ||||
|             "end": 144600000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144600000, | ||||
|             "end": 144900000, | ||||
|             "type": "amateur1", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 144900000, | ||||
|             "end": 145000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, FM, DV, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145000000, | ||||
|             "end": 145200000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes, IVG" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145200000, | ||||
|             "end": 145500000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145500000, | ||||
|             "end": 145565000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145565000, | ||||
|             "end": 145575000, | ||||
|             "type": "amateur", | ||||
|             "name": "APRS" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145575000, | ||||
|             "end": 145790000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145790000, | ||||
|             "end": 145800000, | ||||
|             "type": "amateur", | ||||
|             "name": "Guard Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 145800000, | ||||
|             "end": 146000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146000000, | ||||
|             "end": 146390000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146390000, | ||||
|             "end": 146600000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, FM, DV - Calling freq: 146.52 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146600000, | ||||
|             "end": 146990000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output" | ||||
|         }, | ||||
|         { | ||||
|             "start": 146990000, | ||||
|             "end": 147400000, | ||||
|             "type": "amateur1", | ||||
|             "name": "FM, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 147400000, | ||||
|             "end": 147590000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, FM, DV" | ||||
|         }, | ||||
|         { | ||||
|             "start": 147590000, | ||||
|             "end": 148000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "FM, DV - Repeater output 2m Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 220000000, | ||||
|             "end": 225000000, | ||||
|             "type": "amateur", | ||||
|             "name": "1.3m Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 430000000, | ||||
|             "end": 432000000, | ||||
|             "type": "amateur", | ||||
|             "name": "|70cm Ham Band All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432000000, | ||||
|             "end": 432025000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432025000, | ||||
|             "end": 432100000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, Digital - EME" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432100000, | ||||
|             "end": 432300000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, SSB - Calling freq: 432.1 MHz" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432300000, | ||||
|             "end": 432400000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432400000, | ||||
|             "end": 432420000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital - Pilot Emissions" | ||||
|         }, | ||||
|         { | ||||
|             "start": 432420000, | ||||
|             "end": 433000000, | ||||
|             "type": "amateur", | ||||
|             "name": "CW, SSB, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 433000000, | ||||
|             "end": 433050000, | ||||
|             "type": "amateur1", | ||||
|             "name": "CW, Digital" | ||||
|         }, | ||||
|         { | ||||
|             "start": 433050000, | ||||
|             "end": 434000000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 434000000, | ||||
|             "end": 435000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "Fm, DV - Repeater input" | ||||
|         }, | ||||
|         { | ||||
|             "start": 435000000, | ||||
|             "end": 438000000, | ||||
|             "type": "amateur", | ||||
|             "name": "All Modes - Satellites" | ||||
|         }, | ||||
|         { | ||||
|             "start": 438000000, | ||||
|             "end": 439000000, | ||||
|             "type": "amateur1", | ||||
|             "name": "All Modes" | ||||
|         }, | ||||
|         { | ||||
|             "start": 439000000, | ||||
|             "end": 440000000, | ||||
|             "type": "amateur", | ||||
|             "name": "FM, DV - Repeater output 70cm Ham Band|" | ||||
|         }, | ||||
|         { | ||||
|             "start": 902000000, | ||||
|             "end": 928000000, | ||||
|             "type": "amateur", | ||||
|             "name": "33cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 1240000000, | ||||
|             "end": 1300000000, | ||||
|             "type": "amateur", | ||||
|             "name": "23cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 2330000000, | ||||
|             "end": 2450000000, | ||||
|             "type": "amateur", | ||||
|             "name": "13cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 3400000000, | ||||
|             "end": 3500000000, | ||||
|             "type": "amateur", | ||||
|             "name": "9cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 5650000000, | ||||
|             "end": 5925000000, | ||||
|             "type": "amateur", | ||||
|             "name": "5cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 10000000000, | ||||
|             "end": 10500000000, | ||||
|             "type": "amateur", | ||||
|             "name": "3cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 24000000000, | ||||
|             "end": 24250000000, | ||||
|             "type": "amateur", | ||||
|             "name": "1.2cm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 47000000000, | ||||
|             "end": 47200000000, | ||||
|             "type": "amateur", | ||||
|             "name": "6mm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 122250000000, | ||||
|             "end": 123000000000, | ||||
|             "type": "amateur", | ||||
|             "name": "2.5mm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 134000000000, | ||||
|             "end": 141000000000, | ||||
|             "type": "amateur", | ||||
|             "name": "2mm Ham Band" | ||||
|         }, | ||||
|         { | ||||
|             "start": 241000000000, | ||||
|             "end": 250000000000, | ||||
|             "type": "amateur", | ||||
|             "name": "1mm Ham Band" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| @@ -2,8 +2,8 @@ | ||||
|     "name": "France", | ||||
|     "country_name": "France", | ||||
|     "country_code": "FR", | ||||
|     "author_name": "Fred F4EED, Armand31", | ||||
|     "author_url": "http://f4eed.wordpress.com, https://github.com/Armand31", | ||||
|     "author_name": "Fred F4EED", | ||||
|     "author_url": "http://f4eed.wordpress.com", | ||||
|     "bands": [ | ||||
|         { | ||||
|             "name": "137KHz - Radioamateur", | ||||
| @@ -355,7 +355,7 @@ | ||||
|             "end": 54000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiodiffusion - Bande FM", | ||||
|             "name": "Bande FM - Radiodif.", | ||||
|             "type": "broadcast", | ||||
|             "start": 80000000, | ||||
|             "end": 108000000 | ||||
| @@ -396,12 +396,6 @@ | ||||
|             "start": 162362500, | ||||
|             "end": 162587500 | ||||
|         }, | ||||
| 	{ | ||||
| 	    "name": "Radiodiffusion - Bande DAB", | ||||
| 	    "type": "broadcast", | ||||
| 	    "start": 174000000, | ||||
| 	    "end": 223000000 | ||||
| 	}, | ||||
|         { | ||||
|             "name": "Military Aviation", | ||||
|             "type": "military", | ||||
| @@ -414,12 +408,6 @@ | ||||
|             "start": 240000000, | ||||
|             "end": 270000000 | ||||
|         }, | ||||
| 	{ | ||||
| 	    "name": "Police (TETRAPOL)", | ||||
| 	    "type": "military", | ||||
|  	    "start": 380000000, | ||||
| 	    "end": 400000000 | ||||
| 	}, | ||||
|         { | ||||
|             "name": "70cm - Radioamateur", | ||||
|             "type": "amateur", | ||||
| @@ -432,24 +420,12 @@ | ||||
|             "start": 446000000, | ||||
|             "end": 446200000 | ||||
|         }, | ||||
| 	{ | ||||
| 	    "name": "TNT (DVB-T)", | ||||
| 	    "type": "broadcast", | ||||
| 	    "start": 470000000, | ||||
| 	    "end": 694000000 | ||||
| 	}, | ||||
|         { | ||||
|             "name": "23cm - Radioamateur", | ||||
|             "type": "amateur", | ||||
|             "start": 1240000000, | ||||
|             "end": 1300000000 | ||||
|         }, | ||||
| 	{ | ||||
| 	    "name": "Radiodiffusion - Bande DAB", | ||||
| 	    "type": "broadcast", | ||||
| 	    "start": 1452000000, | ||||
| 	    "end": 1492000000 | ||||
| 	}, | ||||
|         { | ||||
|             "name": "13cm - Radioamateur", | ||||
|             "type": "amateur", | ||||
|   | ||||
| @@ -1,201 +0,0 @@ | ||||
| { | ||||
|     "name": "Ireland", | ||||
|     "country_name": "Republic Of Ireland", | ||||
|     "country_code": "IE", | ||||
|     "author_name": "Oskar Dudek", | ||||
|     "author_url": "", | ||||
|     "bands": [ | ||||
|         { | ||||
|             "name": "2200m Ham Band", | ||||
|             "type": "amateur", | ||||
|             "start": 135700, | ||||
|             "end": 137800 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Long wave", | ||||
|             "type": "broadcast", | ||||
|             "start": 148500, | ||||
|             "end": 282500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "AM broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 531000, | ||||
|             "end": 1602000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "160m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 1810000, | ||||
|             "end": 2000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "120m SW broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 2300000, | ||||
|             "end": 2495000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "90m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 3200000, | ||||
|             "end": 3400000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "80m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 3500000, | ||||
|             "end": 3800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "75m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 3900000, | ||||
|             "end": 4000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "60m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 4750000, | ||||
|             "end": 5060000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "60m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 5351500, | ||||
|             "end": 5366500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "49m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 5900000, | ||||
|             "end": 6200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "40m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 7000000, | ||||
|             "end": 7200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "40m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 7200000, | ||||
|             "end": 7450000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "31m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 9400000, | ||||
|             "end": 9900000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "30m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 10100000, | ||||
|             "end": 10150000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "25m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 11600000, | ||||
|             "end": 12100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "22m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13570000, | ||||
|             "end": 13870000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "20m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 14000000, | ||||
|             "end": 14350000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "19m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15100000, | ||||
|             "end": 15800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "17m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 18068000, | ||||
|             "end": 18168000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "16m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 17480000, | ||||
|             "end": 17900000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "15m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 18900000, | ||||
|             "end": 19020000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "15m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 21000000, | ||||
|             "end": 21450000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "13m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 21450000, | ||||
|             "end": 21850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "12m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 24890000, | ||||
|             "end": 24990000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "11m SW Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 25670000, | ||||
|             "end": 26100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "10m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 28000000, | ||||
|             "end": 29700000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "FM Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 87500000, | ||||
|             "end": 108000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "6m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 50000000, | ||||
|             "end": 52000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "4m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 70000000, | ||||
|             "end": 70500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "2m ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 144000000, | ||||
|             "end": 146000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "70cm ham band", | ||||
|             "type": "amateur", | ||||
|             "start": 430000000, | ||||
|             "end": 440000000 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| @@ -1,549 +0,0 @@ | ||||
| { | ||||
|     "name": "Republic of Korea", | ||||
|     "country_name": "Republic of Korea", | ||||
|     "country_code": "KR", | ||||
|     "author_name": "SeoyeonBae", | ||||
|     "author_url": "https://github.com/bsy0317", | ||||
|     "bands": [ | ||||
|         { | ||||
|             "name": "Radio Navigation", | ||||
|             "type": "aviation", | ||||
|             "start": 8300, | ||||
|             "end": 14000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Coastal Telegraph", | ||||
|             "type": "marine", | ||||
|             "start": 14000, | ||||
|             "end": 19950 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 19950, | ||||
|             "end": 20250 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Coastal Telegraph", | ||||
|             "type": "marine", | ||||
|             "start": 20250, | ||||
|             "end": 70000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radio Navigation", | ||||
|             "type": "navigation", | ||||
|             "start": 70000, | ||||
|             "end": 160000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Radio Navigation", | ||||
|             "type": "aviation", | ||||
|             "start": 160000, | ||||
|             "end": 285000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Maritime Radiobeacon", | ||||
|             "type": "aviation", | ||||
|             "start": 285000, | ||||
|             "end": 325000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Radio Navigation", | ||||
|             "type": "aviation", | ||||
|             "start": 325000, | ||||
|             "end": 472000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur", | ||||
|             "type": "amateur", | ||||
|             "start": 472000, | ||||
|             "end": 479000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "International Distress Safety Call", | ||||
|             "type": "marine", | ||||
|             "start": 479000, | ||||
|             "end": 505000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Maritime Telegraph", | ||||
|             "type": "marine", | ||||
|             "start": 505000, | ||||
|             "end": 526500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 526500, | ||||
|             "end": 1606500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiobuoy", | ||||
|             "type": "navigation", | ||||
|             "start": 1606500, | ||||
|             "end": 1800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 1800000, | ||||
|             "end": 1825000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiobuoy Control LORAN", | ||||
|             "type": "radiolocation", | ||||
|             "start": 1825000, | ||||
|             "end": 2000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiobuoy", | ||||
|             "type": "fixed", | ||||
|             "start": 2000000, | ||||
|             "end": 2065000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Distress Call", | ||||
|             "type": "marine", | ||||
|             "start": 2065000, | ||||
|             "end": 2107000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "International Distress Search and Rescue", | ||||
|             "type": "mobile", | ||||
|             "start": 2173500, | ||||
|             "end": 2190500 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Road Management", | ||||
|             "type": "fixed", | ||||
|             "start": 2194000, | ||||
|             "end": 2495000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 2495000, | ||||
|             "end": 2505000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Telephone", | ||||
|             "type": "fixed", | ||||
|             "start": 2505000, | ||||
|             "end": 2850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile R", | ||||
|             "type": "aviation", | ||||
|             "start": 2850000, | ||||
|             "end": 3025000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile OR", | ||||
|             "type": "aviation", | ||||
|             "start": 3025000, | ||||
|             "end": 3155000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile R", | ||||
|             "type": "aviation", | ||||
|             "start": 3400000, | ||||
|             "end": 3500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 3500000, | ||||
|             "end": 3550000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Experimental Station", | ||||
|             "type": "fixed", | ||||
|             "start": 3550000, | ||||
|             "end": 3790000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 3790000, | ||||
|             "end": 3800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 3900000, | ||||
|             "end": 3950000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 3995000, | ||||
|             "end": 4005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Telephone", | ||||
|             "type": "marine", | ||||
|             "start": 4005000, | ||||
|             "end": 4063000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Oceanographic Data", | ||||
|             "type": "marine", | ||||
|             "start": 4063000, | ||||
|             "end": 4065000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Duplex Telephone", | ||||
|             "type": "marine", | ||||
|             "start": 4065000, | ||||
|             "end": 4146000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Simplex Telephone", | ||||
|             "type": "marine", | ||||
|             "start": 4146000, | ||||
|             "end": 4152000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Wideband Telegraph Fax", | ||||
|             "type": "marine", | ||||
|             "start": 4152000, | ||||
|             "end": 4172000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station Narrowband", | ||||
|             "type": "marine", | ||||
|             "start": 4172000, | ||||
|             "end": 4181750 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Ship Station A1A Morse Code Communication", | ||||
|             "type": "marine", | ||||
|             "start": 4186750, | ||||
|             "end": 4202250 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radiolocation", | ||||
|             "type": "radiolocation", | ||||
|             "start": 4438000, | ||||
|             "end": 4488000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Calling Response", | ||||
|             "type": "fixed", | ||||
|             "start": 4488000, | ||||
|             "end": 4650000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile R", | ||||
|             "type": "aviation", | ||||
|             "start": 4650000, | ||||
|             "end": 4850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 4995000, | ||||
|             "end": 5005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Search Rescue", | ||||
|             "type": "aviation", | ||||
|             "start": 5480000, | ||||
|             "end": 5730000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 5900000, | ||||
|             "end": 5950000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 5950000, | ||||
|             "end": 6200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 7000000, | ||||
|             "end": 7100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 7100000, | ||||
|             "end": 7200000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 7200000, | ||||
|             "end": 7450000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 7995000, | ||||
|             "end": 8005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 9400000, | ||||
|             "end": 9500000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 9500000, | ||||
|             "end": 9900000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 9995000, | ||||
|             "end": 10005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 10100000, | ||||
|             "end": 10150000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile", | ||||
|             "type": "aviation", | ||||
|             "start": 10150000, | ||||
|             "end": 11600000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 11600000, | ||||
|             "end": 11650000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 11650000, | ||||
|             "end": 12050000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 12050000, | ||||
|             "end": 12100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile", | ||||
|             "type": "aviation", | ||||
|             "start": 13260000, | ||||
|             "end": 13360000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Radio Astronomy", | ||||
|             "type": "astronomy", | ||||
|             "start": 13360000, | ||||
|             "end": 13410000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13570000, | ||||
|             "end": 13600000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13600000, | ||||
|             "end": 13800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 13800000, | ||||
|             "end": 13870000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 14000000, | ||||
|             "end": 14350000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Aviation Mobile", | ||||
|             "type": "aviation", | ||||
|             "start": 15010000, | ||||
|             "end": 15100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15100000, | ||||
|             "end": 15600000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15600000, | ||||
|             "end": 15800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 15800000, | ||||
|             "end": 15995000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Standard Frequency Time Signal", | ||||
|             "type": "utility", | ||||
|             "start": 15995000, | ||||
|             "end": 16005000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 18900000, | ||||
|             "end": 19020000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 21000000, | ||||
|             "end": 21450000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 21450000, | ||||
|             "end": 21850000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 24890000, | ||||
|             "end": 24990000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Shortwave Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 25670000, | ||||
|             "end": 26100000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 28000000, | ||||
|             "end": 29700000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 50000000, | ||||
|             "end": 54000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "TV Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 54000000, | ||||
|             "end": 72000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Flood Warning", | ||||
|             "type": "broadcast", | ||||
|             "start": 72000000, | ||||
|             "end": 74800000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "TV Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 76000000, | ||||
|             "end": 88000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "FM Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 88000000, | ||||
|             "end": 100000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "FM Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 100000000, | ||||
|             "end": 108000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "ILS Localizer VOR", | ||||
|             "type": "fixed", | ||||
|             "start": 108000000, | ||||
|             "end": 117975000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Amateur Station", | ||||
|             "type": "amateur", | ||||
|             "start": 144000000, | ||||
|             "end": 146000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "General Communication", | ||||
|             "type": "fixed", | ||||
|             "start": 146000000, | ||||
|             "end": 148000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 162037500, | ||||
|             "end": 174000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "TV Broadcast", | ||||
|             "type": "broadcast", | ||||
|             "start": 174000000, | ||||
|             "end": 216000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 216000000, | ||||
|             "end": 230000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 273000000, | ||||
|             "end": 322000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Personal Radio", | ||||
|             "type": "fixed", | ||||
|             "start": 420000000, | ||||
|             "end": 470000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Public Network", | ||||
|             "type": "broadcast", | ||||
|             "start": 698000000, | ||||
|             "end": 806000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Low Power Device", | ||||
|             "type": "fixed", | ||||
|             "start": 942000000, | ||||
|             "end": 960000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Satellite Mobile Communication", | ||||
|             "type": "fixed", | ||||
|             "start": 15250000000, | ||||
|             "end": 16605000000 | ||||
|         }, | ||||
|         { | ||||
|             "name": "Mobile Communication", | ||||
|             "type": "mobile", | ||||
|             "start": 25000000000, | ||||
|             "end": 37000000000 | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| @@ -1,285 +0,0 @@ | ||||
| { | ||||
|   "name": "Turkey", | ||||
|   "country_name": "Turkey", | ||||
|   "country_code": "TR", | ||||
|   "author_name": "Yunus TA2PEA", | ||||
|   "author_url": "https://github.com/ycanerol", | ||||
|   "bands": [ | ||||
|     { | ||||
|       "name": "LW", | ||||
|       "type": "amateur", | ||||
|       "start": 135700, | ||||
|       "end": 137800 | ||||
|     }, | ||||
|     { | ||||
|       "name": "630m", | ||||
|       "type": "amateur", | ||||
|       "start": 472000, | ||||
|       "end": 479000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "160m", | ||||
|       "type": "amateur", | ||||
|       "start": 1810000, | ||||
|       "end": 1850000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "80m", | ||||
|       "type": "amateur", | ||||
|       "start": 3500000, | ||||
|       "end": 3800000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "60m", | ||||
|       "type": "amateur", | ||||
|       "start": 5351500, | ||||
|       "end": 5366500 | ||||
|     }, | ||||
|     { | ||||
|       "name": "40m", | ||||
|       "type": "amateur", | ||||
|       "start": 7000000, | ||||
|       "end": 7200000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "30m", | ||||
|       "type": "amateur", | ||||
|       "start": 10100000, | ||||
|       "end": 10150000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "20m", | ||||
|       "type": "amateur", | ||||
|       "start": 14000000, | ||||
|       "end": 14350000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "17m", | ||||
|       "type": "amateur", | ||||
|       "start": 18068000, | ||||
|       "end": 18168000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "15m", | ||||
|       "type": "amateur", | ||||
|       "start": 21000000, | ||||
|       "end": 21450000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "12m", | ||||
|       "type": "amateur", | ||||
|       "start": 24890000, | ||||
|       "end": 24990000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "CB", | ||||
|       "type": "other", | ||||
|       "start": 26565000, | ||||
|       "end": 27405000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Pagers", | ||||
|       "type": "amateur", | ||||
|       "start": 27750000, | ||||
|       "end": 28000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "10m", | ||||
|       "type": "amateur", | ||||
|       "start": 28000000, | ||||
|       "end": 29700000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "6m", | ||||
|       "type": "amateur", | ||||
|       "start": 50030000, | ||||
|       "end": 51000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "FM", | ||||
|       "type": "broadcast", | ||||
|       "start": 87500000, | ||||
|       "end": 108000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Airband VOR/ILS", | ||||
|       "type": "aviation", | ||||
|       "start": 108000000, | ||||
|       "end": 117975000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Airband Voice", | ||||
|       "type": "aviation", | ||||
|       "start": 117975000, | ||||
|       "end": 137000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "2m", | ||||
|       "type": "amateur", | ||||
|       "start": 144000000, | ||||
|       "end": 146000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Sayac Okuma", | ||||
|       "type": "other", | ||||
|       "start": 169400000, | ||||
|       "end": 169475000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Pagers", | ||||
|       "type": "other", | ||||
|       "start": 167000000, | ||||
|       "end": 167100000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Public announcement systems", | ||||
|       "type": "other", | ||||
|       "start": 173882500, | ||||
|       "end": 174000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "DVB-T", | ||||
|       "type": "broadcast", | ||||
|       "start": 174000000, | ||||
|       "end": 216000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "T-DAB", | ||||
|       "type": "broadcast", | ||||
|       "start": 216000000, | ||||
|       "end": 233000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "ILS-Glide Path", | ||||
|       "type": "aviation", | ||||
|       "start": 328600000, | ||||
|       "end": 335400000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Public Safety/Emergency", | ||||
|       "type": "other", | ||||
|       "start": 380000000, | ||||
|       "end": 385000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Public Safety/Emergency", | ||||
|       "type": "other", | ||||
|       "start": 390000000, | ||||
|       "end": 395000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "70cm", | ||||
|       "type": "amateur", | ||||
|       "start": 430200000, | ||||
|       "end": 430700000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "70cm-RepeaterRX", | ||||
|       "type": "amateur", | ||||
|       "start": 431550000, | ||||
|       "end": 431825000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "70cm", | ||||
|       "type": "amateur", | ||||
|       "start": 432000000, | ||||
|       "end": 432975000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "70cm", | ||||
|       "type": "amateur", | ||||
|       "start": 433400000, | ||||
|       "end": 434000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "70cm", | ||||
|       "type": "amateur", | ||||
|       "start": 435000000, | ||||
|       "end": 438000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "70cm-RepeaterTX", | ||||
|       "type": "amateur", | ||||
|       "start": 439150000, | ||||
|       "end": 439425000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "Public announcement systems", | ||||
|       "type": "other", | ||||
|       "start": 445250000, | ||||
|       "end": 445462500 | ||||
|     }, | ||||
|     { | ||||
|       "name": "PMR446", | ||||
|       "type": "other", | ||||
|       "start": 446006250, | ||||
|       "end": 446196875 | ||||
|     }, | ||||
|     { | ||||
|       "name": "RFID", | ||||
|       "type": "other", | ||||
|       "start": 865000000, | ||||
|       "end": 868000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "RFID", | ||||
|       "type": "other", | ||||
|       "start": 916100000, | ||||
|       "end": 918900000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "23cm", | ||||
|       "type": "amateur", | ||||
|       "start": 1240000000, | ||||
|       "end": 1300000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "DECT", | ||||
|       "type": "other", | ||||
|       "start": 1880000000, | ||||
|       "end": 1900000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "5GHz", | ||||
|       "type": "amateur", | ||||
|       "start": 5650000000, | ||||
|       "end": 5670000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "5GHz", | ||||
|       "type": "amateur", | ||||
|       "start": 5820000000, | ||||
|       "end": 5850000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "3cm", | ||||
|       "type": "amateur", | ||||
|       "start": 104500000000, | ||||
|       "end": 104520000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "24GHz", | ||||
|       "type": "amateur", | ||||
|       "start": 24000000000, | ||||
|       "end": 24050000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "47GHz", | ||||
|       "type": "amateur", | ||||
|       "start": 47000000000, | ||||
|       "end": 47200000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "75GHz", | ||||
|       "type": "amateur", | ||||
|       "start": 75500000000, | ||||
|       "end": 7600000000 | ||||
|     }, | ||||
|     { | ||||
|       "name": "134GHz", | ||||
|       "type": "amateur", | ||||
|       "start": 134000000000, | ||||
|       "end": 142000000000 | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								root/res/icons/align_center.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								root/res/icons/align_center.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 8.8 KiB | 
| @@ -2,10 +2,10 @@ | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <signal_path/sink.h> | ||||
| #include <dsp/buffer/packer.h> | ||||
| #include <dsp/convert/stereo_to_mono.h> | ||||
| #include <utils/flog.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <RtAudio.h> | ||||
| #include <config.h> | ||||
| #include <core.h> | ||||
| @@ -16,36 +16,51 @@ SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "audio_sink", | ||||
|     /* Description:     */ "Audio sink module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Version:         */ 0, 2, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| class AudioSink : SinkManager::Sink { | ||||
| bool operator==(const RtAudio::DeviceInfo& a, const RtAudio::DeviceInfo& b) { | ||||
|     return a.name == b.name; | ||||
| } | ||||
|  | ||||
| class AudioSink : public Sink { | ||||
| public: | ||||
|     AudioSink(SinkManager::Stream* stream, std::string streamName) { | ||||
|         _stream = stream; | ||||
|         _streamName = streamName; | ||||
|         s2m.init(_stream->sinkOut); | ||||
|     AudioSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) : | ||||
|         Sink(entry, stream, name, id, stringId) | ||||
|     { | ||||
|         s2m.init(stream); | ||||
|         monoPacker.init(&s2m.out, 512); | ||||
|         stereoPacker.init(_stream->sinkOut, 512); | ||||
|         stereoPacker.init(stream, 512); | ||||
|  | ||||
| #if RTAUDIO_VERSION_MAJOR >= 6 | ||||
|         audio.setErrorCallback(&errorCallback); | ||||
| #endif | ||||
|  | ||||
| #if RTAUDIO_VERSION_MAJOR >= 6 | ||||
|         audio.setErrorCallback(&errorCallback); | ||||
| #endif | ||||
|  | ||||
|         // Load config (TODO) | ||||
|         bool created = false; | ||||
|         std::string device = ""; | ||||
|         config.acquire(); | ||||
|         if (!config.conf.contains(_streamName)) { | ||||
|             created = true; | ||||
|             config.conf[_streamName]["device"] = ""; | ||||
|             config.conf[_streamName]["devices"] = json({}); | ||||
|         } | ||||
|         device = config.conf[_streamName]["device"]; | ||||
|         config.release(created); | ||||
|         // config.acquire(); | ||||
|         // if (config.conf.contains(streamName)) { | ||||
|         //     if (!config.conf[streamName].is_array()) { | ||||
|         //         json tmp = config.conf[streamName]; | ||||
|         //         config.conf[streamName] = json::array(); | ||||
|         //         config.conf[streamName][0] = tmp; | ||||
|         //         modified = true; | ||||
|         //     } | ||||
|         //     if (config.conf[streamName].contains((int)id)) { | ||||
|         //         device = config.conf[streamName][(int)id]["device"]; | ||||
|         //     } | ||||
|         // } | ||||
|         // config.release(modified); | ||||
|  | ||||
|         // List devices | ||||
|         RtAudio::DeviceInfo info; | ||||
| #if RTAUDIO_VERSION_MAJOR >= 6 | ||||
|         for (int i : audio.getDeviceIds()) { | ||||
| @@ -60,15 +75,13 @@ public: | ||||
| #endif | ||||
|                 if (info.outputChannels == 0) { continue; } | ||||
|                 if (info.isDefaultOutput) { defaultDevId = devList.size(); } | ||||
|                 devList.push_back(info); | ||||
|                 deviceIds.push_back(i); | ||||
|                 txtDevList += info.name; | ||||
|                 txtDevList += '\0'; | ||||
|                 devList.define(i, info.name, info); | ||||
|             } | ||||
|             catch (const std::exception& e) { | ||||
|                 flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         selectByName(device); | ||||
|     } | ||||
|  | ||||
| @@ -102,67 +115,92 @@ public: | ||||
|     } | ||||
|  | ||||
|     void selectById(int id) { | ||||
|         // Update ID | ||||
|         devId = id; | ||||
|         bool created = false; | ||||
|         config.acquire(); | ||||
|         if (!config.conf[_streamName]["devices"].contains(devList[id].name)) { | ||||
|             created = true; | ||||
|             config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate; | ||||
|         } | ||||
|         sampleRate = config.conf[_streamName]["devices"][devList[id].name]; | ||||
|         config.release(created); | ||||
|         selectedDevName = devList[id].name; | ||||
|  | ||||
|         sampleRates = devList[id].sampleRates; | ||||
|         sampleRatesTxt = ""; | ||||
|         // List samplerates and select default SR | ||||
|         char buf[256]; | ||||
|         bool found = false; | ||||
|         unsigned int defaultId = 0; | ||||
|         sampleRates.clear(); | ||||
|         const auto& srList = devList[id].sampleRates; | ||||
|         unsigned int defaultSr = devList[id].preferredSampleRate; | ||||
|         for (int i = 0; i < sampleRates.size(); i++) { | ||||
|             if (sampleRates[i] == sampleRate) { | ||||
|                 found = true; | ||||
|                 srId = i; | ||||
|         for (auto& sr : srList) { | ||||
|             if (sr == defaultSr) { | ||||
|                 srId = sampleRates.size(); | ||||
|                 sampleRate = sr; | ||||
|             } | ||||
|             if (sampleRates[i] == defaultSr) { | ||||
|                 defaultId = i; | ||||
|             } | ||||
|             sprintf(buf, "%d", sampleRates[i]); | ||||
|             sampleRatesTxt += buf; | ||||
|             sampleRatesTxt += '\0'; | ||||
|         } | ||||
|         if (!found) { | ||||
|             sampleRate = defaultSr; | ||||
|             srId = defaultId; | ||||
|             sprintf(buf, "%d", sr); | ||||
|             sampleRates.define(sr, buf, sr); | ||||
|         } | ||||
|      | ||||
|         // // Load config | ||||
|         // config.acquire(); | ||||
|         // if (config.conf[streamName][(int)id].contains(selectedDevName)) { | ||||
|         //     unsigned int wantedSr = config.conf[streamName][id][selectedDevName]; | ||||
|         //     if (sampleRates.keyExists(wantedSr)) { | ||||
|         //         srId = sampleRates.keyId(wantedSr); | ||||
|         //         sampleRate = sampleRates[srId]; | ||||
|         //     } | ||||
|         // } | ||||
|         // config.release(); | ||||
|  | ||||
|         _stream->setSampleRate(sampleRate); | ||||
|         // Lock the sink | ||||
|         auto lck = entry->getLock(); | ||||
|  | ||||
|         // Stop the sink DSP | ||||
|         // TODO: Only if the sink DSP is running, otherwise you risk starting it when  it shouldn't | ||||
|         entry->stopDSP(); | ||||
|  | ||||
|         // Stop the sink | ||||
|         if (running) { doStop(); } | ||||
|  | ||||
|         // Update stream samplerate | ||||
|         entry->setSamplerate(sampleRate); | ||||
|  | ||||
|         // Start the DSP | ||||
|         entry->startDSP(); | ||||
|  | ||||
|         // Start the sink | ||||
|         if (running) { doStart(); } | ||||
|     } | ||||
|  | ||||
|     void menuHandler() { | ||||
|     void showMenu() { | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(("##_audio_sink_dev_" + _streamName).c_str(), &devId, txtDevList.c_str())) { | ||||
|         if (ImGui::Combo(("##_audio_sink_dev_" + stringId).c_str(), &devId, devList.txt)) { | ||||
|             selectById(devId); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["device"] = devList[devId].name; | ||||
|             config.release(true); | ||||
|             // config.acquire(); | ||||
|             // config.conf[streamName]["device"] = devList[devId].name; | ||||
|             // config.release(true); | ||||
|         } | ||||
|  | ||||
|         ImGui::SetNextItemWidth(menuWidth); | ||||
|         if (ImGui::Combo(("##_audio_sink_sr_" + _streamName).c_str(), &srId, sampleRatesTxt.c_str())) { | ||||
|         if (ImGui::Combo(("##_audio_sink_sr_" + stringId).c_str(), &srId, sampleRates.txt)) { | ||||
|             sampleRate = sampleRates[srId]; | ||||
|             _stream->setSampleRate(sampleRate); | ||||
|             if (running) { | ||||
|                 doStop(); | ||||
|                 doStart(); | ||||
|             } | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["devices"][devList[devId].name] = sampleRate; | ||||
|             config.release(true); | ||||
|              | ||||
|             // Lock the sink | ||||
|             auto lck = entry->getLock(); | ||||
|  | ||||
|             // Stop the sink DSP | ||||
|             // TODO: Only if the sink DSP is running, otherwise you risk starting it when  it shouldn't | ||||
|             entry->stopDSP(); | ||||
|  | ||||
|             // Stop the sink | ||||
|             if (running) { doStop(); } | ||||
|  | ||||
|             // Update stream samplerate | ||||
|             entry->setSamplerate(sampleRate); | ||||
|  | ||||
|             // Start the DSP | ||||
|             entry->startDSP(); | ||||
|  | ||||
|             // Start the sink | ||||
|             if (running) { doStart(); } | ||||
|  | ||||
|             // config.acquire(); | ||||
|             // config.conf[streamName]["devices"][devList[devId].name] = sampleRate; | ||||
|             // config.release(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -185,12 +223,12 @@ public: | ||||
| private: | ||||
|     bool doStart() { | ||||
|         RtAudio::StreamParameters parameters; | ||||
|         parameters.deviceId = deviceIds[devId]; | ||||
|         parameters.deviceId = devList.key(devId); | ||||
|         parameters.nChannels = 2; | ||||
|         unsigned int bufferFrames = sampleRate / 60; | ||||
|         RtAudio::StreamOptions opts; | ||||
|         opts.flags = RTAUDIO_MINIMIZE_LATENCY; | ||||
|         opts.streamName = _streamName; | ||||
|         opts.streamName = streamName; | ||||
|  | ||||
|         try { | ||||
|             audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts); | ||||
| @@ -229,44 +267,34 @@ private: | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     SinkManager::Stream* _stream; | ||||
|     dsp::convert::StereoToMono s2m; | ||||
|     dsp::buffer::Packer<float> monoPacker; | ||||
|     dsp::buffer::Packer<dsp::stereo_t> stereoPacker; | ||||
|  | ||||
|     std::string _streamName; | ||||
|  | ||||
|     int srId = 0; | ||||
|     int devCount; | ||||
|     int devId = 0; | ||||
|     bool running = false; | ||||
|     std::string selectedDevName; | ||||
|  | ||||
|     unsigned int defaultDevId = 0; | ||||
|  | ||||
|     std::vector<RtAudio::DeviceInfo> devList; | ||||
|     std::vector<unsigned int> deviceIds; | ||||
|     std::string txtDevList; | ||||
|     OptionList<unsigned int, RtAudio::DeviceInfo> devList; | ||||
|     OptionList<unsigned int, unsigned int> sampleRates; | ||||
|  | ||||
|     std::vector<unsigned int> sampleRates; | ||||
|     std::string sampleRatesTxt; | ||||
|     unsigned int sampleRate = 48000; | ||||
|  | ||||
|     RtAudio audio; | ||||
| }; | ||||
|  | ||||
| class AudioSinkModule : public ModuleManager::Instance { | ||||
| class AudioSinkModule : public ModuleManager::Instance, public SinkProvider { | ||||
| public: | ||||
|     AudioSinkModule(std::string name) { | ||||
|         this->name = name; | ||||
|         provider.create = create_sink; | ||||
|         provider.ctx = this; | ||||
|  | ||||
|         sigpath::sinkManager.registerSinkProvider("Audio", provider); | ||||
|         sigpath::streamManager.registerSinkProvider("Audio", this); | ||||
|     } | ||||
|  | ||||
|     ~AudioSinkModule() { | ||||
|         // Unregister sink, this will automatically stop and delete all instances of the audio sink | ||||
|         sigpath::sinkManager.unregisterSinkProvider("Audio"); | ||||
|         sigpath::streamManager.unregisterSinkProvider(this); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
| @@ -283,14 +311,13 @@ public: | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { | ||||
|         return (SinkManager::Sink*)(new AudioSink(stream, streamName)); | ||||
|     std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) { | ||||
|         return std::make_unique<AudioSink>(entry, stream, name, id, stringId); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     SinkManager::SinkProvider provider; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
| @@ -312,4 +339,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| } | ||||
| @@ -3,13 +3,14 @@ | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <signal_path/sink.h> | ||||
| #include <signal_path/stream.h> | ||||
| #include <dsp/buffer/packer.h> | ||||
| #include <dsp/convert/stereo_to_mono.h> | ||||
| #include <dsp/sink/handler_sink.h> | ||||
| #include <utils/flog.h> | ||||
| #include <config.h> | ||||
| #include <gui/style.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <core.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
| @@ -18,84 +19,97 @@ SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "network_sink", | ||||
|     /* Description:     */ "Network sink module for SDR++", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Version:         */ 0, 2, 0, | ||||
|     /* Max instances    */ 1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| enum { | ||||
| enum SinkMode { | ||||
|     SINK_MODE_TCP, | ||||
|     SINK_MODE_UDP | ||||
| }; | ||||
|  | ||||
| const char* sinkModesTxt = "TCP\0UDP\0"; | ||||
|  | ||||
| class NetworkSink : SinkManager::Sink { | ||||
| class NetworkSink : public Sink { | ||||
| public: | ||||
|     NetworkSink(SinkManager::Stream* stream, std::string streamName) { | ||||
|         _stream = stream; | ||||
|         _streamName = streamName; | ||||
|     NetworkSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) : | ||||
|         Sink(entry, stream, name, id, stringId) | ||||
|     { | ||||
|         // Define modes | ||||
|         modes.define("TCP", SINK_MODE_TCP); | ||||
|         modes.define("UDP", SINK_MODE_UDP); | ||||
|  | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         if (!config.conf.contains(_streamName)) { | ||||
|             config.conf[_streamName]["hostname"] = "localhost"; | ||||
|             config.conf[_streamName]["port"] = 7355; | ||||
|             config.conf[_streamName]["protocol"] = SINK_MODE_UDP; // UDP | ||||
|             config.conf[_streamName]["sampleRate"] = 48000.0; | ||||
|             config.conf[_streamName]["stereo"] = false; | ||||
|             config.conf[_streamName]["listening"] = false; | ||||
|         // Create a list of sample rates | ||||
|         std::vector<int> srList; | ||||
|         for (int sr = 12000; sr <= 200000; sr += 12000) { | ||||
|             srList.push_back(sr); | ||||
|         } | ||||
|         for (int sr = 11025; sr <= 192000; sr += 11025) { | ||||
|             srList.push_back(sr); | ||||
|         } | ||||
|         std::string host = config.conf[_streamName]["hostname"]; | ||||
|         strcpy(hostname, host.c_str()); | ||||
|         port = config.conf[_streamName]["port"]; | ||||
|         modeId = config.conf[_streamName]["protocol"]; | ||||
|         sampleRate = config.conf[_streamName]["sampleRate"]; | ||||
|         stereo = config.conf[_streamName]["stereo"]; | ||||
|         bool startNow = config.conf[_streamName]["listening"]; | ||||
|         config.release(true); | ||||
|  | ||||
|         // Sort sample rate list | ||||
|         std::sort(srList.begin(), srList.end(), [](double a, double b) { return (a < b); }); | ||||
|  | ||||
|         // Define samplerate options | ||||
|         for (int sr : srList) { | ||||
|             char buf[16]; | ||||
|             sprintf(buf, "%d", sr); | ||||
|             samplerates.define(sr, buf, sr); | ||||
|         } | ||||
|  | ||||
|         // Allocate buffer | ||||
|         netBuf = new int16_t[STREAM_BUFFER_SIZE]; | ||||
|  | ||||
|         packer.init(_stream->sinkOut, 512); | ||||
|         // Init DSP | ||||
|         packer.init(stream, 512); | ||||
|         s2m.init(&packer.out); | ||||
|         monoSink.init(&s2m.out, monoHandler, this); | ||||
|         stereoSink.init(&packer.out, stereoHandler, this); | ||||
|  | ||||
|  | ||||
|         // Create a list of sample rates | ||||
|         for (int sr = 12000; sr < 200000; sr += 12000) { | ||||
|             sampleRates.push_back(sr); | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         bool startNow = false; | ||||
|         if (config.conf[stringId].contains("hostname")) { | ||||
|             std::string host = config.conf[stringId]["hostname"]; | ||||
|             strcpy(hostname, host.c_str()); | ||||
|         } | ||||
|         for (int sr = 11025; sr < 192000; sr += 11025) { | ||||
|             sampleRates.push_back(sr); | ||||
|         if (config.conf[stringId].contains("port")) { | ||||
|             port = config.conf[stringId]["port"]; | ||||
|         } | ||||
|  | ||||
|         // Sort sample rate list | ||||
|         std::sort(sampleRates.begin(), sampleRates.end(), [](double a, double b) { return (a < b); }); | ||||
|  | ||||
|         // Generate text list for UI | ||||
|         char buffer[128]; | ||||
|         int id = 0; | ||||
|         int _48kId; | ||||
|         bool found = false; | ||||
|         for (auto sr : sampleRates) { | ||||
|             sprintf(buffer, "%d", (int)sr); | ||||
|             sampleRatesTxt += buffer; | ||||
|             sampleRatesTxt += '\0'; | ||||
|             if (sr == sampleRate) { | ||||
|                 srId = id; | ||||
|                 found = true; | ||||
|         if (config.conf[stringId].contains("mode")) { | ||||
|             std::string modeStr = config.conf[stringId]["mode"]; | ||||
|             if (modes.keyExists(modeStr)) { | ||||
|                 mode = modes.value(modes.keyId(modeStr)); | ||||
|             } | ||||
|             else { | ||||
|                 mode = SINK_MODE_TCP; | ||||
|             } | ||||
|             if (sr == 48000.0) { _48kId = id; } | ||||
|             id++; | ||||
|         } | ||||
|         if (!found) { | ||||
|             srId = _48kId; | ||||
|             sampleRate = 48000.0; | ||||
|         if (config.conf[stringId].contains("samplerate")) { | ||||
|             int nSr = config.conf[stringId]["samplerate"]; | ||||
|             if (samplerates.keyExists(nSr)) { | ||||
|                 sampleRate = samplerates.value(samplerates.keyId(nSr)); | ||||
|             } | ||||
|             else { | ||||
|                 sampleRate = 48000; | ||||
|             } | ||||
|         } | ||||
|         _stream->setSampleRate(sampleRate); | ||||
|         if (config.conf[stringId].contains("stereo")) { | ||||
|             stereo = config.conf[stringId]["stereo"]; | ||||
|         } | ||||
|         if (config.conf[stringId].contains("running")) { | ||||
|             startNow = config.conf[stringId]["running"]; | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         // Set mode ID | ||||
|         modeId = modes.valueId(mode); | ||||
|  | ||||
|         // Set samplerate ID | ||||
|         srId = samplerates.valueId(sampleRate); | ||||
|  | ||||
|         // Start if needed | ||||
|         if (startNow) { startServer(); } | ||||
| @@ -122,30 +136,30 @@ public: | ||||
|         running = false; | ||||
|     } | ||||
|  | ||||
|     void menuHandler() { | ||||
|     void showMenu() { | ||||
|         float menuWidth = ImGui::GetContentRegionAvail().x; | ||||
|  | ||||
|         bool listening = (listener && listener->isListening()) || (conn && conn->isOpen()); | ||||
|  | ||||
|         if (listening) { style::beginDisabled(); } | ||||
|         if (ImGui::InputText(CONCAT("##_network_sink_host_", _streamName), hostname, 1023)) { | ||||
|         if (ImGui::InputText(CONCAT("##_network_sink_host_", stringId), hostname, 1023)) { | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["hostname"] = hostname; | ||||
|             config.conf[stringId]["hostname"] = hostname; | ||||
|             config.release(true); | ||||
|         } | ||||
|         ImGui::SameLine(); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::InputInt(CONCAT("##_network_sink_port_", _streamName), &port, 0, 0)) { | ||||
|         if (ImGui::InputInt(CONCAT("##_network_sink_port_", stringId), &port, 0, 0)) { | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["port"] = port; | ||||
|             config.conf[stringId]["port"] = port; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         ImGui::LeftLabel("Protocol"); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_mode_", _streamName), &modeId, sinkModesTxt)) { | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_mode_", stringId), &modeId, sinkModesTxt)) { | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["protocol"] = modeId; | ||||
|             config.conf[stringId]["mode"] = modeId; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
| @@ -153,33 +167,33 @@ public: | ||||
|  | ||||
|         ImGui::LeftLabel("Samplerate"); | ||||
|         ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_sr_", _streamName), &srId, sampleRatesTxt.c_str())) { | ||||
|             sampleRate = sampleRates[srId]; | ||||
|             _stream->setSampleRate(sampleRate); | ||||
|         if (ImGui::Combo(CONCAT("##_network_sink_sr_", stringId), &srId, samplerates.txt)) { | ||||
|             sampleRate = samplerates.value(srId); | ||||
|             entry->setSamplerate(sampleRate); | ||||
|             packer.setSampleCount(sampleRate / 60); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["sampleRate"] = sampleRate; | ||||
|             config.conf[stringId]["samplerate"] = sampleRate; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", _streamName), &stereo)) { | ||||
|         if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", stringId), &stereo)) { | ||||
|             stop(); | ||||
|             start(); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["stereo"] = stereo; | ||||
|             config.conf[stringId]["stereo"] = stereo; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) { | ||||
|         if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) { | ||||
|             stopServer(); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["listening"] = false; | ||||
|             config.conf[stringId]["running"] = false; | ||||
|             config.release(true); | ||||
|         } | ||||
|         else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) { | ||||
|         else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) { | ||||
|             startServer(); | ||||
|             config.acquire(); | ||||
|             config.conf[_streamName]["listening"] = true; | ||||
|             config.conf[stringId]["running"] = true; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
| @@ -217,19 +231,14 @@ private: | ||||
|     } | ||||
|  | ||||
|     void startServer() { | ||||
|         try { | ||||
|             if (modeId == SINK_MODE_TCP) { | ||||
|                 listener = net::listen(hostname, port); | ||||
|                 if (listener) { | ||||
|                     listener->acceptAsync(clientHandler, this); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 conn = net::openUDP("0.0.0.0", port, hostname, port, false); | ||||
|         if (modeId == SINK_MODE_TCP) { | ||||
|             listener = net::listen(hostname, port); | ||||
|             if (listener) { | ||||
|                 listener->acceptAsync(clientHandler, this); | ||||
|             } | ||||
|         } | ||||
|         catch (const std::exception& e) { | ||||
|             flog::error("Failed to open socket: {}", e.what()); | ||||
|         else { | ||||
|             conn = net::openUDP("0.0.0.0", port, hostname, port, false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -276,47 +285,40 @@ private: | ||||
|         _this->listener->acceptAsync(clientHandler, _this); | ||||
|     } | ||||
|  | ||||
|     SinkManager::Stream* _stream; | ||||
|     // DSP | ||||
|     dsp::buffer::Packer<dsp::stereo_t> packer; | ||||
|     dsp::convert::StereoToMono s2m; | ||||
|     dsp::sink::Handler<float> monoSink; | ||||
|     dsp::sink::Handler<dsp::stereo_t> stereoSink; | ||||
|  | ||||
|     std::string _streamName; | ||||
|  | ||||
|     int srId = 0; | ||||
|     bool running = false; | ||||
|     OptionList<std::string, SinkMode> modes; | ||||
|     OptionList<int, double> samplerates; | ||||
|  | ||||
|     char hostname[1024]; | ||||
|     int port = 4242; | ||||
|  | ||||
|     int modeId = 1; | ||||
|  | ||||
|     std::vector<unsigned int> sampleRates; | ||||
|     std::string sampleRatesTxt; | ||||
|     unsigned int sampleRate = 48000; | ||||
|     int port = 7355; | ||||
|     SinkMode mode = SINK_MODE_TCP; | ||||
|     int modeId; | ||||
|     int sampleRate = 48000; | ||||
|     int srId; | ||||
|     bool stereo = false; | ||||
|     bool running = false; | ||||
|  | ||||
|     int16_t* netBuf; | ||||
|  | ||||
|     net::Listener listener; | ||||
|     net::Conn conn; | ||||
|     std::mutex connMtx; | ||||
| }; | ||||
|  | ||||
| class NetworkSinkModule : public ModuleManager::Instance { | ||||
| class NetworkSinkModule : public ModuleManager::Instance, SinkProvider { | ||||
| public: | ||||
|     NetworkSinkModule(std::string name) { | ||||
|         this->name = name; | ||||
|         provider.create = create_sink; | ||||
|         provider.ctx = this; | ||||
|  | ||||
|         sigpath::sinkManager.registerSinkProvider("Network", provider); | ||||
|         // Register self as provider | ||||
|         sigpath::streamManager.registerSinkProvider("Network", this); | ||||
|     } | ||||
|  | ||||
|     ~NetworkSinkModule() { | ||||
|         // Unregister sink, this will automatically stop and delete all instances of the audio sink | ||||
|         sigpath::sinkManager.unregisterSinkProvider("Network"); | ||||
|         // Unregister self | ||||
|         sigpath::streamManager.unregisterSinkProvider(this); | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
| @@ -333,14 +335,13 @@ public: | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { | ||||
|         return (SinkManager::Sink*)(new NetworkSink(stream, streamName)); | ||||
|     std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) { | ||||
|         return std::make_unique<NetworkSink>(entry, stream, name, id, stringId); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     SinkManager::SinkProvider provider; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|   | ||||
| @@ -152,6 +152,7 @@ public: | ||||
|  | ||||
|         // Update samplerate from ID | ||||
|         sampleRate = sampleRates[srId]; | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
| private: | ||||
| @@ -231,7 +232,6 @@ private: | ||||
|         if (SmGui::Combo(CONCAT("##_audio_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             std::string dev = _this->devices.key(_this->devId); | ||||
|             _this->select(dev); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = dev; | ||||
|             config.release(true); | ||||
| @@ -253,7 +253,6 @@ private: | ||||
|         if (SmGui::Button(CONCAT("Refresh##_audio_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedDevice); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|   | ||||
| @@ -1,25 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(badgesdr_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| if (MSVC) | ||||
|     find_package(libusb CONFIG REQUIRED) | ||||
|     target_include_directories(badgesdr_source PRIVATE ${LIBUSB_INCLUDE_DIRS}) | ||||
|     target_link_libraries(badgesdr_source PRIVATE ${LIBUSB_LIBRARIES}) | ||||
| elseif (ANDROID) | ||||
|     target_link_libraries(badgesdr_source PUBLIC | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/libusb1.0.so | ||||
|         /sdr-kit/${ANDROID_ABI}/lib/librtlsdr.so | ||||
|     ) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBUSB REQUIRED libusb-1.0) | ||||
|  | ||||
|     target_include_directories(badgesdr_source PRIVATE ${LIBUSB_INCLUDE_DIRS}) | ||||
|     target_link_directories(badgesdr_source PRIVATE ${LIBUSB_LIBRARY_DIRS}) | ||||
|     target_link_libraries(badgesdr_source PRIVATE ${LIBUSB_LIBRARIES}) | ||||
| endif () | ||||
| @@ -1,309 +0,0 @@ | ||||
| #include "badgesdr.h" | ||||
| #include <stdexcept> | ||||
| #include <utils/flog.h> | ||||
|  | ||||
| #define R820T_I2C_ADDR  0x1A | ||||
|  | ||||
| namespace BadgeSDR { | ||||
|     enum Commands { | ||||
|         CMD_I2C_RW, | ||||
|         CMD_I2C_STATUS, | ||||
|         CMD_ADC_START, | ||||
|         CMD_ADC_STOP, | ||||
|         CMD_ADC_GET_SAMP_COUNT | ||||
|     }; | ||||
|  | ||||
|     libusb_context* ctx = NULL; | ||||
|  | ||||
|     bool DeviceInfo::operator==(const DeviceInfo& b) const { | ||||
|         return serialNumber == b.serialNumber; | ||||
|     } | ||||
|  | ||||
|     void Device::write_reg(uint8_t reg, uint8_t value, void* ctx) { | ||||
|         Device* dev = (Device*)ctx; | ||||
|         dev->writeR820TReg(reg, value); | ||||
|         dev->writeR820TReg(0x1F, 0); // TODO: Figure out why this is needed | ||||
|     } | ||||
|  | ||||
|     void Device::read_reg(uint8_t* data, int len, void* ctx) { | ||||
|         Device* dev = (Device*)ctx; | ||||
|         dev->readI2C(R820T_I2C_ADDR, data, len); | ||||
|     } | ||||
|  | ||||
|     Device::Device(libusb_device_handle* dev) { | ||||
|         // Save device handle | ||||
|         this->dev = dev; | ||||
|  | ||||
|         // Init tuner | ||||
|         r820t = { | ||||
|             16000000, // xtal_freq => 16MHz | ||||
|             3000000, // Set at boot to airspy_m0_m4_conf_t conf0 -> r820t_if_freq | ||||
|             100000000, /* Default Freq 100Mhz */ | ||||
|             { | ||||
|                 /* 05 */ 0x9F, // LNA manual gain mode, init to 0 | ||||
|                 /* 06 */ 0x80, | ||||
|                 /* 07 */ 0x60, | ||||
|                 /* 08 */ 0x80, // Image Gain Adjustment | ||||
|                 /* 09 */ 0x40, // Image Phase Adjustment | ||||
|                 /* 0A */ 0xA8, // Channel filter [0..3]: 0 = widest, f = narrowest - Optimal. Don't touch! | ||||
|                 /* 0B */ 0x0F, // High pass filter - Optimal. Don't touch! | ||||
|                 /* 0C */ 0x4F, // VGA control by code, init at 0 | ||||
|                 /* 0D */ 0x63, // LNA AGC settings: [0..3]: Lower threshold; [4..7]: High threshold | ||||
|                 /* 0E */ 0x75, | ||||
|                 /* 0F */ 0xE8, // Filter Widest, LDO_5V OFF, clk out ON, | ||||
|                 /* 10 */ 0x7C, | ||||
|                 /* 11 */ 0x42, | ||||
|                 /* 12 */ 0x06, | ||||
|                 /* 13 */ 0x00, | ||||
|                 /* 14 */ 0x0F, | ||||
|                 /* 15 */ 0x00, | ||||
|                 /* 16 */ 0xC0, | ||||
|                 /* 17 */ 0xA0, | ||||
|                 /* 18 */ 0x48, | ||||
|                 /* 19 */ 0xCC, | ||||
|                 /* 1A */ 0x60, | ||||
|                 /* 1B */ 0x00, | ||||
|                 /* 1C */ 0x54, | ||||
|                 /* 1D */ 0xAE, | ||||
|                 /* 1E */ 0x0A, | ||||
|                 /* 1F */ 0xC0 | ||||
|             }, | ||||
|             0 /* uint16_t padding */ | ||||
|         }; | ||||
|         r820t_init(&r820t, 3000000, write_reg, read_reg, this); | ||||
|         r820t_set_mixer_gain(&r820t, 15); | ||||
|         r820t_set_vga_gain(&r820t, 15); | ||||
|     } | ||||
|  | ||||
|     Device::~Device() { | ||||
|         // Release the bulk interface | ||||
|         libusb_release_interface(dev, 0); | ||||
|  | ||||
|         // Close device | ||||
|         libusb_close(dev); | ||||
|     } | ||||
|  | ||||
|     void Device::setFrequency(double freq) { | ||||
|         r820t_set_freq(&r820t, freq - 2125000); | ||||
|     } | ||||
|  | ||||
|     void Device::setLNAGain(int gain) { | ||||
|         r820t_set_lna_gain(&r820t, gain); | ||||
|     } | ||||
|  | ||||
|     void Device::setMixerGain(int gain) { | ||||
|         r820t_set_mixer_gain(&r820t, gain); | ||||
|     } | ||||
|  | ||||
|     void Device::setVGAGain(int gain) { | ||||
|         r820t_set_vga_gain(&r820t, gain); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     void Device::start(void (*callback)(const uint8_t* samples, int count, void* ctx), void* ctx, int minBufferSize) { | ||||
|         // Do nothing if already running | ||||
|         if (run) { return; } | ||||
|  | ||||
|         // Save handler | ||||
|         this->callback = callback; | ||||
|         this->ctx = ctx; | ||||
|  | ||||
|         // Compute buffer size | ||||
|         int bufCount = minBufferSize / 64; | ||||
|         if (minBufferSize % 64) { bufCount++; } | ||||
|         bufferSize = bufCount * 64; | ||||
|  | ||||
|         // Mark as running | ||||
|         run = true; | ||||
|  | ||||
|         // Start the ADC | ||||
|         startADC(); | ||||
|  | ||||
|         // Start worker thread | ||||
|         workerThread = std::thread(&Device::worker, this); | ||||
|     } | ||||
|  | ||||
|     void Device::stop() { | ||||
|         // Do nothing if already stopped | ||||
|         if (!run) { return; } | ||||
|  | ||||
|         // Mark as stopped | ||||
|         run = false; | ||||
|  | ||||
|         // Wait for the worker to exit | ||||
|         if (workerThread.joinable()) { workerThread.join(); } | ||||
|  | ||||
|         // Stop the ADC | ||||
|         stopADC(); | ||||
|     } | ||||
|  | ||||
|     int Device::getI2CStatus() { | ||||
|         int status; | ||||
|         int ret = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_STATUS, 0, 0, (uint8_t*)&status, sizeof(status), 1000); | ||||
|         if (ret <= 0 || status < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     int Device::readI2C(uint8_t addr, uint8_t* data, int len) { | ||||
|         // Do read | ||||
|         int bytes = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_RW, 0, addr, data, len, 1000); | ||||
|         if (bytes < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|  | ||||
|         // Get status (TODO: Use function) | ||||
|         int status; | ||||
|         int ret = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_STATUS, 0, 0, (uint8_t*)&status, sizeof(status), 1000); | ||||
|         if (ret <= 0 || status < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|  | ||||
|         return bytes; | ||||
|     } | ||||
|  | ||||
|     int Device::writeI2C(uint8_t addr, const uint8_t* data, int len) { | ||||
|         // Do write | ||||
|         int bytes = libusb_control_transfer(dev, (2 << 5), CMD_I2C_RW, 0, addr, (uint8_t*)data, len, 1000); | ||||
|         if (bytes < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|  | ||||
|         // Get status (TODO: Use function) | ||||
|         int status; | ||||
|         int ret = libusb_control_transfer(dev, (1 << 7) | (2 << 5), CMD_I2C_STATUS, 0, 0, (uint8_t*)&status, sizeof(status), 1000); | ||||
|         if (ret <= 0 || status < 0) { | ||||
|             return -1; | ||||
|         } | ||||
|  | ||||
|         return bytes; | ||||
|     } | ||||
|  | ||||
|     uint8_t bitrev(uint8_t val) { | ||||
|         return ((val & 0x01) << 7) | ((val & 0x02) << 5) | ((val & 0x04) << 3) | ((val & 0x08) << 1) | ((val & 0x10) >> 1) | ((val & 0x20) >> 3) | ((val & 0x40) >> 5) | ((val & 0x80) >> 7); | ||||
|     } | ||||
|  | ||||
|     uint8_t Device::readR820TReg(uint8_t reg) { | ||||
|         // Read registers up to it (can't read single) | ||||
|         uint8_t regs[0x20]; | ||||
|         readI2C(R820T_I2C_ADDR, regs, reg+1); | ||||
|  | ||||
|         // Invert bit order | ||||
|         return bitrev(regs[reg]); | ||||
|     } | ||||
|  | ||||
|     void Device::writeR820TReg(uint8_t reg, uint8_t val) { | ||||
|         // Write register id then value | ||||
|         uint8_t cmd[2] = { reg, val }; | ||||
|         writeI2C(R820T_I2C_ADDR, cmd, 2); | ||||
|     } | ||||
|  | ||||
|     int Device::startADC() { | ||||
|         return libusb_control_transfer(dev, (2 << 5), CMD_ADC_START, 0, 0, NULL, 0, 1000); | ||||
|     } | ||||
|  | ||||
|     int Device::stopADC() { | ||||
|         return libusb_control_transfer(dev, (2 << 5), CMD_ADC_STOP, 0, 0, NULL, 0, 1000); | ||||
|     } | ||||
|  | ||||
|     void Device::worker() { | ||||
|         // Allocate sample buffer | ||||
|         uint8_t* buffer = new uint8_t[bufferSize]; | ||||
|         int sampleCount = 0; | ||||
|  | ||||
|         while (run) { | ||||
|             // Receive data with bulk transfer | ||||
|             int recvLen = 0; | ||||
|             int val = libusb_bulk_transfer(dev, LIBUSB_ENDPOINT_IN | 1, &buffer[sampleCount], bufferSize - sampleCount, &recvLen, 1000); | ||||
|              | ||||
|             // If timed out, try again. Otherwise, if an error occur, stop the thread | ||||
|             if (val == LIBUSB_ERROR_TIMEOUT) { | ||||
|                 continue; | ||||
|             } | ||||
|             else if (val) { | ||||
|                 flog::error("USB Error: {}", val); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // Increment sample count | ||||
|             if (recvLen) { sampleCount += recvLen; } | ||||
|  | ||||
|             // If the buffer is full, call handler and reset sample count | ||||
|             if (sampleCount >= bufferSize) { | ||||
|                 callback(buffer, sampleCount, ctx); | ||||
|                 sampleCount = 0; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Free buffer | ||||
|         delete[] buffer; | ||||
|     } | ||||
|  | ||||
|     std::vector<DeviceInfo> list() { | ||||
|         // Init libusb if done yet | ||||
|         if (!ctx) { | ||||
|             libusb_init(&ctx); | ||||
|             libusb_set_debug(ctx, LIBUSB_LOG_LEVEL_WARNING); | ||||
|         } | ||||
|  | ||||
|         // List devices | ||||
|         std::vector<DeviceInfo> devList; | ||||
|         libusb_device** devices; | ||||
|         int devCount = libusb_get_device_list(ctx, &devices); | ||||
|         for (int i = 0; i < devCount; i++) { | ||||
|             // Get device info | ||||
|             DeviceInfo devInfo; | ||||
|             devInfo.dev = devices[i]; | ||||
|             libusb_device_descriptor desc; | ||||
|             libusb_get_device_descriptor(devInfo.dev, &desc); | ||||
|  | ||||
|             // Check the VID/PID and give up if not the right ones | ||||
|             if (desc.idVendor != 0xCAFE || desc.idProduct != 0x4010) { | ||||
|                 libusb_unref_device(devInfo.dev); | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             // Open devices | ||||
|             libusb_device_handle* openDev; | ||||
|             int err = libusb_open(devInfo.dev, &openDev); | ||||
|             if (err) { | ||||
|                 libusb_unref_device(devInfo.dev); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Get serial number | ||||
|             char serial[128]; | ||||
|             libusb_get_string_descriptor_ascii(openDev, desc.iSerialNumber, (uint8_t*)serial, sizeof(serial)); | ||||
|             devInfo.serialNumber = serial; | ||||
|  | ||||
|             // TODO: The libusb device should be unreffed but would need to know when the list disappears | ||||
|  | ||||
|             // Close device | ||||
|             libusb_close(openDev); | ||||
|  | ||||
|             // Add to list | ||||
|             devList.push_back(devInfo); | ||||
|         } | ||||
|  | ||||
|         // Return devices | ||||
|         return devList; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Device> open(const DeviceInfo& dev) { | ||||
|         // Open device | ||||
|         libusb_device_handle* openDev; | ||||
|         int err = libusb_open(dev.dev, &openDev); | ||||
|         if (err) { | ||||
|             throw std::runtime_error("Failed to open device"); | ||||
|         } | ||||
|  | ||||
|         // Claim the bulk transfer interface | ||||
|         if (libusb_claim_interface(openDev, 0)) { | ||||
|             throw std::runtime_error("Failed to claim bulk interface"); | ||||
|         } | ||||
|  | ||||
|         // Create device | ||||
|         return std::make_shared<Device>(openDev); | ||||
|     } | ||||
| } | ||||
| @@ -1,53 +0,0 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <memory> | ||||
| #include <thread> | ||||
| #include <libusb.h> | ||||
| #include "r820t.h" | ||||
|  | ||||
| namespace BadgeSDR { | ||||
|     struct DeviceInfo { | ||||
|         std::string serialNumber; | ||||
|         libusb_device* dev; | ||||
|         bool operator==(const DeviceInfo& b) const; | ||||
|     }; | ||||
|  | ||||
|     class Device { | ||||
|     public: | ||||
|         Device(libusb_device_handle* dev); | ||||
|         ~Device(); | ||||
|  | ||||
|         void setFrequency(double freq); | ||||
|         void setLNAGain(int gain); | ||||
|         void setMixerGain(int gain); | ||||
|         void setVGAGain(int gain); | ||||
|          | ||||
|         void start(void (*callback)(const uint8_t* samples, int count, void* ctx), void* ctx = NULL, int minBufferSize = 2500); | ||||
|         void stop(); | ||||
|  | ||||
|     private: | ||||
|         int getI2CStatus(); | ||||
|         int readI2C(uint8_t addr, uint8_t* data, int len); | ||||
|         int writeI2C(uint8_t addr, const uint8_t* data, int len); | ||||
|         uint8_t readR820TReg(uint8_t reg); | ||||
|         void writeR820TReg(uint8_t reg, uint8_t val); | ||||
|         int startADC(); | ||||
|         int stopADC(); | ||||
|         void worker(); | ||||
|  | ||||
|         libusb_device_handle* dev; | ||||
|         std::thread workerThread; | ||||
|         bool run = false; | ||||
|         int bufferSize = 0; // Must be multiple of 64 for best performance | ||||
|         void* ctx = NULL; | ||||
|         void (*callback)(const uint8_t* samples, int count, void* ctx); | ||||
|  | ||||
|         static void write_reg(uint8_t reg, uint8_t value, void* ctx); | ||||
|         static void read_reg(uint8_t* data, int len, void* ctx); | ||||
|         r820t_priv_t r820t; | ||||
|     }; | ||||
|  | ||||
|     std::vector<DeviceInfo> list(); | ||||
|     std::shared_ptr<Device> open(const DeviceInfo& dev); | ||||
| } | ||||
| @@ -1,272 +0,0 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <dsp/channel/rx_vfo.h> | ||||
| #include <dsp/correction/dc_blocker.h> | ||||
| #include "badgesdr.h" | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "badgesdr_source", | ||||
|     /* Description:     */ "BadgeSDR Source Module", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| class BadgeSDRSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     BadgeSDRSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 250000.0; | ||||
|  | ||||
|         // Initialize DSP | ||||
|         dcBlock.init(&input, 0.001); | ||||
|         ddc.init(&dcBlock.out, 500000, 250000, 250000, 125000); | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &ddc.out; | ||||
|  | ||||
|         // Refresh devices | ||||
|         refresh(); | ||||
|  | ||||
|         // Select first (TODO: Select from config) | ||||
|         select(""); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("BadgeSDR", &handler); | ||||
|     } | ||||
|  | ||||
|     ~BadgeSDRSourceModule() { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void refresh() { | ||||
|         devices.clear(); | ||||
|         auto list = BadgeSDR::list(); | ||||
|         for (const auto& info : list) { | ||||
|             // Format device name | ||||
|             std::string devName = "BadgeSDR "; | ||||
|             devName += " ["; | ||||
|             devName += info.serialNumber; | ||||
|             devName += ']'; | ||||
|  | ||||
|             // Save device | ||||
|             devices.define(info.serialNumber, devName, info); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devices.empty()) { | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial was not found, select the first available serial | ||||
|         if (!devices.keyExists(serial)) { | ||||
|             select(devices.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Save serial number | ||||
|         selectedSerial = serial; | ||||
|         selectedDev = devices.value(devices.keyId(serial)); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("BadgeSDRSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx; | ||||
|         flog::info("BadgeSDRSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|  | ||||
|         // Open the device | ||||
|         _this->openDev = BadgeSDR::open(_this->selectedDev); | ||||
|  | ||||
|         // Configure the device | ||||
|         _this->openDev->setFrequency(_this->freq); | ||||
|         _this->openDev->setLNAGain(_this->lnaGain); | ||||
|         _this->openDev->setMixerGain(_this->mixerGain); | ||||
|         _this->openDev->setVGAGain(_this->vgaGain); | ||||
|  | ||||
|         // Start DSP | ||||
|         _this->dcBlock.start(); | ||||
|         _this->ddc.start(); | ||||
|  | ||||
|         // Start device | ||||
|         _this->openDev->start(callback, _this, 500000/200); | ||||
|          | ||||
|         _this->running = true; | ||||
|         flog::info("BadgeSDRSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|          | ||||
|         // Stop worker | ||||
|         _this->openDev->stop(); | ||||
|  | ||||
|         // Stop DSP | ||||
|         _this->dcBlock.stop(); | ||||
|         _this->ddc.stop(); | ||||
|  | ||||
|         // Close device | ||||
|         _this->openDev.reset(); | ||||
|  | ||||
|         flog::info("BadgeSDRSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             _this->openDev->setFrequency(freq); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("BadgeSDRSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx; | ||||
|          | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_badgesdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_badgesdr_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::LeftLabel("LNA Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_badgesdr_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) { | ||||
|             if (_this->running) { | ||||
|                 _this->openDev->setLNAGain(_this->lnaGain); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Mixer Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_badgesdr_mixer_gain_", _this->name), &_this->mixerGain, 0, 15)) { | ||||
|             if (_this->running) { | ||||
|                 _this->openDev->setMixerGain(_this->mixerGain); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("VGA Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_badgesdr_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) { | ||||
|             if (_this->running) { | ||||
|                 _this->openDev->setVGAGain(_this->vgaGain); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static void callback(const uint8_t* samples, int count, void* ctx) { | ||||
|         BadgeSDRSourceModule* _this = (BadgeSDRSourceModule*)ctx; | ||||
|  | ||||
|         // Convert samples to float | ||||
|         dsp::complex_t* out = _this->input.writeBuf; | ||||
|         int min = 255, max = 0; | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             if (samples[i] < min) { min = samples[i]; } | ||||
|             if (samples[i] > max) { max = samples[i]; } | ||||
|  | ||||
|             out[i].re = ((float)samples[i] - 127.5f) * (1.0f/127.0f); | ||||
|             out[i].im = 1.0f; | ||||
|         } | ||||
|  | ||||
|         // Send out samples | ||||
|         _this->input.swap(count); | ||||
|  | ||||
|         flog::debug("Amplitudes: {} -> {}", min, max); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|  | ||||
|     OptionList<std::string, BadgeSDR::DeviceInfo> devices; | ||||
|  | ||||
|     int devId = 0; | ||||
|     int lnaGain = 0; | ||||
|     int mixerGain = 0; | ||||
|     int vgaGain = 0; | ||||
|     std::string selectedSerial; | ||||
|     BadgeSDR::DeviceInfo selectedDev; | ||||
|     std::shared_ptr<BadgeSDR::Device> openDev; | ||||
|  | ||||
|     dsp::stream<dsp::complex_t> input; | ||||
|     dsp::correction::DCBlocker<dsp::complex_t> dcBlock; | ||||
|     dsp::channel::RxVFO ddc; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Nothing here | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new BadgeSDRSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (BadgeSDRSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     // Nothing here | ||||
| } | ||||
| @@ -1,622 +0,0 @@ | ||||
| /* | ||||
|  * Rafael Micro R820T driver for AIRSPY | ||||
|  * | ||||
|  * Copyright 2013 Youssef Touil <youssef@airspy.com> | ||||
|  * Copyright 2014-2016 Benjamin Vernoux <bvernoux@airspy.com> | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 2 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <stdint.h> | ||||
| #include <string.h> | ||||
| #include "r820t.h" | ||||
| #include <thread> | ||||
|  | ||||
| static int r820t_read_cache_reg(r820t_priv_t *priv, int reg); | ||||
|  | ||||
| #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) | ||||
|  | ||||
| /* Tuner frequency ranges */ | ||||
| struct r820t_freq_range | ||||
| { | ||||
|   uint8_t open_d; | ||||
|   uint8_t rf_mux_ploy; | ||||
|   uint8_t tf_c; | ||||
| }; | ||||
|  | ||||
| #define R820T_READ_MAX_DATA 32 | ||||
| #define R820T_INIT_NB_REGS (32-5) | ||||
| uint8_t r820t_read_data[R820T_READ_MAX_DATA]; /* Buffer for data read from I2C */ | ||||
| uint8_t r820t_state_standby = 1; /* 1=standby/power off 0=r820t initialized/power on */ | ||||
|  | ||||
| /* Tuner frequency ranges | ||||
| "Copyright (C) 2013 Mauro Carvalho Chehab" | ||||
| https://stuff.mit.edu/afs/sipb/contrib/linux/drivers/media/tuners/r820t.c | ||||
| part of freq_ranges() | ||||
| */ | ||||
| const struct r820t_freq_range freq_ranges[] = | ||||
| { | ||||
|   { | ||||
|   /* 0 MHz */ | ||||
|   /* .open_d = */     0x08, /* low */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0xdf, /* R27[7:0]  band2,band0 */ | ||||
|   }, { | ||||
|   /* 50 MHz */ | ||||
|   /* .open_d = */     0x08, /* low */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0xbe, /* R27[7:0]  band4,band1  */ | ||||
|   }, { | ||||
|   /* 55 MHz */ | ||||
|   /* .open_d = */     0x08, /* low */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x8b, /* R27[7:0]  band7,band4 */ | ||||
|   }, { | ||||
|   /* 60 MHz */ | ||||
|   /* .open_d = */     0x08, /* low */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x7b, /* R27[7:0]  band8,band4 */ | ||||
|   }, { | ||||
|   /* 65 MHz */ | ||||
|   /* .open_d = */     0x08, /* low */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x69, /* R27[7:0]  band9,band6 */ | ||||
|   }, { | ||||
|   /* 70 MHz */ | ||||
|   /* .open_d = */     0x08, /* low */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x58, /* R27[7:0]  band10,band7 */ | ||||
|   }, { | ||||
|   /* 75 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x44, /* R27[7:0]  band11,band11 */ | ||||
|   }, { | ||||
|   /* 80 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x44, /* R27[7:0]  band11,band11 */ | ||||
|   }, { | ||||
|   /* 90 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x34, /* R27[7:0]  band12,band11 */ | ||||
|   }, { | ||||
|   /* 100 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x34, /* R27[7:0]  band12,band11 */ | ||||
|   }, { | ||||
|   /* 110 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x24, /* R27[7:0]  band13,band11 */ | ||||
|   }, { | ||||
|   /* 120 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x24, /* R27[7:0]  band13,band11 */ | ||||
|   }, { | ||||
|   /* 140 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x14, /* R27[7:0]  band14,band11 */ | ||||
|   }, { | ||||
|   /* 180 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x13, /* R27[7:0]  band14,band12 */ | ||||
|   }, { | ||||
|   /* 220 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x13, /* R27[7:0]  band14,band12 */ | ||||
|   }, { | ||||
|   /* 250 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x11, /* R27[7:0]  highest,highest */ | ||||
|   }, { | ||||
|   /* 280 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x02, /* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */ | ||||
|   /* .tf_c = */     0x00, /* R27[7:0]  highest,highest */ | ||||
|   }, { | ||||
|   /* 310 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x41, /* R26[7:6]=1 (bypass)  R26[1:0]=1 (middle) */ | ||||
|   /* .tf_c = */     0x00, /* R27[7:0]  highest,highest */ | ||||
|   }, { | ||||
|   /* 450 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x41, /* R26[7:6]=1 (bypass)  R26[1:0]=1 (middle) */ | ||||
|   /* .tf_c = */     0x00, /* R27[7:0]  highest,highest */ | ||||
|   }, { | ||||
|   /* 588 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x40, /* R26[7:6]=1 (bypass)  R26[1:0]=0 (highest) */ | ||||
|   /* .tf_c = */     0x00, /* R27[7:0]  highest,highest */ | ||||
|   }, { | ||||
|   /* 650 MHz */ | ||||
|   /* .open_d = */     0x00, /* high */ | ||||
|   /* .rf_mux_ploy = */  0x40, /* R26[7:6]=1 (bypass)  R26[1:0]=0 (highest) */ | ||||
|   /* .tf_c = */     0x00, /* R27[7:0]  highest,highest */ | ||||
|   } | ||||
| }; | ||||
|  | ||||
| #define FREQ_TO_IDX_SIZE (600) | ||||
| const uint8_t freq_to_idx[FREQ_TO_IDX_SIZE]= | ||||
| { | ||||
|   /* 50 */ 1,/* 51 */ 1,/* 52 */ 1,/* 53 */ 1,/* 54 */ 1, | ||||
|   /* 55 */ 2,/* 56 */ 2,/* 57 */ 2,/* 58 */ 2,/* 59 */ 2, | ||||
|   /* 60 */ 3,/* 61 */ 3,/* 62 */ 3,/* 63 */ 3,/* 64 */ 3, | ||||
|   /* 65 */ 4,/* 66 */ 4,/* 67 */ 4,/* 68 */ 4,/* 69 */ 4, | ||||
|   /* 70 */ 5,/* 71 */ 5,/* 72 */ 5,/* 73 */ 5,/* 74 */ 5, | ||||
|   /* 75 */ 6,/* 76 */ 6,/* 77 */ 6,/* 78 */ 6,/* 79 */ 6, | ||||
|   /* 80 */ 7,/* 81 */ 7,/* 82 */ 7,/* 83 */ 7,/* 84 */ 7,/* 85 */ 7,/* 86 */ 7,/* 87 */ 7,/* 88 */ 7,/* 89 */ 7, | ||||
|   /* 90 */ 8,/* 91 */ 8,/* 92 */ 8,/* 93 */ 8,/* 94 */ 8,/* 95 */ 8,/* 96 */ 8,/* 97 */ 8,/* 98 */ 8,/* 99 */ 8, | ||||
|   /* 100 */ 9,/* 101 */ 9,/* 102 */ 9,/* 103 */ 9,/* 104 */ 9,/* 105 */ 9,/* 106 */ 9,/* 107 */ 9,/* 108 */ 9,/* 109 */ 9, | ||||
|   /* 110 */ 10,/* 111 */ 10,/* 112 */ 10,/* 113 */ 10,/* 114 */ 10,/* 115 */ 10,/* 116 */ 10,/* 117 */ 10,/* 118 */ 10,/* 119 */ 10, | ||||
|   /* 120 */ 11,/* 121 */ 11,/* 122 */ 11,/* 123 */ 11,/* 124 */ 11,/* 125 */ 11,/* 126 */ 11,/* 127 */ 11,/* 128 */ 11,/* 129 */ 11, | ||||
|   /* 130 */ 11,/* 131 */ 11,/* 132 */ 11,/* 133 */ 11,/* 134 */ 11,/* 135 */ 11,/* 136 */ 11,/* 137 */ 11,/* 138 */ 11,/* 139 */ 11, | ||||
|   /* 140 */ 12,/* 141 */ 12,/* 142 */ 12,/* 143 */ 12,/* 144 */ 12,/* 145 */ 12,/* 146 */ 12,/* 147 */ 12,/* 148 */ 12,/* 149 */ 12, | ||||
|   /* 150 */ 12,/* 151 */ 12,/* 152 */ 12,/* 153 */ 12,/* 154 */ 12,/* 155 */ 12,/* 156 */ 12,/* 157 */ 12,/* 158 */ 12,/* 159 */ 12, | ||||
|   /* 160 */ 12,/* 161 */ 12,/* 162 */ 12,/* 163 */ 12,/* 164 */ 12,/* 165 */ 12,/* 166 */ 12,/* 167 */ 12,/* 168 */ 12,/* 169 */ 12, | ||||
|   /* 170 */ 12,/* 171 */ 12,/* 172 */ 12,/* 173 */ 12,/* 174 */ 12,/* 175 */ 12,/* 176 */ 12,/* 177 */ 12,/* 178 */ 12,/* 179 */ 12, | ||||
|   /* 180 */ 13,/* 181 */ 13,/* 182 */ 13,/* 183 */ 13,/* 184 */ 13,/* 185 */ 13,/* 186 */ 13,/* 187 */ 13,/* 188 */ 13,/* 189 */ 13, | ||||
|   /* 190 */ 13,/* 191 */ 13,/* 192 */ 13,/* 193 */ 13,/* 194 */ 13,/* 195 */ 13,/* 196 */ 13,/* 197 */ 13,/* 198 */ 13,/* 199 */ 13, | ||||
|   /* 200 */ 13,/* 201 */ 13,/* 202 */ 13,/* 203 */ 13,/* 204 */ 13,/* 205 */ 13,/* 206 */ 13,/* 207 */ 13,/* 208 */ 13,/* 209 */ 13, | ||||
|   /* 210 */ 13,/* 211 */ 13,/* 212 */ 13,/* 213 */ 13,/* 214 */ 13,/* 215 */ 13,/* 216 */ 13,/* 217 */ 13,/* 218 */ 13,/* 219 */ 13, | ||||
|   /* 220 */ 14,/* 221 */ 14,/* 222 */ 14,/* 223 */ 14,/* 224 */ 14,/* 225 */ 14,/* 226 */ 14,/* 227 */ 14,/* 228 */ 14,/* 229 */ 14, | ||||
|   /* 230 */ 14,/* 231 */ 14,/* 232 */ 14,/* 233 */ 14,/* 234 */ 14,/* 235 */ 14,/* 236 */ 14,/* 237 */ 14,/* 238 */ 14,/* 239 */ 14, | ||||
|   /* 240 */ 14,/* 241 */ 14,/* 242 */ 14,/* 243 */ 14,/* 244 */ 14,/* 245 */ 14,/* 246 */ 14,/* 247 */ 14,/* 248 */ 14,/* 249 */ 14, | ||||
|   /* 250 */ 15,/* 251 */ 15,/* 252 */ 15,/* 253 */ 15,/* 254 */ 15,/* 255 */ 15,/* 256 */ 15,/* 257 */ 15,/* 258 */ 15,/* 259 */ 15, | ||||
|   /* 260 */ 15,/* 261 */ 15,/* 262 */ 15,/* 263 */ 15,/* 264 */ 15,/* 265 */ 15,/* 266 */ 15,/* 267 */ 15,/* 268 */ 15,/* 269 */ 15, | ||||
|   /* 270 */ 15,/* 271 */ 15,/* 272 */ 15,/* 273 */ 15,/* 274 */ 15,/* 275 */ 15,/* 276 */ 15,/* 277 */ 15,/* 278 */ 15,/* 279 */ 15, | ||||
|   /* 280 */ 16,/* 281 */ 16,/* 282 */ 16,/* 283 */ 16,/* 284 */ 16,/* 285 */ 16,/* 286 */ 16,/* 287 */ 16,/* 288 */ 16,/* 289 */ 16, | ||||
|   /* 290 */ 16,/* 291 */ 16,/* 292 */ 16,/* 293 */ 16,/* 294 */ 16,/* 295 */ 16,/* 296 */ 16,/* 297 */ 16,/* 298 */ 16,/* 299 */ 16, | ||||
|   /* 300 */ 16,/* 301 */ 16,/* 302 */ 16,/* 303 */ 16,/* 304 */ 16,/* 305 */ 16,/* 306 */ 16,/* 307 */ 16,/* 308 */ 16,/* 309 */ 16, | ||||
|   /* 310 */ 17,/* 311 */ 17,/* 312 */ 17,/* 313 */ 17,/* 314 */ 17,/* 315 */ 17,/* 316 */ 17,/* 317 */ 17,/* 318 */ 17,/* 319 */ 17, | ||||
|   /* 320 */ 17,/* 321 */ 17,/* 322 */ 17,/* 323 */ 17,/* 324 */ 17,/* 325 */ 17,/* 326 */ 17,/* 327 */ 17,/* 328 */ 17,/* 329 */ 17, | ||||
|   /* 330 */ 17,/* 331 */ 17,/* 332 */ 17,/* 333 */ 17,/* 334 */ 17,/* 335 */ 17,/* 336 */ 17,/* 337 */ 17,/* 338 */ 17,/* 339 */ 17, | ||||
|   /* 340 */ 17,/* 341 */ 17,/* 342 */ 17,/* 343 */ 17,/* 344 */ 17,/* 345 */ 17,/* 346 */ 17,/* 347 */ 17,/* 348 */ 17,/* 349 */ 17, | ||||
|   /* 350 */ 17,/* 351 */ 17,/* 352 */ 17,/* 353 */ 17,/* 354 */ 17,/* 355 */ 17,/* 356 */ 17,/* 357 */ 17,/* 358 */ 17,/* 359 */ 17, | ||||
|   /* 360 */ 17,/* 361 */ 17,/* 362 */ 17,/* 363 */ 17,/* 364 */ 17,/* 365 */ 17,/* 366 */ 17,/* 367 */ 17,/* 368 */ 17,/* 369 */ 17, | ||||
|   /* 370 */ 17,/* 371 */ 17,/* 372 */ 17,/* 373 */ 17,/* 374 */ 17,/* 375 */ 17,/* 376 */ 17,/* 377 */ 17,/* 378 */ 17,/* 379 */ 17, | ||||
|   /* 380 */ 17,/* 381 */ 17,/* 382 */ 17,/* 383 */ 17,/* 384 */ 17,/* 385 */ 17,/* 386 */ 17,/* 387 */ 17,/* 388 */ 17,/* 389 */ 17, | ||||
|   /* 390 */ 17,/* 391 */ 17,/* 392 */ 17,/* 393 */ 17,/* 394 */ 17,/* 395 */ 17,/* 396 */ 17,/* 397 */ 17,/* 398 */ 17,/* 399 */ 17, | ||||
|   /* 400 */ 17,/* 401 */ 17,/* 402 */ 17,/* 403 */ 17,/* 404 */ 17,/* 405 */ 17,/* 406 */ 17,/* 407 */ 17,/* 408 */ 17,/* 409 */ 17, | ||||
|   /* 410 */ 17,/* 411 */ 17,/* 412 */ 17,/* 413 */ 17,/* 414 */ 17,/* 415 */ 17,/* 416 */ 17,/* 417 */ 17,/* 418 */ 17,/* 419 */ 17, | ||||
|   /* 420 */ 17,/* 421 */ 17,/* 422 */ 17,/* 423 */ 17,/* 424 */ 17,/* 425 */ 17,/* 426 */ 17,/* 427 */ 17,/* 428 */ 17,/* 429 */ 17, | ||||
|   /* 430 */ 17,/* 431 */ 17,/* 432 */ 17,/* 433 */ 17,/* 434 */ 17,/* 435 */ 17,/* 436 */ 17,/* 437 */ 17,/* 438 */ 17,/* 439 */ 17, | ||||
|   /* 440 */ 17,/* 441 */ 17,/* 442 */ 17,/* 443 */ 17,/* 444 */ 17,/* 445 */ 17,/* 446 */ 17,/* 447 */ 17,/* 448 */ 17,/* 449 */ 17, | ||||
|   /* 450 */ 18,/* 451 */ 18,/* 452 */ 18,/* 453 */ 18,/* 454 */ 18,/* 455 */ 18,/* 456 */ 18,/* 457 */ 18,/* 458 */ 18,/* 459 */ 18, | ||||
|   /* 460 */ 18,/* 461 */ 18,/* 462 */ 18,/* 463 */ 18,/* 464 */ 18,/* 465 */ 18,/* 466 */ 18,/* 467 */ 18,/* 468 */ 18,/* 469 */ 18, | ||||
|   /* 470 */ 18,/* 471 */ 18,/* 472 */ 18,/* 473 */ 18,/* 474 */ 18,/* 475 */ 18,/* 476 */ 18,/* 477 */ 18,/* 478 */ 18,/* 479 */ 18, | ||||
|   /* 480 */ 18,/* 481 */ 18,/* 482 */ 18,/* 483 */ 18,/* 484 */ 18,/* 485 */ 18,/* 486 */ 18,/* 487 */ 18,/* 488 */ 18,/* 489 */ 18, | ||||
|   /* 490 */ 18,/* 491 */ 18,/* 492 */ 18,/* 493 */ 18,/* 494 */ 18,/* 495 */ 18,/* 496 */ 18,/* 497 */ 18,/* 498 */ 18,/* 499 */ 18, | ||||
|   /* 500 */ 18,/* 501 */ 18,/* 502 */ 18,/* 503 */ 18,/* 504 */ 18,/* 505 */ 18,/* 506 */ 18,/* 507 */ 18,/* 508 */ 18,/* 509 */ 18, | ||||
|   /* 510 */ 18,/* 511 */ 18,/* 512 */ 18,/* 513 */ 18,/* 514 */ 18,/* 515 */ 18,/* 516 */ 18,/* 517 */ 18,/* 518 */ 18,/* 519 */ 18, | ||||
|   /* 520 */ 18,/* 521 */ 18,/* 522 */ 18,/* 523 */ 18,/* 524 */ 18,/* 525 */ 18,/* 526 */ 18,/* 527 */ 18,/* 528 */ 18,/* 529 */ 18, | ||||
|   /* 530 */ 18,/* 531 */ 18,/* 532 */ 18,/* 533 */ 18,/* 534 */ 18,/* 535 */ 18,/* 536 */ 18,/* 537 */ 18,/* 538 */ 18,/* 539 */ 18, | ||||
|   /* 540 */ 18,/* 541 */ 18,/* 542 */ 18,/* 543 */ 18,/* 544 */ 18,/* 545 */ 18,/* 546 */ 18,/* 547 */ 18,/* 548 */ 18,/* 549 */ 18, | ||||
|   /* 550 */ 18,/* 551 */ 18,/* 552 */ 18,/* 553 */ 18,/* 554 */ 18,/* 555 */ 18,/* 556 */ 18,/* 557 */ 18,/* 558 */ 18,/* 559 */ 18, | ||||
|   /* 560 */ 18,/* 561 */ 18,/* 562 */ 18,/* 563 */ 18,/* 564 */ 18,/* 565 */ 18,/* 566 */ 18,/* 567 */ 18,/* 568 */ 18,/* 569 */ 18, | ||||
|   /* 570 */ 18,/* 571 */ 18,/* 572 */ 18,/* 573 */ 18,/* 574 */ 18,/* 575 */ 18,/* 576 */ 18,/* 577 */ 18,/* 578 */ 18,/* 579 */ 18, | ||||
|   /* 580 */ 18,/* 581 */ 18,/* 582 */ 18,/* 583 */ 18,/* 584 */ 18,/* 585 */ 18,/* 586 */ 18,/* 587 */ 18, | ||||
|   /* 588 */ 19,/* 589 */ 19,/* 590 */ 19,/* 591 */ 19,/* 592 */ 19,/* 593 */ 19,/* 594 */ 19,/* 595 */ 19,/* 596 */ 19,/* 597 */ 19, | ||||
|   /* 598 */ 19,/* 599 */ 19,/* 600 */ 19,/* 601 */ 19,/* 602 */ 19,/* 603 */ 19,/* 604 */ 19,/* 605 */ 19,/* 606 */ 19,/* 607 */ 19, | ||||
|   /* 608 */ 19,/* 609 */ 19,/* 610 */ 19,/* 611 */ 19,/* 612 */ 19,/* 613 */ 19,/* 614 */ 19,/* 615 */ 19,/* 616 */ 19,/* 617 */ 19, | ||||
|   /* 618 */ 19,/* 619 */ 19,/* 620 */ 19,/* 621 */ 19,/* 622 */ 19,/* 623 */ 19,/* 624 */ 19,/* 625 */ 19,/* 626 */ 19,/* 627 */ 19, | ||||
|   /* 628 */ 19,/* 629 */ 19,/* 630 */ 19,/* 631 */ 19,/* 632 */ 19,/* 633 */ 19,/* 634 */ 19,/* 635 */ 19,/* 636 */ 19,/* 637 */ 19, | ||||
|   /* 638 */ 19,/* 639 */ 19,/* 640 */ 19,/* 641 */ 19,/* 642 */ 19,/* 643 */ 19,/* 644 */ 19,/* 645 */ 19,/* 646 */ 19,/* 647 */ 19, | ||||
|   /* 648 */ 19,/* 649 */ 19 | ||||
| }; | ||||
|  | ||||
| #define FREQ_50MHZ (50) | ||||
| #define FREQ_TO_IDX_0_TO_49MHZ (0) | ||||
| #define FREQ_TO_IDX_650_TO_1800MHZ (20) | ||||
|  | ||||
| int r820t_freq_get_idx(uint32_t freq_mhz) | ||||
| { | ||||
|   uint32_t freq_mhz_fix; | ||||
|  | ||||
|   if(freq_mhz < FREQ_50MHZ) | ||||
|   { | ||||
|     /* Frequency Less than 50MHz */ | ||||
|     return FREQ_TO_IDX_0_TO_49MHZ; | ||||
|   }else | ||||
|   { | ||||
|     /* Frequency Between 50 to 649MHz use table */ | ||||
|     /* Fix the frequency for the table */ | ||||
|     freq_mhz_fix = freq_mhz - FREQ_50MHZ; | ||||
|     if(freq_mhz_fix < FREQ_TO_IDX_SIZE) | ||||
|     { | ||||
|  | ||||
|       return freq_to_idx[freq_mhz_fix]; | ||||
|     }else | ||||
|     { | ||||
|       /* Frequency Between 650 to 1800MHz */ | ||||
|       return FREQ_TO_IDX_650_TO_1800MHZ; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| static inline bool r820t_is_power_enabled(void) | ||||
| { | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Write regs 5 to 32 (R820T_INIT_NB_REGS values) using data parameter and write last reg to 0 | ||||
|  */ | ||||
| void airspy_r820t_write_init(r820t_priv_t *priv, const uint8_t* data) | ||||
| { | ||||
|   for (int i = 0; i < R820T_INIT_NB_REGS; i++) { | ||||
|     priv->write_reg(i+REG_SHADOW_START, data[i], priv->ctx); | ||||
|   } | ||||
|   priv->write_reg(0x1F, 0, priv->ctx); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Read from one or more contiguous registers. data[0] should be the first | ||||
|  * register number, one or more values follow. | ||||
|  */ | ||||
|  const uint8_t lut[16] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, | ||||
|       0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf }; | ||||
|  | ||||
| static uint8_t r82xx_bitrev(uint8_t byte) | ||||
| { | ||||
|  return (lut[byte & 0xf] << 4) | lut[byte >> 4]; | ||||
| } | ||||
|  | ||||
| static int r820t_write_reg(r820t_priv_t *priv, uint8_t reg, uint8_t val) | ||||
| { | ||||
|   if (r820t_read_cache_reg(priv, reg) == val) | ||||
|     return 0; | ||||
|   priv->write_reg(reg, val, priv->ctx); | ||||
|   priv->regs[reg - REG_SHADOW_START] = val; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| static int r820t_read_cache_reg(r820t_priv_t *priv, int reg) | ||||
| { | ||||
|   reg -= REG_SHADOW_START; | ||||
|  | ||||
|   if (reg >= 0 && reg < NUM_REGS) | ||||
|     return priv->regs[reg]; | ||||
|   else | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| static int r820t_write_reg_mask(r820t_priv_t *priv, uint8_t reg, uint8_t val, uint8_t bit_mask) | ||||
| { | ||||
|   int rc = r820t_read_cache_reg(priv, reg); | ||||
|  | ||||
|   if (rc < 0) | ||||
|     return rc; | ||||
|  | ||||
|   val = (rc & ~bit_mask) | (val & bit_mask); | ||||
|  | ||||
|   return r820t_write_reg(priv, reg, val); | ||||
| } | ||||
|  | ||||
| static int r820t_read(r820t_priv_t *priv, uint8_t *val, int len) | ||||
| { | ||||
|   /* reg not used and assumed to be always 0 because start from reg0 to reg0+len */ | ||||
|   priv->read_reg(val, len, priv->ctx); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * r820t tuning logic | ||||
|  */ | ||||
| #ifdef OPTIM_SET_MUX | ||||
| int r820t_set_mux_freq_idx = -1; /* Default set to invalid value in order to force set_mux */ | ||||
| #endif | ||||
|  | ||||
| /* | ||||
| "inspired by Mauro Carvalho Chehab set_mux technique" | ||||
| https://stuff.mit.edu/afs/sipb/contrib/linux/drivers/media/tuners/r820t.c | ||||
| part of r820t_set_mux() (set tracking filter) | ||||
| */ | ||||
| static int r820t_set_tf(r820t_priv_t *priv, uint32_t freq) | ||||
| { | ||||
|   const struct r820t_freq_range *range; | ||||
|   int freq_idx; | ||||
|   int rc = 0; | ||||
|  | ||||
|   /* Get the proper frequency range in MHz instead of Hz */ | ||||
|   /* Fast divide freq by 1000000 */ | ||||
|   freq = (uint32_t)((uint64_t)freq * 4295 >> 32); | ||||
|  | ||||
|   freq_idx = r820t_freq_get_idx(freq); | ||||
|   range = &freq_ranges[freq_idx]; | ||||
|  | ||||
|   /* Only reconfigure mux freq if modified vs previous range */ | ||||
| #ifdef OPTIM_SET_MUX | ||||
|   if(freq_idx != r820t_set_mux_freq_idx) | ||||
|   { | ||||
| #endif | ||||
|     /* Open Drain */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x17, range->open_d, 0x08); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* RF_MUX,Polymux */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x1a, range->rf_mux_ploy, 0xc3); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* TF BAND */ | ||||
|     rc = r820t_write_reg(priv, 0x1b, range->tf_c); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* XTAL CAP & Drive */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x10, 0x08, 0x0b); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     rc = r820t_write_reg_mask(priv, 0x08, 0x00, 0x3f); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     rc = r820t_write_reg_mask(priv, 0x09, 0x00, 0x3f); | ||||
| #ifdef OPTIM_SET_MUX | ||||
|   } | ||||
|   r820t_set_mux_freq_idx = freq_idx; | ||||
| #endif | ||||
|  | ||||
|   return rc; | ||||
| } | ||||
|  | ||||
| int r820t_set_pll(r820t_priv_t *priv, uint32_t freq) | ||||
| { | ||||
|   const uint32_t vco_min = 1770000000; | ||||
|   const uint32_t vco_max = 3900000000; | ||||
|   uint32_t ref = priv->xtal_freq >> 1; | ||||
|  | ||||
|   int rc; | ||||
|   uint32_t div_num; | ||||
|   uint32_t vco; | ||||
|   uint32_t rem; | ||||
|   uint32_t mask; | ||||
|   uint16_t sdm; | ||||
|   uint8_t nint; | ||||
|   uint8_t ni; | ||||
|   uint8_t si; | ||||
|   uint8_t div_found; | ||||
|  | ||||
|   /* Find a suitable divider */ | ||||
|   div_found = 0; | ||||
|   for (div_num = 0; div_num <= 5; div_num++) | ||||
|   { | ||||
|     vco = freq << (div_num + 1); | ||||
|     if (vco >= vco_min && vco <= vco_max) | ||||
|     { | ||||
|       div_found = 1; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!div_found) | ||||
|     return -1; | ||||
|  | ||||
|   vco += ref >> 16; | ||||
|   ref <<= 8; | ||||
|   mask = 1 << 23; | ||||
|   rem = 0; | ||||
|   while (mask > 0 && vco > 0) | ||||
|   { | ||||
|     if (vco >= ref) | ||||
|     { | ||||
|       rem |= mask; | ||||
|       vco -= ref; | ||||
|     } | ||||
|     ref >>= 1; | ||||
|     mask >>= 1; | ||||
|   } | ||||
|  | ||||
|   nint = rem >> 16; | ||||
|   sdm = rem & 0xffff; | ||||
|  | ||||
|   nint -= 13; | ||||
|   ni = nint >> 2; | ||||
|   si = nint & 3; | ||||
|  | ||||
|   /* Set the phase splitter */ | ||||
|   rc = r820t_write_reg_mask(priv, 0x10, (uint8_t) (div_num << 5), 0xe0); | ||||
|   if(rc < 0) | ||||
|     return rc; | ||||
|  | ||||
|   /* Set the integer part of the PLL */ | ||||
|   rc = r820t_write_reg(priv, 0x14, (uint8_t) (ni + (si << 6))); | ||||
|   if(rc < 0) | ||||
|     return rc; | ||||
|  | ||||
|   if (sdm == 0) | ||||
|   { | ||||
|     /* Disable SDM */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x12, 0x08, 0x08); | ||||
|     if(rc < 0) | ||||
|       return rc; | ||||
|   } | ||||
|   else | ||||
|   { | ||||
| 	/* Write SDM */ | ||||
|     rc = r820t_write_reg(priv, 0x15, (uint8_t)(sdm & 0xff)); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     rc = r820t_write_reg(priv, 0x16, (uint8_t)(sdm >> 8)); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* Enable SDM */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x12, 0x00, 0x08); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|   } | ||||
|   return rc; | ||||
| } | ||||
|  | ||||
| int r820t_set_freq(r820t_priv_t *priv, uint32_t freq) | ||||
| { | ||||
|   int rc; | ||||
|   uint32_t lo_freq = freq + priv->if_freq; | ||||
|  | ||||
|   rc = r820t_set_tf(priv, freq); | ||||
|   if (rc < 0) | ||||
|     return rc; | ||||
|  | ||||
|   rc = r820t_set_pll(priv, lo_freq); | ||||
|   if (rc < 0) | ||||
|     return rc; | ||||
|  | ||||
|   priv->freq = freq; | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int r820t_set_lna_gain(r820t_priv_t *priv, uint8_t gain_index) | ||||
| { | ||||
|   return r820t_write_reg_mask(priv, 0x05, gain_index, 0x0f); | ||||
| } | ||||
|  | ||||
| int r820t_set_mixer_gain(r820t_priv_t *priv, uint8_t gain_index) | ||||
| { | ||||
|   return r820t_write_reg_mask(priv, 0x07, gain_index, 0x0f); | ||||
| } | ||||
|  | ||||
| int r820t_set_vga_gain(r820t_priv_t *priv, uint8_t gain_index) | ||||
| { | ||||
|   return r820t_write_reg_mask(priv, 0x0c, gain_index, 0x0f); | ||||
| } | ||||
|  | ||||
| int r820t_set_lna_agc(r820t_priv_t *priv, uint8_t value) | ||||
| { | ||||
|   value = value != 0 ? 0x00 : 0x10; | ||||
|   return r820t_write_reg_mask(priv, 0x05, value, 0x10); | ||||
| } | ||||
|  | ||||
| int r820t_set_mixer_agc(r820t_priv_t *priv, uint8_t value) | ||||
| { | ||||
|   value = value != 0 ? 0x10 : 0x00; | ||||
|   return r820t_write_reg_mask(priv, 0x07, value, 0x10); | ||||
| } | ||||
|  | ||||
| /*  | ||||
| "inspired by Mauro Carvalho Chehab calibration technique" | ||||
| https://stuff.mit.edu/afs/sipb/contrib/linux/drivers/media/tuners/r820t.c | ||||
| part of r820t_set_tv_standard() | ||||
| */ | ||||
| int r820t_calibrate(r820t_priv_t *priv) | ||||
| { | ||||
|   int i, rc, cal_code; | ||||
|   uint8_t data[5]; | ||||
|  | ||||
|   for (i = 0; i < 5; i++) | ||||
|   { | ||||
|     /* Set filt_cap */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x0b, 0x08, 0x60); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* set cali clk =on */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x0f, 0x04, 0x04); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* X'tal cap 0pF for PLL */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x10, 0x00, 0x03); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     rc = r820t_set_pll(priv, CALIBRATION_LO * 1000); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* Start Trigger */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x0b, 0x10, 0x10); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     std::this_thread::sleep_for(std::chrono::milliseconds(10)); | ||||
|  | ||||
|     /* Stop Trigger */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x0b, 0x00, 0x10); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* set cali clk =off */ | ||||
|     rc = r820t_write_reg_mask(priv, 0x0f, 0x00, 0x04); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     /* Check if calibration worked */ | ||||
|     rc = r820t_read(priv, data, sizeof(data)); | ||||
|     if (rc < 0) | ||||
|       return rc; | ||||
|  | ||||
|     cal_code = data[4] & 0x0f; | ||||
|     if (cal_code && cal_code != 0x0f) | ||||
|       return 0; | ||||
|   } | ||||
|  | ||||
|   return -1; | ||||
| } | ||||
|  | ||||
| int r820t_init(r820t_priv_t *priv, const uint32_t if_freq, r820t_write_reg_f write_reg, r820t_read_f read_reg, void* ctx) | ||||
| { | ||||
|   int rc; | ||||
|   uint32_t saved_freq; | ||||
|  | ||||
|   r820t_state_standby = 0; | ||||
|   priv->if_freq = if_freq; | ||||
|   priv->write_reg = write_reg; | ||||
|   priv->read_reg = read_reg; | ||||
|   priv->ctx = ctx; | ||||
|   /* Initialize registers */ | ||||
|   airspy_r820t_write_init(priv, priv->regs); | ||||
|  | ||||
|   r820t_set_freq(priv, priv->freq); | ||||
|  | ||||
|   /* Calibrate the IF filter */ | ||||
|   saved_freq = priv->freq; | ||||
|   rc = r820t_calibrate(priv); | ||||
|   priv->freq = saved_freq; | ||||
|   if (rc < 0) | ||||
|   { | ||||
|     saved_freq = priv->freq; | ||||
|     r820t_calibrate(priv); | ||||
|     priv->freq = saved_freq; | ||||
|   } | ||||
|  | ||||
|   /* Restore freq as it has been modified by r820t_calibrate() */ | ||||
|   rc = r820t_set_freq(priv, priv->freq); | ||||
|   return rc; | ||||
| } | ||||
|  | ||||
| void r820t_set_if_bandwidth(r820t_priv_t *priv, uint8_t bw) | ||||
| { | ||||
|     const uint8_t modes[] = { 0xE0, 0x80, 0x60, 0x00 }; | ||||
|     const uint8_t opt[] = { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; | ||||
|     uint8_t a = 0xB0 | opt[bw & 0x0F]; | ||||
|     uint8_t b = 0x0F | modes[bw >> 4]; | ||||
|     r820t_write_reg(priv, 0x0A, a); | ||||
|     r820t_write_reg(priv, 0x0B, b); | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| /* | ||||
|  * Copyright 2013-2016 Benjamin Vernoux <bvernoux@airspy.com> | ||||
|  * | ||||
|  * This file is part of AirSpy. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation; either version 2, or (at your option) | ||||
|  * any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; see the file COPYING.  If not, write to | ||||
|  * the Free Software Foundation, Inc., 51 Franklin Street, | ||||
|  * Boston, MA 02110-1301, USA. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
|  | ||||
| #define REG_SHADOW_START 5 | ||||
| #define NUM_REGS 30 | ||||
|  | ||||
| /* R820T Clock */ | ||||
| #define CALIBRATION_LO 88000 | ||||
|  | ||||
| typedef void (*r820t_write_reg_f)(uint8_t reg, uint8_t value, void* ctx); | ||||
| typedef void (*r820t_read_f)(uint8_t* data, int len, void* ctx); | ||||
|  | ||||
| typedef struct | ||||
| { | ||||
|   uint32_t xtal_freq; /* XTAL_FREQ_HZ */ | ||||
|   uint32_t freq; | ||||
|   uint32_t if_freq; | ||||
|   uint8_t regs[NUM_REGS]; | ||||
|   uint16_t padding; | ||||
|  | ||||
|   r820t_write_reg_f write_reg; | ||||
|   r820t_read_f read_reg; | ||||
|   void* ctx; | ||||
|  | ||||
| } r820t_priv_t; | ||||
|  | ||||
| void airspy_r820t_write_single(r820t_priv_t *priv, uint8_t reg, uint8_t val); | ||||
| uint8_t airspy_r820t_read_single(r820t_priv_t *priv, uint8_t reg); | ||||
|  | ||||
| int r820t_init(r820t_priv_t *priv, const uint32_t if_freq, r820t_write_reg_f write_reg, r820t_read_f read_reg, void* ctx); | ||||
| int r820t_set_freq(r820t_priv_t *priv, uint32_t freq); | ||||
| int r820t_set_lna_gain(r820t_priv_t *priv, uint8_t gain_index); | ||||
| int r820t_set_mixer_gain(r820t_priv_t *priv, uint8_t gain_index); | ||||
| int r820t_set_vga_gain(r820t_priv_t *priv, uint8_t gain_index); | ||||
| int r820t_set_lna_agc(r820t_priv_t *priv, uint8_t value); | ||||
| int r820t_set_mixer_agc(r820t_priv_t *priv, uint8_t value); | ||||
| void r820t_set_if_bandwidth(r820t_priv_t *priv, uint8_t bw); | ||||
| @@ -10,7 +10,6 @@ | ||||
| #include <libbladeRF.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <algorithm> | ||||
| #include <utils/optionlist.h> | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| @@ -38,10 +37,6 @@ public: | ||||
|     BladeRFSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         // Define clocks | ||||
|         clocks.define("onboard", "On-Board", CLOCK_SELECT_ONBOARD); | ||||
|         clocks.define("external", "External", CLOCK_SELECT_EXTERNAL); | ||||
|  | ||||
|         sampleRate = 1000000.0; | ||||
|  | ||||
|         handler.ctx = this; | ||||
| @@ -272,15 +267,6 @@ public: | ||||
|         } | ||||
|         config.release(true); | ||||
|  | ||||
|         // Load clock source | ||||
|         clkId = clocks.keyId("onboard"); | ||||
|         if (config.conf["devices"][selectedSerial].contains("clock")) { | ||||
|             std::string clkStr = config.conf["devices"][selectedSerial]["clock"]; | ||||
|             if (clocks.keyExists(clkStr)) { | ||||
|                 clkId = clocks.keyId(clkStr); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Load gain mode | ||||
|         if (config.conf["devices"][selectedSerial].contains("gainMode")) { | ||||
|             std::string gm = config.conf["devices"][selectedSerial]["gainMode"]; | ||||
| @@ -378,7 +364,6 @@ private: | ||||
|         if (_this->bufferSize < 1024) { _this->bufferSize = 1024; } | ||||
|  | ||||
|         // Setup device parameters | ||||
|         _this->setClockSource(_this->clocks[_this->clkId]); | ||||
|         bladerf_set_sample_rate(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->sampleRate, NULL); | ||||
|         bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq); | ||||
|         bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ? std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL); | ||||
| @@ -501,19 +486,6 @@ private: | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Clock Source"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_balderf_clk_sel_", _this->name), &_this->clkId, _this->clocks.txt)) { | ||||
|             if (_this->running) { | ||||
|                 _this->setClockSource(_this->clocks[_this->clkId]); | ||||
|             } | ||||
|             if (_this->selectedSerial != "") { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["clock"] = _this->clocks.key(_this->clkId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // General config BS | ||||
|         SmGui::LeftLabel("Gain control mode"); | ||||
|         SmGui::FillWidth(); | ||||
| @@ -565,15 +537,6 @@ private: | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void setClockSource(bladerf_clock_select clk) { | ||||
|         if (selectedBladeType == BLADERF_TYPE_V1) { | ||||
|             bladerf_set_smb_mode(openDev, (clk == CLOCK_SELECT_EXTERNAL) ? BLADERF_SMB_MODE_INPUT : BLADERF_SMB_MODE_DISABLED); | ||||
|         } | ||||
|         else { | ||||
|             bladerf_set_clock_select(openDev, clk); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         int16_t* buffer = new int16_t[bufferSize * 2]; | ||||
|         bladerf_metadata meta; | ||||
| @@ -602,7 +565,6 @@ private: | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int bwId = 0; | ||||
|     int clkId = 0; | ||||
|     int chanId = 0; | ||||
|     int gainMode = 0; | ||||
|     bool streamingEnabled = false; | ||||
| @@ -618,8 +580,8 @@ private: | ||||
|     std::string sampleRatesTxt; | ||||
|     std::vector<uint64_t> bandwidths; | ||||
|     std::string bandwidthsTxt; | ||||
|  | ||||
|     std::string channelNamesTxt; | ||||
|     OptionList<std::string, bladerf_clock_select> clocks; | ||||
|  | ||||
|     int bufferSize; | ||||
|     struct bladerf_stream* rxStream; | ||||
|   | ||||
| @@ -1,21 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(fobossdr_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/lib/") | ||||
|     target_include_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/include/") | ||||
|     target_link_libraries(fobossdr_source PRIVATE fobos) | ||||
| else (MSVC) | ||||
|     find_package(PkgConfig) | ||||
|  | ||||
|     pkg_check_modules(LIBFOBOS REQUIRED libfobos) | ||||
|  | ||||
|     target_include_directories(fobossdr_source PRIVATE ${LIBFOBOS_INCLUDE_DIRS}) | ||||
|     target_link_directories(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARY_DIRS}) | ||||
|     target_link_libraries(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARIES}) | ||||
| endif () | ||||
| @@ -1,560 +0,0 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <atomic> | ||||
| #include <fobos.h> | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "fobossdr_source", | ||||
|     /* Description:     */ "FobosSDR Source Module", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| ConfigManager config; | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| // Work around for the fobos API not including | ||||
| #define FOBOS_LNA_GAIN_MIN  1 | ||||
| #define FOBOS_LNA_GAIN_MAX  3 | ||||
| #define FOBOS_VGA_GAIN_MIN  0 | ||||
| #define FOBOS_VGA_GAIN_MAX  31 | ||||
|  | ||||
| class FobosSDRSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     FobosSDRSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 50000000.0; | ||||
|  | ||||
|         // Initialize the DDC | ||||
|         ddc.init(&ddcIn, 50e6, 50e6, 50e6, 0.0); | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &ddc.out; | ||||
|  | ||||
|         // Refresh devices | ||||
|         refresh(); | ||||
|  | ||||
|         // Select device from config | ||||
|         config.acquire(); | ||||
|         std::string devSerial = config.conf["device"]; | ||||
|         config.release(); | ||||
|         select(devSerial); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("FobosSDR", &handler); | ||||
|     } | ||||
|  | ||||
|     ~FobosSDRSourceModule() { | ||||
|         // Nothing to do | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
|     enum Port { | ||||
|         PORT_RF, | ||||
|         PORT_HF1, | ||||
|         PORT_HF2 | ||||
|     }; | ||||
|  | ||||
| private: | ||||
|     std::string getBandwdithScaled(double bw) { | ||||
|         char buf[1024]; | ||||
|         if (bw >= 1000000.0) { | ||||
|             sprintf(buf, "%.1lfMHz", bw / 1000000.0); | ||||
|         } | ||||
|         else if (bw >= 1000.0) { | ||||
|             sprintf(buf, "%.1lfKHz", bw / 1000.0); | ||||
|         } | ||||
|         else { | ||||
|             sprintf(buf, "%.1lfHz", bw); | ||||
|         } | ||||
|         return std::string(buf); | ||||
|     } | ||||
|  | ||||
|     void refresh() { | ||||
|         devices.clear(); | ||||
|          | ||||
|         // Get device list | ||||
|         char serials[1024]; | ||||
|         memset(serials, 0, sizeof(serials)); | ||||
|         int devCount = fobos_rx_list_devices(serials); | ||||
|         if (devCount < 0) { | ||||
|             flog::error("Failed to get device list: {}", devCount); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If no device, give up | ||||
|         if (!devCount) { return; } | ||||
|  | ||||
|         // Generate device entries | ||||
|         const char* _serials = serials; | ||||
|         int index = 0; | ||||
|         while (*_serials) { | ||||
|             // Read serial until space | ||||
|             std::string serial = ""; | ||||
|             while (*_serials) { | ||||
|                 // Get a character | ||||
|                 char c = *(_serials++); | ||||
|  | ||||
|                 // If it's a space, we're done | ||||
|                 if (c == ' ') { break; } | ||||
|  | ||||
|                 // Otherwise, add it to the string | ||||
|                 serial += c; | ||||
|             } | ||||
|  | ||||
|             // Create entry | ||||
|             devices.define(serial, serial, index++); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devices.empty()) { | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial was not found, select the first available serial | ||||
|         if (!devices.keyExists(serial)) { | ||||
|             select(devices.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the ID in the list | ||||
|         int id = devices.keyId(serial); | ||||
|         selectedDevId = devices[id]; | ||||
|  | ||||
|         // Open the device | ||||
|         fobos_dev_t* dev; | ||||
|         int err = fobos_rx_open(&dev, selectedDevId); | ||||
|         if (err) { | ||||
|             flog::error("Failed to open device: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get a list of supported samplerates | ||||
|         double srList[128]; | ||||
|         unsigned int srCount; | ||||
|         err = fobos_rx_get_samplerates(dev, srList, &srCount); | ||||
|         if (err) { | ||||
|             flog::error("Failed to get samplerate list: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Generate samplerate list | ||||
|         samplerates.clear(); | ||||
|         for (int i = 0; i < srCount; i++) { | ||||
|             std::string str = getBandwdithScaled(srList[i]); | ||||
|             samplerates.define(srList[i], str, srList[i]); | ||||
|         } | ||||
|  | ||||
|         // Add some custom samplerates | ||||
|         samplerates.define(5e6, "5.0MHz", 5e6); | ||||
|         samplerates.define(2.5e6, "2.5MHz", 2.5e6); | ||||
|         samplerates.define(1.25e6, "1.25MHz", 1.25e6); | ||||
|  | ||||
|         // Define the ports | ||||
|         ports.clear(); | ||||
|         ports.define("rf", "RF", PORT_RF); | ||||
|         ports.define("hf1", "HF1", PORT_HF1); | ||||
|         ports.define("hf2", "HF2", PORT_HF2); | ||||
|  | ||||
|         // Define clock sources | ||||
|         clockSources.clear(); | ||||
|         clockSources.define("internal", "Internal", 0); | ||||
|         clockSources.define("external", "External", 1); | ||||
|  | ||||
|         // Close the device | ||||
|         fobos_rx_close(dev); | ||||
|  | ||||
|         // Save serial number | ||||
|         selectedSerial = serial; | ||||
|         devId = id; | ||||
|  | ||||
|         // Load default options | ||||
|         sampleRate = 50e6; | ||||
|         srId = samplerates.valueId(sampleRate); | ||||
|         port = PORT_RF; | ||||
|         portId = ports.valueId(port); | ||||
|         clkSrcId = clockSources.nameId("Internal"); | ||||
|         lnaGain = 0; | ||||
|         vgaGain = 0; | ||||
|  | ||||
|         // Load config | ||||
|         config.acquire(); | ||||
|         if (config.conf["devices"][selectedSerial].contains("samplerate")) { | ||||
|             int desiredSr = config.conf["devices"][selectedSerial]["samplerate"]; | ||||
|             if (samplerates.keyExists(desiredSr)) { | ||||
|                 srId = samplerates.keyId(desiredSr); | ||||
|                 sampleRate = samplerates[srId]; | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("port")) { | ||||
|             std::string desiredPort = config.conf["devices"][selectedSerial]["port"]; | ||||
|             if (ports.keyExists(desiredPort)) { | ||||
|                 portId = ports.keyId(desiredPort); | ||||
|                 port = ports[portId]; | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("clkSrc")) { | ||||
|             std::string desiredClkSrc = config.conf["devices"][selectedSerial]["clkSrc"]; | ||||
|             if (clockSources.keyExists(desiredClkSrc)) { | ||||
|                 clkSrcId = clockSources.keyId(desiredClkSrc); | ||||
|             } | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("lnaGain")) { | ||||
|             lnaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["lnaGain"], FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX); | ||||
|         } | ||||
|         if (config.conf["devices"][selectedSerial].contains("vgaGain")) { | ||||
|             vgaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["vgaGain"], FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX); | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         // Update the samplerate | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("FobosSDRSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         flog::info("FobosSDRSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|  | ||||
|         // Open the device | ||||
|         int err = fobos_rx_open(&_this->openDev, _this->selectedDevId); | ||||
|         if (err) { | ||||
|             flog::error("Failed to open device: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the selected port | ||||
|         _this->port = _this->ports[_this->portId]; | ||||
|  | ||||
|         // Configure the device | ||||
|         double actualSr, actualFreq; | ||||
|         fobos_rx_set_samplerate(_this->openDev, (_this->sampleRate >= 50e6) ? _this->sampleRate : 50e6, &actualSr); | ||||
|         fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq); | ||||
|         fobos_rx_set_direct_sampling(_this->openDev, _this->port != PORT_RF); | ||||
|         fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]); | ||||
|         fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|         fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|  | ||||
|         // Configure the DDC | ||||
|         if (_this->port == PORT_RF && _this->sampleRate >= 50e6) { | ||||
|             // Set the frequency | ||||
|             fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq); | ||||
|         } | ||||
|         else if (_this->port == PORT_RF) { | ||||
|             // Set the frequency | ||||
|             fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq); | ||||
|  | ||||
|             // Configure and start the DDC for decimation only | ||||
|             _this->ddc.setInSamplerate(actualSr); | ||||
|             _this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate); | ||||
|             _this->ddc.setOffset(0.0); | ||||
|             _this->ddc.start(); | ||||
|         } | ||||
|         else { | ||||
|             // Configure and start the DDC | ||||
|             _this->ddc.setInSamplerate(actualSr); | ||||
|             _this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate); | ||||
|             _this->ddc.setOffset(_this->freq); | ||||
|             _this->ddc.start(); | ||||
|         } | ||||
|  | ||||
|         // Compute buffer size (Lower than usual, but it's a workaround for their API having broken streaming) | ||||
|         _this->bufferSize = _this->sampleRate / 400.0; | ||||
|  | ||||
|         // Start streaming | ||||
|         err = fobos_rx_start_sync(_this->openDev, _this->bufferSize); | ||||
|         if (err) { | ||||
|             flog::error("Failed to start stream: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Start worker | ||||
|         _this->run = true; | ||||
|         _this->workerThread = std::thread(&FobosSDRSourceModule::worker, _this); | ||||
|          | ||||
|         _this->running = true; | ||||
|         flog::info("FobosSDRSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|  | ||||
|         // Stop worker | ||||
|         _this->run = false; | ||||
|         if (_this->port == PORT_RF && _this->sampleRate >= 50e6) { | ||||
|             _this->ddc.out.stopWriter(); | ||||
|             if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|             _this->ddc.out.clearWriteStop(); | ||||
|         } | ||||
|         else { | ||||
|             _this->ddcIn.stopWriter(); | ||||
|             if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|             _this->ddcIn.clearWriteStop(); | ||||
|         } | ||||
|  | ||||
|         // Stop streaming | ||||
|         fobos_rx_stop_sync(_this->openDev); | ||||
|  | ||||
|         // Stop the DDC | ||||
|         _this->ddc.stop(); | ||||
|  | ||||
|         // Close the device | ||||
|         fobos_rx_close(_this->openDev); | ||||
|  | ||||
|         flog::info("FobosSDRSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             if (_this->port == PORT_RF) { | ||||
|                 double actual; // Dummy, don't care | ||||
|                 fobos_rx_set_frequency(_this->openDev, freq, &actual); | ||||
|             } | ||||
|             else { | ||||
|                 _this->ddc.setOffset(freq); | ||||
|             } | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("FobosSDRSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx; | ||||
|          | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = _this->selectedSerial; | ||||
|             config.release(true); | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { | ||||
|             _this->sampleRate = _this->samplerates.value(_this->srId); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["samplerate"] = _this->samplerates.key(_this->srId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_fobossdr_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Antenna Port"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_port_", _this->name), &_this->portId, _this->ports.txt)) { | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["port"] = _this->ports.key(_this->portId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::LeftLabel("Clock Source"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_fobossdr_clk_", _this->name), &_this->clkSrcId, _this->clockSources.txt)) { | ||||
|             if (_this->running) { | ||||
|                 fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]); | ||||
|             } | ||||
|             if (!_this->selectedSerial.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["devices"][_this->selectedSerial]["clkSrc"] = _this->clockSources.key(_this->clkSrcId); | ||||
|                 config.release(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_this->port == PORT_RF) { | ||||
|             SmGui::LeftLabel("LNA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_fobossdr_lna_gain_", _this->name), &_this->lnaGain, FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX)) { | ||||
|                 if (_this->running) { | ||||
|                     fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain); | ||||
|                 } | ||||
|                 if (!_this->selectedSerial.empty()) { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerial]["lnaGain"] = _this->lnaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             SmGui::LeftLabel("VGA Gain"); | ||||
|             SmGui::FillWidth(); | ||||
|             if (SmGui::SliderInt(CONCAT("##_fobossdr_vga_gain_", _this->name), &_this->vgaGain, FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX)) { | ||||
|                 if (_this->running) { | ||||
|                     fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain); | ||||
|                 } | ||||
|                 if (!_this->selectedSerial.empty()) { | ||||
|                     config.acquire(); | ||||
|                     config.conf["devices"][_this->selectedSerial]["vgaGain"] = _this->vgaGain; | ||||
|                     config.release(true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         // Select different processing depending on the mode | ||||
|         if (port == PORT_RF && sampleRate >= 50e6) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddc.out.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|                  | ||||
|                 // Send out samples to the core | ||||
|                 if (!ddc.out.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (port == PORT_RF) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|                  | ||||
|                 // Send samples to the DDC | ||||
|                 if (!ddcIn.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (port == PORT_HF1) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|  | ||||
|                 // Null out the HF2 samples | ||||
|                 for (int i = 0; i < sampCount; i++) { | ||||
|                     ddcIn.writeBuf[i].im = 0.0f; | ||||
|                 } | ||||
|                  | ||||
|                 // Send samples to the DDC | ||||
|                 if (!ddcIn.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (port == PORT_HF2) { | ||||
|             while (run) { | ||||
|                 // Read samples | ||||
|                 unsigned int sampCount = 0; | ||||
|                 int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount); | ||||
|                 if (err) { break; } | ||||
|  | ||||
|                 // Null out the HF2 samples | ||||
|                 for (int i = 0; i < sampCount; i++) { | ||||
|                     ddcIn.writeBuf[i].re = 0.0f; | ||||
|                 } | ||||
|                  | ||||
|                 // Send samples to the DDC | ||||
|                 if (!ddcIn.swap(sampCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|  | ||||
|     OptionList<std::string, int> devices; | ||||
|     OptionList<int, double> samplerates; | ||||
|     OptionList<std::string, Port> ports; | ||||
|     OptionList<std::string, int> clockSources; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int portId = 0; | ||||
|     int clkSrcId = 0; | ||||
|     Port port; | ||||
|     int lnaGain = 0; | ||||
|     int vgaGain = 0; | ||||
|     std::string selectedSerial; | ||||
|     int selectedDevId; | ||||
|  | ||||
|     fobos_dev_t* openDev; | ||||
|  | ||||
|     int bufferSize; | ||||
|     std::thread workerThread; | ||||
|     std::atomic<bool> run = false; | ||||
|  | ||||
|     dsp::stream<dsp::complex_t> ddcIn; | ||||
|     dsp::channel::RxVFO ddc; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     json def = json({}); | ||||
|     def["devices"] = json({}); | ||||
|     def["device"] = ""; | ||||
|     config.setPath(core::args["root"].s() + "/fobossdr_config.json"); | ||||
|     config.load(def); | ||||
|     config.enableAutoSave(); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new FobosSDRSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (FobosSDRSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     config.disableAutoSave(); | ||||
|     config.save(); | ||||
| } | ||||
| @@ -304,7 +304,6 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_hackrf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) { | ||||
|             _this->selectBySerial(_this->devList[_this->devId]); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             config.acquire(); | ||||
|             config.conf["device"] = _this->selectedSerial; | ||||
|             config.release(true); | ||||
|   | ||||
| @@ -1,17 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(harogic_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| if (MSVC) | ||||
|     # Lib path | ||||
|     target_link_directories(harogic_source PRIVATE "C:/radio/HTRA_API/x64/htra_api/") | ||||
|     target_include_directories(harogic_source PRIVATE "C:/radio/HTRA_API/x64/htra_api/") | ||||
|     target_link_libraries(harogic_source PRIVATE htra_api) | ||||
| else (MSVC) | ||||
|     target_link_directories(harogic_source PRIVATE "/opt/htraapi/lib/${CMAKE_SYSTEM_PROCESSOR}/") | ||||
|     target_include_directories(harogic_source PRIVATE "/opt/htraapi/inc/") | ||||
|     target_link_libraries(harogic_source PRIVATE htraapi) | ||||
| endif () | ||||
| @@ -1,510 +0,0 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include <htra_api.h> | ||||
| #include <atomic> | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "harogic_source", | ||||
|     /* Description:     */ "harogic Source Module", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| class HarogicSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     HarogicSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 61440000.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 devices | ||||
|         refresh(); | ||||
|  | ||||
|         // Select first (TODO: Select from config) | ||||
|         select(""); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("Harogic", &handler); | ||||
|     } | ||||
|  | ||||
|     ~HarogicSourceModule() { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void refresh() { | ||||
|         devices.clear(); | ||||
|          | ||||
|         // Set up the device parameters | ||||
|         BootProfile_TypeDef profile = {}; | ||||
|         profile.PhysicalInterface = PhysicalInterface_TypeDef::USB; | ||||
|         profile.DevicePowerSupply = DevicePowerSupply_TypeDef::USBPortOnly; | ||||
|  | ||||
|         // Working variables | ||||
|         void* dev; | ||||
|         BootInfo_TypeDef binfo; | ||||
|          | ||||
|         for (int i = 0; i < 128; i++) { | ||||
|             // Attempt to open the device with the given ID | ||||
|             int ret = Device_Open(&dev, i, &profile, &binfo); | ||||
|             if (ret < 0) { break; } | ||||
|  | ||||
|             // Create serial string | ||||
|             char serial[64]; | ||||
|             sprintf(serial, "%" PRIX64, binfo.DeviceInfo.DeviceUID); | ||||
|              | ||||
|             // Add the device to the list | ||||
|             devices.define(serial, serial, i); | ||||
|  | ||||
|             // Close the device | ||||
|             Device_Close(&dev); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devices.empty()) { | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial was not found, select the first available serial | ||||
|         if (!devices.keyExists(serial)) { | ||||
|             select(devices.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the menu ID | ||||
|         devId = devices.keyId(serial); | ||||
|         selectedDevIndex = devices.value(devId); | ||||
|  | ||||
|         // Set up the device parameters | ||||
|         BootProfile_TypeDef bprofile = {}; | ||||
|         bprofile.PhysicalInterface = PhysicalInterface_TypeDef::USB; | ||||
|         bprofile.DevicePowerSupply = DevicePowerSupply_TypeDef::USBPortOnly; | ||||
|  | ||||
|         // Working variables | ||||
|         BootInfo_TypeDef binfo; | ||||
|  | ||||
|         // Attempt to open the device by ID | ||||
|         void* dev; | ||||
|         int ret = Device_Open(&dev, selectedDevIndex, &bprofile, &binfo); | ||||
|         if (ret < 0) { | ||||
|             flog::error("Could not open device: {}", ret); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the default streaming parameters to query some info | ||||
|         IQS_Profile_TypeDef profile; | ||||
|         IQS_ProfileDeInit(&dev, &profile); | ||||
|          | ||||
|         // Compute all available samplerates | ||||
|         samplerates.clear(); | ||||
|         for (int i = 0; i < 8; i++) { | ||||
|             double sr = profile.NativeIQSampleRate_SPS / (double)(1 << i); | ||||
|             char buf[128]; | ||||
|             sprintf(buf, "%.02fMHz", sr / 1e6); | ||||
|             samplerates.define(1 << i, buf, sr); | ||||
|         } | ||||
|  | ||||
|         // Define RX ports | ||||
|         rxPorts.clear(); | ||||
|         rxPorts.define("external", "External", ExternalPort); | ||||
|         rxPorts.define("internal", "Internal", InternalPort); | ||||
|         rxPorts.define("ant", "ANT", ANT_Port); | ||||
|         rxPorts.define("tr", "T/R", TR_Port); | ||||
|         rxPorts.define("swr", "SWR", SWR_Port); | ||||
|         rxPorts.define("int", "INT", INT_Port); | ||||
|  | ||||
|         // Define gain strategies | ||||
|         gainStategies.clear(); | ||||
|         gainStategies.define("lowNoise", "Low Noise", LowNoisePreferred); | ||||
|         gainStategies.define("highLinearity", "High Linearity", HighLinearityPreferred); | ||||
|  | ||||
|         // Define preamplifier modes | ||||
|         preampModes.clear(); | ||||
|         preampModes.define("auto", "Auto", AutoOn); | ||||
|         preampModes.define("off", "Off", ForcedOff); | ||||
|  | ||||
|         // Define LO modes | ||||
|         loModes.clear(); | ||||
|         loModes.define("auto", "Auto", LOOpt_Auto); | ||||
|         loModes.define("speed", "Speed", LOOpt_Speed); | ||||
|         loModes.define("spurs", "Spurs", LOOpt_Spur); | ||||
|         loModes.define("phaseNoise", "Phase Noise", LOOpt_PhaseNoise); | ||||
|  | ||||
|         // Close the device | ||||
|         Device_Close(&dev); | ||||
|  | ||||
|         // TODO: Load configuration | ||||
|         sampleRate = samplerates.value(0); | ||||
|         refLvl = 0; | ||||
|         portId = rxPorts.valueId(ExternalPort); | ||||
|         gainStratId = gainStategies.valueId(LowNoisePreferred); | ||||
|         preampModeId = preampModes.valueId(AutoOn); | ||||
|         ifAgc = false; | ||||
|         loModeId = loModes.valueId(LOOpt_Auto); | ||||
|  | ||||
|         // Update the samplerate | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         // Save serial number | ||||
|         selectedSerial = serial; | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         HarogicSourceModule* _this = (HarogicSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("HarogicSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         HarogicSourceModule* _this = (HarogicSourceModule*)ctx; | ||||
|         flog::info("HarogicSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         HarogicSourceModule* _this = (HarogicSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|  | ||||
|         // Set up the device parameters | ||||
|         BootProfile_TypeDef bprofile = {}; | ||||
|         bprofile.PhysicalInterface = PhysicalInterface_TypeDef::USB; | ||||
|         bprofile.DevicePowerSupply = DevicePowerSupply_TypeDef::USBPortAndPowerPort; | ||||
|  | ||||
|         // Working variables | ||||
|         BootInfo_TypeDef binfo; | ||||
|  | ||||
|         // Attempt to open the device by ID | ||||
|         int ret = Device_Open(&_this->openDev, _this->selectedDevIndex, &bprofile, &binfo); | ||||
|         if (ret < 0) { | ||||
|             flog::error("Could not open device: {}", ret); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the decimation amount | ||||
|         int dec = _this->samplerates.key(_this->samplerates.valueId(_this->sampleRate)); | ||||
|         flog::debug("Using decimation factor: {}", dec); | ||||
|  | ||||
|         // Decide to use either 8 or 16bit samples | ||||
|         _this->sampsInt8 = (_this->sampleRate > 64e6); | ||||
|  | ||||
|         // Get the default configuration | ||||
|         IQS_ProfileDeInit(&_this->openDev, &_this->profile); | ||||
|          | ||||
|         // Automatic configuration | ||||
|         _this->profile.Atten = -1; | ||||
|         _this->profile.BusTimeout_ms = 100; | ||||
|         _this->profile.TriggerSource = Bus;  | ||||
|         _this->profile.TriggerMode = Adaptive; | ||||
|         _this->profile.DataFormat = _this->sampsInt8 ? Complex8bit : Complex16bit; | ||||
|  | ||||
|         // User selectable config | ||||
|         _this->profile.CenterFreq_Hz = _this->freq; | ||||
|         _this->profile.RefLevel_dBm = _this->refLvl; | ||||
|         _this->profile.DecimateFactor = dec; | ||||
|         _this->profile.RxPort = _this->rxPorts.value(_this->portId); | ||||
|         _this->profile.GainStrategy = _this->gainStategies.value(_this->gainStratId); | ||||
|         _this->profile.Preamplifier = _this->preampModes.value(_this->preampModeId); | ||||
|         _this->profile.EnableIFAGC = _this->ifAgc; | ||||
|         _this->profile.LOOptimization = _this->loModes.value(_this->loModeId); | ||||
|  | ||||
|         // Apply the configuration | ||||
|         IQS_StreamInfo_TypeDef info; | ||||
|         ret = IQS_Configuration(&_this->openDev, &_this->profile, &_this->profile, &info); | ||||
|         if (ret < 0) { | ||||
|             flog::error("Could not configure device: {}", ret); | ||||
|             Device_Close(&_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Save the stream configuration | ||||
|         _this->bufferSize = info.PacketSamples; | ||||
|         flog::debug("Got buffer size: {}", _this->bufferSize); | ||||
|  | ||||
|         // Start the stream | ||||
|         ret = IQS_BusTriggerStart(&_this->openDev); | ||||
|         if (ret < 0) { | ||||
|             flog::error("Could not start stream: {}", ret); | ||||
|             Device_Close(&_this->openDev); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Start worker | ||||
|         _this->run = true; | ||||
|         _this->workerThread = std::thread(&HarogicSourceModule::worker, _this); | ||||
|          | ||||
|         _this->running = true; | ||||
|         flog::info("HarogicSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         HarogicSourceModule* _this = (HarogicSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|          | ||||
|         // Stop worker | ||||
|         _this->run = false; | ||||
|         _this->stream.stopWriter(); | ||||
|         if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|         _this->stream.clearWriteStop(); | ||||
|  | ||||
|         // Stop the stream | ||||
|         IQS_BusTriggerStop(&_this->openDev); | ||||
|  | ||||
|         // Close the device | ||||
|         Device_Close(&_this->openDev); | ||||
|  | ||||
|         flog::info("HarogicSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         HarogicSourceModule* _this = (HarogicSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             // Update the frequency in the configuration | ||||
|             _this->profile.CenterFreq_Hz = freq; | ||||
|             _this->applyProfile(); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("HarogicSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         HarogicSourceModule* _this = (HarogicSourceModule*)ctx; | ||||
|          | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_harogic_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_harogic_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { | ||||
|             _this->sampleRate = _this->samplerates.value(_this->srId); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_harogic_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         SmGui::LeftLabel("RX Port"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_harogic_port_", _this->name), &_this->portId, _this->rxPorts.txt)) { | ||||
|             if (_this->running) { | ||||
|                 _this->profile.RxPort = _this->rxPorts.value(_this->portId); | ||||
|                 _this->applyProfile(); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("LO Mode"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_lo_mode_", _this->name), &_this->loModeId, _this->loModes.txt)) { | ||||
|             if (_this->running) { | ||||
|                 _this->profile.LOOptimization = _this->loModes.value(_this->loModeId); | ||||
|                 _this->applyProfile(); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Gain Mode"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_harogic_gain_mode_", _this->name), &_this->gainStratId, _this->gainStategies.txt)) { | ||||
|             if (_this->running) { | ||||
|                 _this->profile.GainStrategy = _this->gainStategies.value(_this->gainStratId); | ||||
|                 _this->applyProfile(); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Preamp Mode"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::Combo(CONCAT("##_harogic_preamp_mode_", _this->name), &_this->preampModeId, _this->preampModes.txt)) { | ||||
|             if (_this->running) { | ||||
|                 _this->profile.Preamplifier = _this->preampModes.value(_this->preampModeId); | ||||
|                 _this->applyProfile(); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Reference"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_harogic_ref_", _this->name), &_this->refLvl, _this->minRef, _this->maxRef)) { | ||||
|             if (_this->running) { | ||||
|                 _this->profile.RefLevel_dBm = _this->refLvl; | ||||
|                 _this->applyProfile(); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Checkbox(CONCAT("IF AGC##_harogic_if_agc_", _this->name), &_this->ifAgc)) { | ||||
|             if (_this->running) { | ||||
|                 _this->profile.EnableIFAGC = _this->ifAgc; | ||||
|                 _this->applyProfile(); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void applyProfile() { | ||||
|         // Acquire device | ||||
|         std::lock_guard<std::mutex> lck(devMtx); | ||||
|  | ||||
|         // Configure the device | ||||
|         IQS_StreamInfo_TypeDef info; | ||||
|         int ret = IQS_Configuration(&openDev, &profile, &profile, &info); | ||||
|         if (ret < 0) { | ||||
|             flog::error("Failed to apply tuning config: {}", ret); | ||||
|         } | ||||
|  | ||||
|         // Re-trigger the stream | ||||
|         ret = IQS_BusTriggerStart(&openDev); | ||||
|         if (ret < 0) { | ||||
|             flog::error("Could not start stream: {}", ret); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         // Allocate sample buffer | ||||
|         int realSamps = bufferSize*2; | ||||
|         IQStream_TypeDef iqs; | ||||
|  | ||||
|         // Define number of buffers per swap to maintain 200 fps | ||||
|         int maxBufCount = STREAM_BUFFER_SIZE / bufferSize; | ||||
|         int bufCount = (sampleRate / bufferSize) / 200; | ||||
|         if (bufCount <= 0) { bufCount = 1; } | ||||
|         if (bufCount > maxBufCount) { bufCount = maxBufCount; } | ||||
|         int count = 0; | ||||
|  | ||||
|         flog::debug("Swapping will be done {} buffers at a time", bufCount); | ||||
|  | ||||
|         // Worker loop | ||||
|         while (run) { | ||||
|             // Read samples | ||||
|             devMtx.lock(); | ||||
|             int ret = IQS_GetIQStream_PM1(&openDev, &iqs); | ||||
|             devMtx.unlock(); | ||||
|             if (ret < 0) { | ||||
|                 if (ret == APIRETVAL_WARNING_BusTimeOut) { | ||||
|                     flog::warn("Stream timed out"); | ||||
|                     continue; | ||||
|                 } | ||||
|                 else if (ret <= APIRETVAL_WARNING_IFOverflow && ret >= APIRETVAL_WARNING_ADCConfigError) { | ||||
|                     // Just warnings, do nothing | ||||
|                 } | ||||
|                 else { | ||||
|                     flog::error("Streaming error: {}", ret); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Convert them to floating point | ||||
|             if (sampsInt8) { | ||||
|                 volk_8i_s32f_convert_32f((float*)&stream.writeBuf[(count++)*bufferSize], (int8_t*)iqs.AlternIQStream, 128.0f, realSamps); | ||||
|             } | ||||
|             else { | ||||
|                 volk_16i_s32f_convert_32f((float*)&stream.writeBuf[(count++)*bufferSize], (int16_t*)iqs.AlternIQStream, 32768.0f, realSamps); | ||||
|             } | ||||
|  | ||||
|             // Send them off if we have enough | ||||
|             if (count >= bufCount) { | ||||
|                 count = 0; | ||||
|                 if (!stream.swap(bufferSize*bufCount)) { break; } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|  | ||||
|     OptionList<std::string, int> devices; | ||||
|     OptionList<int, double> samplerates; | ||||
|     OptionList<std::string, RxPort_TypeDef> rxPorts; | ||||
|     OptionList<std::string, GainStrategy_TypeDef> gainStategies; | ||||
|     OptionList<std::string, PreamplifierState_TypeDef> preampModes; | ||||
|     OptionList<std::string, LOOptimization_TypeDef> loModes; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int refLvl = -30; | ||||
|     int minRef = -100; | ||||
|     int maxRef = 7; | ||||
|     int portId = 0; | ||||
|     int gainStratId = 0; | ||||
|     int preampModeId = 0; | ||||
|     int loModeId = 0; | ||||
|     bool ifAgc = false; | ||||
|     std::string selectedSerial; | ||||
|     int selectedDevIndex; | ||||
|  | ||||
|     void* openDev; | ||||
|     IQS_Profile_TypeDef profile; | ||||
|  | ||||
|     int bufferSize; | ||||
|     std::thread workerThread; | ||||
|     std::atomic<bool> run = false; | ||||
|     std::mutex devMtx; | ||||
|     bool sampsInt8; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Nothing here | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new HarogicSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (HarogicSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     // Nothing here | ||||
| } | ||||
| @@ -118,6 +118,7 @@ private: | ||||
|  | ||||
|         // Update host samplerate | ||||
|         sampleRate = samplerates.key(srId); | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
| @@ -198,7 +199,6 @@ private: | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_hermes_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->selectMac(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             if (!_this->selectedMac.empty()) { | ||||
|                 config.acquire(); | ||||
|                 config.conf["device"] = _this->devices.key(_this->devId); | ||||
| @@ -225,7 +225,6 @@ private: | ||||
|             std::string mac = config.conf["device"]; | ||||
|             config.release(); | ||||
|             _this->selectMac(mac); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|   | ||||
							
								
								
									
										1
									
								
								source_modules/kcsdr_source/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								source_modules/kcsdr_source/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| vendor/* | ||||
| @@ -1,10 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(kcsdr_source) | ||||
|  | ||||
| file(GLOB SRC "src/*.cpp" "src/*.c") | ||||
|  | ||||
| include(${SDRPP_MODULE_CMAKE}) | ||||
|  | ||||
| target_link_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10/x64/DLL") | ||||
| target_include_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10") | ||||
| target_link_libraries(kcsdr_source PRIVATE FTD3XX) | ||||
| @@ -1,209 +0,0 @@ | ||||
| #include "kcsdr.h" | ||||
| #include <string.h> | ||||
| #include "../vendor/FTD3XXLibrary_1.3.0.10/FTD3XX.h" | ||||
| #include <stdio.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| #define KCSDR_PKT_EMPTY_LEN 0x0C | ||||
|  | ||||
| #define KCSDR_COMMAND_PIPE  0x02 | ||||
| #define KCSDR_RX_DATA_PIPE  0x83 | ||||
| #define KCSDR_TX_DATA_PIPE  0x03 | ||||
|  | ||||
| struct kcsdr { | ||||
|     FT_HANDLE ft; | ||||
| }; | ||||
|  | ||||
| #pragma pack(push, 1) | ||||
| struct kcsdr_packet { | ||||
|     uint8_t zeros0[4]; | ||||
|     uint8_t length; | ||||
|     uint8_t zeros1[2]; | ||||
|     uint8_t hex_eighty; | ||||
|     uint32_t command; | ||||
|     uint8_t data[188]; | ||||
| }; | ||||
| typedef struct kcsdr_packet kcsdr_packet_t; | ||||
| #pragma pack(pop) | ||||
|  | ||||
| enum kcsdr_command { | ||||
|     CMD_NOT_USED_0x00   = 0x00, | ||||
|     CMD_SET_PORT        = 0x01, | ||||
|     CMD_SET_FREQUENCY   = 0x02, | ||||
|     CMD_SET_ATTENUATION = 0x03, | ||||
|     CMD_SET_AMPLIFIER   = 0x04, | ||||
|     CMD_SET_BANDWIDTH   = 0x05, | ||||
|     CMD_START           = 0x06, | ||||
|     CMD_STOP            = 0x07, | ||||
|     CMD_SET_EXT_AMP     = 0x08, | ||||
|     CMD_START_REMOTE    = 0x09, | ||||
|     CMD_STOP_REMOTE     = 0x0A | ||||
| }; | ||||
| typedef enum kcsdr_command kcsdr_command_t; | ||||
|  | ||||
| int kcsdr_send_command(kcsdr_t* dev, kcsdr_direction_t dir, kcsdr_command_t cmd, const uint8_t* data, int len) { | ||||
|     Sleep(50); | ||||
|      | ||||
|     // Create an empty packet | ||||
|     kcsdr_packet_t pkt; | ||||
|     memset(&pkt, 0, sizeof(kcsdr_packet_t)); | ||||
|  | ||||
|     // Fill out the packet info | ||||
|     pkt.length = len + KCSDR_PKT_EMPTY_LEN; | ||||
|     pkt.hex_eighty = 0x80; // Whatever the fuck that is | ||||
|     pkt.command = (uint32_t)cmd | (uint32_t)dir; | ||||
|  | ||||
|     // Copy the data if there is some | ||||
|     if (len) { memcpy(pkt.data, data, len); } | ||||
|  | ||||
|     // Dump the bytes | ||||
|     uint8_t* dump = (uint8_t*)&pkt; | ||||
|     printf("Sending:"); | ||||
|     for (int i = 0; i < pkt.length; i++) { | ||||
|         printf(" %02X", dump[i]); | ||||
|     } | ||||
|     printf("\n"); | ||||
|  | ||||
|     // Send the command to endpoint 0 | ||||
|     int sent; | ||||
|     FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_COMMAND_PIPE, (uint8_t*)&pkt, sizeof(kcsdr_packet_t), &sent, NULL); | ||||
|     if (err != FT_OK) { | ||||
|         return -err; | ||||
|     } | ||||
|     printf("Sent %d bytes (%d)\n", sent, err); | ||||
|  | ||||
|     Sleep(50); | ||||
|  | ||||
|     // Flush existing commands | ||||
|     FT_FlushPipe(dev->ft, KCSDR_COMMAND_PIPE); | ||||
|  | ||||
|     return -(int)err; | ||||
| } | ||||
|  | ||||
| int kcsdr_list_devices(kcsdr_info_t** devices) { | ||||
|     // Generate a list of FTDI devices | ||||
|     int ftdiDevCount = 0; | ||||
|     FT_STATUS err = FT_CreateDeviceInfoList(&ftdiDevCount); | ||||
|     if (err != FT_OK) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // If no device was found, return nothing | ||||
|     if (!ftdiDevCount) { | ||||
|         *devices = NULL; | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     // Get said device list | ||||
|     FT_DEVICE_LIST_INFO_NODE* list = malloc(ftdiDevCount * sizeof(FT_DEVICE_LIST_INFO_NODE)); | ||||
|     err = FT_GetDeviceInfoList(list, &ftdiDevCount); | ||||
|     if (err != FT_OK) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // Allocate the device info list | ||||
|     *devices = malloc(ftdiDevCount * sizeof(kcsdr_info_t)); | ||||
|  | ||||
|     // Find all KC908s | ||||
|     int kcCount = 0; | ||||
|     for (int i = 0; i < ftdiDevCount; i++) { | ||||
|         strcpy((*devices)[kcCount++].serial, list[i].SerialNumber); | ||||
|     } | ||||
|  | ||||
|     // Free the FTDI list | ||||
|     free(list); | ||||
|  | ||||
|     return kcCount; | ||||
| } | ||||
|  | ||||
| void kcsdr_free_device_list(kcsdr_info_t* devices) { | ||||
|     // Free the list | ||||
|     if (devices) { free(devices); } | ||||
| } | ||||
|  | ||||
| int kcsdr_open(kcsdr_t** dev, const char* serial) { | ||||
|     // Attempt to open the device using the serial number | ||||
|     FT_HANDLE ft; | ||||
|     FT_STATUS err = FT_Create(serial, FT_OPEN_BY_SERIAL_NUMBER, &ft); | ||||
|     if (err != FT_OK) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     // Set the timeouts for the data pipes | ||||
|     FT_SetPipeTimeout(ft, KCSDR_RX_DATA_PIPE, 1000); | ||||
|     FT_SetPipeTimeout(ft, KCSDR_TX_DATA_PIPE, 1000); | ||||
|  | ||||
|     // Allocate the device object | ||||
|     *dev = malloc(sizeof(kcsdr_t)); | ||||
|  | ||||
|     // Fill out the device object | ||||
|     (*dev)->ft = ft; | ||||
|  | ||||
|     // Put device into remote control mode | ||||
|     return kcsdr_send_command(*dev, KCSDR_DIR_RX, CMD_START_REMOTE, NULL, 0); | ||||
| } | ||||
|  | ||||
| void kcsdr_close(kcsdr_t* dev) { | ||||
|     // Put device back in normal mode | ||||
|     kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_STOP_REMOTE, NULL, 0); | ||||
|  | ||||
|     // Close the device | ||||
|     FT_Close(dev->ft); | ||||
|  | ||||
|     // Free the device object | ||||
|     free(dev); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port) { | ||||
|     // Send SET_PORT command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_PORT, &port, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq) { | ||||
|     // Send SET_FREQUENCY command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_FREQUENCY, (uint8_t*)&freq, 8); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att) { | ||||
|     // Send SET_ATTENUATION command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_ATTENUATION, &att, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain) { | ||||
|     // Send SET_AMPLIFIER command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_AMPLIFIER, &gain, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain) { | ||||
|     // Send CMD_SET_EXT_AMP command | ||||
|     return kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_SET_EXT_AMP, &gain, 1); | ||||
| } | ||||
|  | ||||
| int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate) { | ||||
|     // Set SET_BANDWIDTH command | ||||
|     return kcsdr_send_command(dev, dir, CMD_SET_BANDWIDTH, (uint8_t*)&samplerate, 4); | ||||
| } | ||||
|  | ||||
| int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir) { | ||||
|     // Send START command | ||||
|     return kcsdr_send_command(dev, dir, CMD_START, NULL, 0); | ||||
| } | ||||
|  | ||||
| int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir) { | ||||
|     // Send STOP command | ||||
|     return kcsdr_send_command(dev, dir, CMD_STOP, NULL, 0); | ||||
| } | ||||
|  | ||||
| int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count) { | ||||
|     // Receive samples (TODO: Endpoint might be 0x81) | ||||
|     int received; | ||||
|     FT_STATUS err = FT_ReadPipeEx(dev->ft, KCSDR_RX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &received, NULL); | ||||
|     return (err == FT_OK) ? received / (2*sizeof(uint16_t)) : -(int)err; | ||||
| } | ||||
|  | ||||
| int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count) { | ||||
|     // Transmit samples | ||||
|     int sent; | ||||
|     FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_TX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &sent, NULL); | ||||
|     return (err == FT_OK) ? sent / (2*sizeof(uint16_t)) : -(int)err; | ||||
| } | ||||
| @@ -1,150 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
|  | ||||
| #define KCSDR_SERIAL_LEN    16 | ||||
| #define KCSDR_MAX_PORTS     6 | ||||
|  | ||||
| // Detect C++ | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| /** | ||||
|  * KCSDR Device. | ||||
| */ | ||||
| struct kcsdr; | ||||
| typedef struct kcsdr kcsdr_t; | ||||
|  | ||||
| /** | ||||
|  * Device Information | ||||
| */ | ||||
| struct kcsdr_info { | ||||
|     char serial[KCSDR_SERIAL_LEN+1]; | ||||
| }; | ||||
| typedef struct kcsdr_info kcsdr_info_t; | ||||
|  | ||||
| /** | ||||
|  * RF Direction. | ||||
| */ | ||||
| enum kcsdr_direction { | ||||
|     KCSDR_DIR_RX    = 0x00, | ||||
|     KCSDR_DIR_TX    = 0x80 | ||||
| }; | ||||
| typedef enum kcsdr_direction kcsdr_direction_t; | ||||
|  | ||||
| /** | ||||
|  * Get a list of KCSDR devices on the system. | ||||
|  * @param devices Pointer to an array of device info. | ||||
|  * @return Number of devices found or error code. | ||||
| */ | ||||
| int kcsdr_list_devices(kcsdr_info_t** devices); | ||||
|  | ||||
| /** | ||||
|  * Free a device list returned by `kcsdr_list_devices()`. | ||||
|  * @param devices Device list to free. | ||||
| */ | ||||
| void kcsdr_free_device_list(kcsdr_info_t* devices); | ||||
|  | ||||
| /** | ||||
|  * Open a KCSDR device. | ||||
|  * @param dev Newly open device. | ||||
|  * @param serial Serial number of the device to open as returned in the device list. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_open(kcsdr_t** dev, const char* serial); | ||||
|  | ||||
| /** | ||||
|  * Close a KCSDR device. | ||||
|  * @param dev Device to be closed. | ||||
| */ | ||||
| void kcsdr_close(kcsdr_t* dev); | ||||
|  | ||||
| /** | ||||
|  * Select the RF port. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX. | ||||
|  * @param port RF port number to select. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port); | ||||
|  | ||||
| /** | ||||
|  * Set the center frequency. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param freq Frequency in Hz. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq); | ||||
|  | ||||
| /** | ||||
|  * Set the attenuation. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param samplerate Attenuation in dB. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att); | ||||
|  | ||||
| /** | ||||
|  * Set the internal amplifier gain. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param gain Gain in dB. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain); | ||||
|  | ||||
| /** | ||||
|  * Set the external amplifier gain. | ||||
|  * @param dev Device to control. | ||||
|  * @param gain Gain in dB. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain); | ||||
|  | ||||
| /** | ||||
|  * Set the samplerate. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX | ||||
|  * @param samplerate Samplerate in Hz. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate); | ||||
|  | ||||
| /** | ||||
|  * Start streaming samples. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir); | ||||
|  | ||||
| /** | ||||
|  * Stop streaming samples. | ||||
|  * @param dev Device to control. | ||||
|  * @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX. | ||||
|  * @return 0 on success, error code otherwise. | ||||
| */ | ||||
| int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir); | ||||
|  | ||||
| /** | ||||
|  * Receive a buffer of samples. | ||||
|  * @param samples Sample buffer. | ||||
|  * @param count Number of complex samples. | ||||
|  * @return Number of samples received. | ||||
| */ | ||||
| int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count); | ||||
|  | ||||
| /** | ||||
|  * Transmit a buffer of samples. | ||||
|  * @param samples Sample buffer. | ||||
|  * @param count Number of complex samples. | ||||
|  * @return Number of samples transmitted. | ||||
| */ | ||||
| int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count); | ||||
|  | ||||
| // Detect C++ | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @@ -1,324 +0,0 @@ | ||||
| #include <imgui.h> | ||||
| #include <module.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/smgui.h> | ||||
| #include <signal_path/signal_path.h> | ||||
| #include <core.h> | ||||
| #include <utils/optionlist.h> | ||||
| #include "kcsdr.h" | ||||
| #include <atomic> | ||||
|  | ||||
| SDRPP_MOD_INFO{ | ||||
|     /* Name:            */ "kcsdr_source", | ||||
|     /* Description:     */ "KCSDR Source Module", | ||||
|     /* Author:          */ "Ryzerth", | ||||
|     /* Version:         */ 0, 1, 0, | ||||
|     /* Max instances    */ -1 | ||||
| }; | ||||
|  | ||||
| #define CONCAT(a, b) ((std::string(a) + b).c_str()) | ||||
|  | ||||
| class KCSDRSourceModule : public ModuleManager::Instance { | ||||
| public: | ||||
|     KCSDRSourceModule(std::string name) { | ||||
|         this->name = name; | ||||
|  | ||||
|         sampleRate = 2000000.0; | ||||
|         samplerates.define(40e6, "40MHz", 40e6); | ||||
|         samplerates.define(35e6, "35MHz", 35e6); | ||||
|         samplerates.define(30e6, "30MHz", 30e6); | ||||
|         samplerates.define(25e6, "25MHz", 25e6); | ||||
|         samplerates.define(20e6, "20MHz", 20e6); | ||||
|         samplerates.define(15e6, "15MHz", 15e6); | ||||
|         samplerates.define(10e6, "10MHz", 10e6); | ||||
|         samplerates.define(5e6, "5MHz", 5e6); | ||||
|  | ||||
|         handler.ctx = this; | ||||
|         handler.selectHandler = menuSelected; | ||||
|         handler.deselectHandler = menuDeselected; | ||||
|         handler.menuHandler = menuHandler; | ||||
|         handler.startHandler = start; | ||||
|         handler.stopHandler = stop; | ||||
|         handler.tuneHandler = tune; | ||||
|         handler.stream = &stream; | ||||
|  | ||||
|         // Refresh devices | ||||
|         refresh(); | ||||
|  | ||||
|         // Select first (TODO: Select from config) | ||||
|         select(""); | ||||
|  | ||||
|         sigpath::sourceManager.registerSource("KCSDR", &handler); | ||||
|     } | ||||
|  | ||||
|     ~KCSDRSourceModule() { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void postInit() {} | ||||
|  | ||||
|     void enable() { | ||||
|         enabled = true; | ||||
|     } | ||||
|  | ||||
|     void disable() { | ||||
|         enabled = false; | ||||
|     } | ||||
|  | ||||
|     bool isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void refresh() { | ||||
|         devices.clear(); | ||||
|          | ||||
|         // Get device list | ||||
|         kcsdr_info_t* list; | ||||
|         int count = kcsdr_list_devices(&list); | ||||
|         if (count < 0) { | ||||
|             flog::error("Failed to list devices: {}", count); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Create list | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             devices.define(list[i].serial, list[i].serial, list[i].serial); | ||||
|         } | ||||
|  | ||||
|         // Free the device list | ||||
|         kcsdr_free_device_list(list); | ||||
|     } | ||||
|  | ||||
|     void select(const std::string& serial) { | ||||
|         // If there are no devices, give up | ||||
|         if (devices.empty()) { | ||||
|             selectedSerial.clear(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If the serial was not found, select the first available serial | ||||
|         if (!devices.keyExists(serial)) { | ||||
|             select(devices.key(0)); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get the menu ID | ||||
|         devId = devices.keyId(serial); | ||||
|  | ||||
|         // TODO | ||||
|  | ||||
|         // Update the samplerate | ||||
|         core::setInputSampleRate(sampleRate); | ||||
|  | ||||
|         // Save serial number | ||||
|         selectedSerial = serial; | ||||
|     } | ||||
|  | ||||
|     static void menuSelected(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         core::setInputSampleRate(_this->sampleRate); | ||||
|         flog::info("KCSDRSourceModule '{0}': Menu Select!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void menuDeselected(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         flog::info("KCSDRSourceModule '{0}': Menu Deselect!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void start(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         if (_this->running) { return; } | ||||
|  | ||||
|         // If no serial is given, do nothing | ||||
|         if (_this->selectedSerial.empty()) { return; } | ||||
|  | ||||
|         // Open the device | ||||
|         int err = kcsdr_open(&_this->openDev, _this->selectedSerial.c_str()); | ||||
|         if (err) { | ||||
|             flog::error("Failed to open device: {}", err); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Configure the device | ||||
|         kcsdr_set_port(_this->openDev, KCSDR_DIR_RX, 0); | ||||
|         kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, _this->freq); | ||||
|         kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att); | ||||
|         kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain); | ||||
|         kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain); | ||||
|         kcsdr_set_samplerate(_this->openDev, KCSDR_DIR_RX, _this->sampleRate); | ||||
|  | ||||
|         // Start the stream | ||||
|         kcsdr_start(_this->openDev, KCSDR_DIR_RX); | ||||
|  | ||||
|         // Start worker | ||||
|         _this->run = true; | ||||
|         _this->workerThread = std::thread(&KCSDRSourceModule::worker, _this); | ||||
|          | ||||
|         _this->running = true; | ||||
|         flog::info("KCSDRSourceModule '{0}': Start!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void stop(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         if (!_this->running) { return; } | ||||
|         _this->running = false; | ||||
|          | ||||
|         // Stop worker | ||||
|         _this->run = false; | ||||
|         _this->stream.stopWriter(); | ||||
|         if (_this->workerThread.joinable()) { _this->workerThread.join(); } | ||||
|         _this->stream.clearWriteStop(); | ||||
|  | ||||
|         // Stop streaming | ||||
|         kcsdr_stop(_this->openDev, KCSDR_DIR_RX); | ||||
|  | ||||
|         // Close the device | ||||
|         kcsdr_close(_this->openDev); | ||||
|  | ||||
|         flog::info("KCSDRSourceModule '{0}': Stop!", _this->name); | ||||
|     } | ||||
|  | ||||
|     static void tune(double freq, void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|         if (_this->running) { | ||||
|             kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, freq); | ||||
|         } | ||||
|         _this->freq = freq; | ||||
|         flog::info("KCSDRSourceModule '{0}': Tune: {1}!", _this->name, freq); | ||||
|     } | ||||
|  | ||||
|     static void menuHandler(void* ctx) { | ||||
|         KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx; | ||||
|          | ||||
|         if (_this->running) { SmGui::BeginDisabled(); } | ||||
|  | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Combo(CONCAT("##_kcsdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) { | ||||
|             _this->select(_this->devices.key(_this->devId)); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         if (SmGui::Combo(CONCAT("##_kcsdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) { | ||||
|             _this->sampleRate = _this->samplerates.value(_this->srId); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::SameLine(); | ||||
|         SmGui::FillWidth(); | ||||
|         SmGui::ForceSync(); | ||||
|         if (SmGui::Button(CONCAT("Refresh##_kcsdr_refr_", _this->name))) { | ||||
|             _this->refresh(); | ||||
|             _this->select(_this->selectedSerial); | ||||
|             core::setInputSampleRate(_this->sampleRate); | ||||
|         } | ||||
|  | ||||
|         if (_this->running) { SmGui::EndDisabled(); } | ||||
|  | ||||
|         // SmGui::LeftLabel("RX Port"); | ||||
|         // SmGui::FillWidth(); | ||||
|         // if (SmGui::Combo(CONCAT("##_kcsdr_port_", _this->name), &_this->portId, _this->rxPorts.txt)) { | ||||
|         //     if (_this->running) { | ||||
|         //         // TODO | ||||
|         //     } | ||||
|         //     // TODO: Save | ||||
|         // } | ||||
|  | ||||
|         SmGui::LeftLabel("Attenuation"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_kcsdr_att_", _this->name), &_this->att, 0, 31)) { | ||||
|             if (_this->running) { | ||||
|                 kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_kcsdr_gain_", _this->name), &_this->gain, 0, 31)) { | ||||
|             if (_this->running) { | ||||
|                 kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|  | ||||
|         SmGui::LeftLabel("External Gain"); | ||||
|         SmGui::FillWidth(); | ||||
|         if (SmGui::SliderInt(CONCAT("##_kcsdr_ext_gain_", _this->name), &_this->extGain, 0, 31)) { | ||||
|             if (_this->running) { | ||||
|                 kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain); | ||||
|             } | ||||
|             // TODO: Save | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void worker() { | ||||
|         // Compute the buffer size | ||||
|         int bufferSize = 0x4000/4;//sampleRate / 200; | ||||
|  | ||||
|         // Allocate the sample buffer | ||||
|         int16_t* samps = dsp::buffer::alloc<int16_t>(bufferSize*2); | ||||
|  | ||||
|         // Loop | ||||
|         while (run) { | ||||
|             // Read samples | ||||
|             int count = kcsdr_rx(openDev, samps, bufferSize); | ||||
|             if (!count) { continue; } | ||||
|             if (count < 0) { | ||||
|                 flog::debug("Failed to read samples: {}", count); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // Convert the samples to float | ||||
|             volk_16i_s32f_convert_32f((float*)stream.writeBuf, samps, 8192.0f, count*2); | ||||
|  | ||||
|             // Send out the samples | ||||
|             if (!stream.swap(count)) { break; } | ||||
|         } | ||||
|  | ||||
|         // Free the sample buffer | ||||
|         dsp::buffer::free(samps); | ||||
|     } | ||||
|  | ||||
|     std::string name; | ||||
|     bool enabled = true; | ||||
|     dsp::stream<dsp::complex_t> stream; | ||||
|     double sampleRate; | ||||
|     SourceManager::SourceHandler handler; | ||||
|     bool running = false; | ||||
|     double freq; | ||||
|  | ||||
|     OptionList<std::string, std::string> devices; | ||||
|     OptionList<int, double> samplerates; | ||||
|     int devId = 0; | ||||
|     int srId = 0; | ||||
|     int att = 0; | ||||
|     int gain = 30; | ||||
|     int extGain = 1; | ||||
|     int portId = 0; | ||||
|     std::string selectedSerial; | ||||
|  | ||||
|     kcsdr_t* openDev; | ||||
|  | ||||
|     std::thread workerThread; | ||||
|     std::atomic<bool> run = false; | ||||
| }; | ||||
|  | ||||
| MOD_EXPORT void _INIT_() { | ||||
|     // Nothing here | ||||
| } | ||||
|  | ||||
| MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { | ||||
|     return new KCSDRSourceModule(name); | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { | ||||
|     delete (KCSDRSourceModule*)instance; | ||||
| } | ||||
|  | ||||
| MOD_EXPORT void _END_() { | ||||
|     // Nothing here | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user