71 Commits

Author SHA1 Message Date
f8078ac3f0 more work on DAB decoding
Some checks failed
Build Binaries / build_debian_buster (push) Failing after 4s
Build Binaries / build_debian_bullseye (push) Failing after 5s
Build Binaries / build_debian_bookworm (push) Failing after 4s
Build Binaries / build_debian_sid (push) Failing after 4s
Build Binaries / build_ubuntu_focal (push) Failing after 4s
Build Binaries / build_ubuntu_jammy (push) Failing after 4s
Build Binaries / build_ubuntu_mantic (push) Failing after 4s
Build Binaries / build_ubuntu_noble (push) Failing after 5s
Build Binaries / build_android (push) Failing after 5s
Build Binaries / check_spelling (push) Failing after 3s
Build Binaries / check_formatting (push) Successful in 3s
Build Binaries / build_windows (push) Has been cancelled
Build Binaries / build_macos_intel (push) Has been cancelled
Build Binaries / build_macos_arm (push) Has been cancelled
Build Binaries / build_raspios_bullseye_armhf (push) Has been cancelled
Build Binaries / create_full_archive (push) Has been cancelled
Build Binaries / update_nightly_release (push) Has been cancelled
2024-09-23 00:51:06 +02:00
064f25ee73 work on the time sync algo
Some checks failed
Build Binaries / build_debian_buster (push) Failing after 5s
Build Binaries / build_debian_bullseye (push) Failing after 4s
Build Binaries / build_debian_bookworm (push) Failing after 4s
Build Binaries / build_debian_sid (push) Failing after 5s
Build Binaries / build_ubuntu_focal (push) Failing after 4s
Build Binaries / build_ubuntu_jammy (push) Failing after 4s
Build Binaries / build_ubuntu_mantic (push) Failing after 5s
Build Binaries / build_ubuntu_noble (push) Failing after 5s
Build Binaries / build_android (push) Failing after 5s
Build Binaries / check_spelling (push) Failing after 4s
Build Binaries / check_formatting (push) Successful in 4s
Build Binaries / build_windows (push) Has been cancelled
Build Binaries / build_macos_intel (push) Has been cancelled
Build Binaries / build_macos_arm (push) Has been cancelled
Build Binaries / build_raspios_bullseye_armhf (push) Has been cancelled
Build Binaries / create_full_archive (push) Has been cancelled
Build Binaries / update_nightly_release (push) Has been cancelled
2024-09-19 17:28:03 +02:00
d87ae23560 beginning of cleaner OFDM demod code 2024-09-19 05:47:58 +02:00
17eccf5156 enabled fobos source on windows 2024-09-14 15:05:12 +02:00
acb1be121c enable fobossdr source 2024-09-14 14:48:37 +02:00
0fa89614bb disabled problematic fobossdr source 2024-09-14 14:47:10 +02:00
256affd918 fixed fobossdr CI 2024-09-14 02:06:34 +02:00
e80cdbf248 fix CI + fix fobossdr_source cmakelist + prepare for fobos source on windows 2024-09-14 00:18:31 +02:00
75e66226c3 finish fobossdr source + add rigexpert to hardware donor list + fix source module default instantiation 2024-09-13 23:02:30 +02:00
c2f0e756a5 add fobossdr_source module and fix network sink crash when the given hostname is invalid 2024-09-11 21:57:12 +02:00
79dd5bdcbb add beginning of DAB decoder and add missing hardware donors to the credits 2024-09-10 15:33:22 +02:00
6dce28345c add missing stddef includes 2024-08-26 22:37:18 +02:00
fe9ac6c9a1 implemented all user options for harogic devices 2024-08-22 04:36:36 +02:00
bfdfa2b30b add debug logging to the harogic source enumeration function 2024-08-22 02:20:51 +02:00
46e98b9b03 add harogic_source module 2024-08-22 01:52:27 +02:00
e674a73771 removed useless include 2024-08-20 00:32:16 +02:00
118e1fbff0 Add badgesdr source and add logging to spectran source 2024-08-05 22:32:39 +02:00
bcadb36232 fix include issues in dsp library 2024-07-26 23:55:26 +02:00
554ba2f596 add ryfi decoder module 2024-07-24 16:31:29 +02:00
949fde022d fix recorder options not disabled during recording, increase rfnm source dsp frame size and fix long pause when stopping rfnm 2024-07-18 18:10:33 +02:00
123e34d250 fix appliesCh of preferred path in rfnm_source 2024-07-18 16:53:02 +02:00
f1c7010437 add rfnm_source path logging 2024-07-18 16:40:21 +02:00
33a7795de1 fix RFNM source on MacOS 2024-07-18 03:12:38 +02:00
fe7299c18a lots of work on the RFNM source 2024-07-18 02:15:20 +02:00
8a9e0abcc2 fix RFNM source not enabled 2024-07-16 21:01:44 +02:00
13abe4860b fix missing permissions 2024-07-16 20:59:32 +02:00
981592fa19 how many times can someone fuck up a build script 2024-07-16 20:58:25 +02:00
582750f79b fix CI script typo 2024-07-16 20:55:19 +02:00
36f2a083ce More work towards working RFNM CI 2024-07-16 20:53:32 +02:00
d753135a61 fix ARM macos CI build not having RFNM source 2024-07-16 20:45:27 +02:00
07744e5bae add rfnm_source module to linux and MacOS CI 2024-07-16 20:43:53 +02:00
9ec78da7ac rfnm source cleanup 2024-07-16 00:23:06 +02:00
0066994899 add missing files for rfnm source and add it to the default instantiation config 2024-07-15 22:48:27 +02:00
93ab51bf2f fix ci 2024-07-15 20:36:08 +02:00
f9d7d20073 add rfnm_source to windows nightlies 2024-07-15 20:25:53 +02:00
e81db5d85c make text bold so people won't miss it 2024-07-07 00:00:23 +02:00
5c3a66642b Added an idiot-proof warning that the drivers for the SDR you want to use must be installed. I know... shocker... 2024-07-06 23:59:33 +02:00
36492e799a Merge pull request #1418 from Armand31/patch-1
Update france.json
2024-06-26 17:41:53 +02:00
0110dfbef6 Another attempt 2024-06-26 17:19:06 +02:00
0b5a2ff786 Update librtlsdr for windows CI 2024-06-26 17:11:23 +02:00
ce0f1f05ae Attempt to fix the Windows CI builds (fuck you Microsoft) 2024-06-26 16:49:05 +02:00
46a5ff8ac5 Update france.json
Correcting typos
2024-06-10 00:57:42 +02:00
03559b928b Update france.json
Adding the 2 DAB bands and Police band (tetrapol protocol)
2024-06-10 00:26:48 +02:00
206ce6e8c3 fix formatting mistake in pull request... for fucks sake... 2024-06-08 05:33:57 +02:00
d7a1f46af0 Merge pull request #1416 from Armand31/patch-1
Update france.json
2024-06-07 22:37:40 +02:00
89e6e4f7ad Update france.json 2024-06-07 22:13:55 +02:00
6ced9b15c3 Update france.json
Add DVB-T (TNT) band, from 470 to 694 MHz
2024-06-07 21:30:13 +02:00
0de189a7b7 Merge pull request #1413 from ycanerol/master
Add Turkish Bandplan
2024-06-06 19:35:30 +02:00
bb9024fadd disable badgesdr_source module 2024-06-04 22:04:25 +02:00
d1dc20f4e2 fix rfnm device selection 2024-06-04 22:03:39 +02:00
309717b5f8 Add Turkish Bandplan 2024-05-31 01:59:31 +03:00
762444d340 fix version number on Linux, MacOS and Android 2024-05-29 00:56:56 +02:00
18300e8916 switch to SDRplay API v3.15 2024-05-26 22:57:47 +02:00
a93bb9d468 fix source/samplerate selection bugs 2024-05-26 22:27:47 +02:00
ea0362b927 add microtelecom to hardware donor list 2024-05-24 17:06:30 +02:00
ffc642f270 set stage of rfnm source to beta 2024-05-24 01:58:17 +02:00
1b5975f563 Add RFNM to hardware donor list 2024-05-24 01:42:36 +02:00
733dc55723 added missing files 2024-05-24 01:38:24 +02:00
b841180f84 add ubuntu noble build 2024-05-24 01:38:15 +02:00
e99e84e809 add gain slider and FM notch controls to RFNM source$ 2024-05-15 13:01:57 +02:00
7a4281dd76 add rfnm_source module 2024-05-14 22:22:03 +02:00
c89763a989 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-05-04 16:38:52 +02:00
27edc260c9 fix some fft sizes not being saved as described in #1396 2024-05-04 16:38:47 +02:00
2ea7ac496f Merge pull request #1391 from rberaldo/master
Add a bandplan for Brazilian ham bands
2024-05-01 21:13:15 +02:00
314d78d9d2 Add another missing include to the spectran source 2024-04-27 07:00:27 +02:00
4e455e6661 Add missing include in spectran source 2024-04-27 06:53:15 +02:00
58b86fcee5 add brazil.json
Add a bandplan for the Brazilian ham bands inspired in netherlands.json.
I intend to improve and extend on this band plan, including by adding non-ham
bands.
2024-04-26 16:36:06 -03:00
27072e9fe7 version bump to indicate ABI change. modules just need to be recompiled. 2024-04-23 22:46:43 +02:00
da1417b5ab made recorder crash fix more robust 2024-04-22 21:54:01 +02:00
e60eca5d6d fix crash when attempting to record from disabled radio 2024-04-22 21:51:49 +02:00
ccb10bfb9a fix missing virtual destructors as reported in #1386 2024-04-22 18:54:12 +02:00
98 changed files with 9594 additions and 1484 deletions

View File

@ -36,6 +36,13 @@ 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
@ -58,17 +65,23 @@ 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
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows spdlog:x64-windows
- name: Install rtaudio
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
- name: Install libperseus-sdr
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
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 .
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
run: cmake -DCOPY_MSVC_REDISTRIBUTABLES=ON "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
- name: Build
working-directory: ${{runner.workspace}}/build
@ -94,13 +107,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 && pip3 install mako
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && 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.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
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 /
- 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 ../../
@ -112,14 +125,20 @@ 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 && make install && cd ..
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 ..
- 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 -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
@ -145,13 +164,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 && pip3 install mako --break-system-packages
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
- 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.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
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 /
- 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 ../../
@ -165,12 +184,18 @@ 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 -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
@ -340,6 +365,28 @@ 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
@ -395,7 +442,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_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_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android']
runs-on: ubuntu-latest
steps:
@ -415,6 +462,7 @@ 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

View File

@ -12,14 +12,18 @@ 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_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)
@ -40,12 +44,14 @@ 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
@ -61,6 +67,7 @@ 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")
@ -125,6 +132,10 @@ 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)
@ -133,10 +144,18 @@ 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)
@ -157,6 +176,10 @@ 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)
@ -225,6 +248,10 @@ if (OPT_BUILD_ATV_DECODER)
add_subdirectory("decoder_modules/atv_decoder")
endif (OPT_BUILD_ATV_DECODER)
if (OPT_BUILD_DAB_DECODER)
add_subdirectory("decoder_modules/dab_decoder")
endif (OPT_BUILD_DAB_DECODER)
if (OPT_BUILD_FALCON9_DECODER)
add_subdirectory("decoder_modules/falcon9_decoder")
endif (OPT_BUILD_FALCON9_DECODER)
@ -249,6 +276,10 @@ 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)
@ -302,6 +333,21 @@ 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 ()
@ -342,4 +388,6 @@ 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)
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
# Create headers target

View File

@ -10,7 +10,7 @@ android {
minSdkVersion 28
targetSdkVersion 28
versionCode 1
versionName "1.1.0"
versionName "1.2.0"
externalNativeBuild {
cmake {

View File

@ -173,16 +173,22 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source";
defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source";
defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true;
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source";
defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
@ -193,8 +199,12 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source";
defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true;
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;
defConfig["moduleInstances"]["USRP Source"]["module"] = "usrp_source";
defConfig["moduleInstances"]["USRP Source"]["enabled"] = true;
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
defConfig["moduleInstances"]["Network Sink"] = "network_sink";

View File

@ -37,14 +37,20 @@ 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"
};

View File

@ -9,6 +9,7 @@
namespace dsp {
class generic_block {
public:
virtual ~generic_block() {}
virtual void start() {}
virtual void stop() {}
virtual int run() { return -1; }
@ -16,8 +17,6 @@ namespace dsp {
class block : public generic_block {
public:
virtual void init() {}
virtual ~block() {
if (!_block_init) { return; }
stop();

View File

@ -1,5 +1,6 @@
#pragma once
#include <volk/volk.h>
#include <string.h>
namespace dsp::buffer {
template<class T>

View File

@ -10,6 +10,8 @@ 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;

View File

@ -11,6 +11,7 @@
namespace dsp {
class untyped_stream {
public:
virtual ~untyped_stream() {}
virtual bool swap(int size) { return false; }
virtual int read() { return -1; }
virtual void flush() {}

View File

@ -1,5 +1,6 @@
#pragma once
#include <volk/volk.h>
#include "../buffer/buffer.h"
namespace dsp {
template<class T>

View File

@ -16,7 +16,6 @@ namespace icons {
ImTextureID UNMUTED;
ImTextureID NORMAL_TUNING;
ImTextureID CENTER_TUNING;
ImTextureID ALIGN_CENTER;
GLuint loadTexture(std::string path) {
int w, h, n;
@ -46,7 +45,6 @@ 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;
}

View File

@ -13,8 +13,7 @@ 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);
}

View File

@ -17,7 +17,6 @@
#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>
@ -73,7 +72,6 @@ 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);
@ -167,7 +165,6 @@ void MainWindow::init() {
sourcemenu::init();
sinkmenu::init();
streamsmenu::init();
bandplanmenu::init();
displaymenu::init();
vfo_color_menu::init();

View File

@ -19,6 +19,7 @@ namespace displaymenu {
std::string colorMapAuthor = "";
int selectedWindow = 0;
int fftRate = 20;
int fftSizeId = 0;
int uiScaleId = 0;
bool restartRequired = false;
bool fftHold = false;
@ -28,34 +29,9 @@ 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,
@ -69,6 +45,18 @@ 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"];
@ -90,15 +78,12 @@ namespace displaymenu {
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
fftSizeId = 3;
int fftSize = core::configManager.conf["fftSize"];
for (int i = 0; i < 7; i++) {
if (fftSize == FFTSizes[i]) {
fftSizeId = i;
break;
}
fftSizeId = fftSizes.valueId(65536);
int size = core::configManager.conf["fftSize"];
if (fftSizes.keyExists(size)) {
fftSizeId = fftSizes.keyId(size);
}
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
fftRate = core::configManager.conf["fftRate"];
sigpath::iqFrontEnd.setFFTRate(fftRate);
@ -229,10 +214,10 @@ namespace displaymenu {
ImGui::LeftLabel("FFT Size");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) {
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) {
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
core::configManager.acquire();
core::configManager.conf["fftSize"] = FFTSizes[fftSizeId];
core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId);
core::configManager.release(true);
}

View File

@ -1,177 +0,0 @@
#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();
}
}
};

View File

@ -1,6 +0,0 @@
#pragma once
namespace streamsmenu {
void init();
void draw(void* ctx);
};

View File

@ -42,6 +42,7 @@ public:
class Instance {
public:
virtual ~Instance() {}
virtual void postInit() = 0;
virtual void enable() = 0;
virtual void disable() = 0;

View File

@ -5,5 +5,4 @@ namespace sigpath {
VFOManager vfoManager;
SourceManager sourceManager;
SinkManager sinkManager;
StreamManager streamManager;
};

View File

@ -3,7 +3,6 @@
#include "vfo_manager.h"
#include "source.h"
#include "sink.h"
#include "stream.h"
#include <module.h>
namespace sigpath {
@ -11,5 +10,4 @@ namespace sigpath {
SDRPP_EXPORT VFOManager vfoManager;
SDRPP_EXPORT SourceManager sourceManager;
SDRPP_EXPORT SinkManager sinkManager;
SDRPP_EXPORT StreamManager streamManager;
};

View File

@ -1,519 +0,0 @@
#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;
}

View File

@ -1,334 +0,0 @@
#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;
};

View File

@ -63,7 +63,6 @@ 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];
}

View File

@ -1,3 +1,3 @@
#pragma once
#define VERSION_STR "1.1.0"
#define VERSION_STR "1.2.0"

View File

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.13)
project(dab_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(dab_decoder PRIVATE "src/")
if (MSVC)
# Lib path
target_include_directories(dab_decoder PRIVATE "C:/Program Files/codec2/include/")
target_link_directories(dab_decoder PRIVATE "C:/Program Files/codec2/lib")
target_link_libraries(dab_decoder PRIVATE libcodec2)
elseif (ANDROID)
target_include_directories(dab_decoder PUBLIC
/sdr-kit/${ANDROID_ABI}/include/codec2
)
target_link_libraries(dab_decoder PUBLIC
/sdr-kit/${ANDROID_ABI}/lib/libcodec2.so
)
else ()
find_package(PkgConfig)
pkg_check_modules(LIBCODEC2 REQUIRED codec2)
target_include_directories(dab_decoder PRIVATE ${LIBCODEC2_INCLUDE_DIRS})
target_link_directories(dab_decoder PRIVATE ${LIBCODEC2_LIBRARY_DIRS})
target_link_libraries(dab_decoder PRIVATE ${LIBCODEC2_LIBRARIES})
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(dab_decoder PRIVATE "/usr/local/include")
endif()
endif ()

View File

@ -0,0 +1,280 @@
#pragma once
#include <dsp/processor.h>
#include <utils/flog.h>
#include <fftw3.h>
#include "dab_phase_sym.h"
namespace dab {
// class CyclicSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
// using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
// public:
// CyclicSync() {}
// // TODO: The default AGC rate is probably way too fast, plot out the avgCorr to see how much it moves
// CyclicSync(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { init(in, symbolLength, cyclicPrefixLength, samplerate, agcRate); }
// void init(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) {
// // Computer the number of samples for the symbol and its cyclic prefix
// symbolSamps = round(samplerate * symbolLength);
// prefixSamps = round(samplerate * cyclicPrefixLength);
// // Allocate and clear the delay buffer
// delayBuf = dsp::buffer::alloc<dsp::complex_t>(STREAM_BUFFER_SIZE + 64000);
// dsp::buffer::clear(delayBuf, symbolSamps);
// // Allocate and clear the history buffer
// histBuf = dsp::buffer::alloc<dsp::complex_t>(prefixSamps);
// dsp::buffer::clear(histBuf, prefixSamps);
// // Compute the delay input addresses
// delayBufInput = &delayBuf[symbolSamps];
// // Compute the correlation AGC configuration
// this->agcRate = agcRate;
// agcRateInv = 1.0f - agcRate;
// base_type::init(in);
// }
// void reset() {
// assert(base_type::_block_init);
// std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
// base_type::tempStop();
// base_type::tempStart();
// }
// int run() {
// int count = base_type::_in->read();
// if (count < 0) { return -1; }
// // Copy the data into the normal delay buffer
// memcpy(delayBufInput, base_type::_in->readBuf, count * sizeof(dsp::complex_t));
// // Flush the input stream
// base_type::_in->flush();
// // Do cross-correlation
// for (int i = 0; i < count; i++) {
// // Get the current history slot
// dsp::complex_t* slot = &histBuf[histId++];
// // Wrap around the history slot index (TODO: Check that the history buffer's length is correct)
// histId %= prefixSamps;
// // Kick out last value from the correlation
// corr -= *slot;
// // Save input value and compute the new prodct
// dsp::complex_t val = delayBuf[i];
// dsp::complex_t prod = val.conj()*delayBuf[i+symbolSamps];
// // Add the new value to the correlation
// *slot = prod;
// // Add the new value to the history buffer
// corr += prod;
// // Compute sample amplitude
// float rcorr = corr.amplitude();
// // If a high enough peak is reached, reset the symbol counter
// if (rcorr > avgCorr && rcorr > peakCorr) { // Note keeping an average level might not be needed
// peakCorr = rcorr;
// peakLCorr = lastCorr;
// samplesSincePeak = 0;
// }
// // If this is the sample right after the peak, save it
// if (samplesSincePeak == 1) {
// peakRCorr = rcorr;
// }
// // Write the sample to the output
// out.writeBuf[samplesSincePeak++] = val;
// // If the end of the symbol is reached, send it off
// if (samplesSincePeak >= symbolSamps) {
// if (!out.swap(symbolSamps)) {
// return -1;
// }
// samplesSincePeak = 0;
// peakCorr = 0;
// }
// // Update the average correlation
// lastCorr = rcorr;
// // Update the average correlation value
// avgCorr = agcRate*rcorr + agcRateInv*avgCorr;
// }
// // Move unused data
// memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t));
// return count;
// }
// protected:
// int symbolSamps;
// int prefixSamps;
// int histId = 0;
// dsp::complex_t* histBuf;
// dsp::complex_t* delayBuf;
// dsp::complex_t* delayBufInput;
// dsp::complex_t corr = { 0.0f, 0.0f };
// int samplesSincePeak = 0;
// float lastCorr = 0.0f;
// float peakCorr = 0.0f;
// float peakLCorr = 0.0f;
// float peakRCorr = 0.0f;
// // Note only required for DAB
// float avgCorr = 0.0f;
// float agcRate;
// float agcRateInv;
// };
class FrameFreqSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
public:
FrameFreqSync() {}
FrameFreqSync(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { init(in, agcRate); }
void init(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) {
// Allocate buffers
amps = dsp::buffer::alloc<float>(2048);
conjRef = dsp::buffer::alloc<dsp::complex_t>(2048);
corrIn = (dsp::complex_t*)fftwf_alloc_complex(2048);
corrOut = (dsp::complex_t*)fftwf_alloc_complex(2048);
// Copy the phase reference
memcpy(conjRef, DAB_PHASE_SYM_CONJ, 2048 * sizeof(dsp::complex_t));
// Plan the FFT computation
plan = fftwf_plan_dft_1d(2048, (fftwf_complex*)corrIn, (fftwf_complex*)corrOut, FFTW_FORWARD, FFTW_ESTIMATE);
// Compute the correlation AGC configuration
this->agcRate = agcRate;
agcRateInv = 1.0f - agcRate;
base_type::init(in);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Apply frequency shift
lv_32fc_t phase = lv_cmake(1.0f, 0.0f);
lv_32fc_t phaseDelta = lv_cmake(cos(offset), sin(offset));
#if VOLK_VERSION >= 030100
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
#else
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
#endif
// Compute the amplitude amplitude of all samples
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)_in->readBuf, 2048);
// Compute the average signal level by adding up all values
float level = 0.0f;
volk_32f_accumulator_s32f(&level, amps, 2048);
// Detect a frame sync condition
if (level < avgLvl * 0.5f) {
// Reset symbol counter
sym = 1;
// Update the average level
avgLvl = agcRate*level + agcRateInv*avgLvl;
// Flush the input stream and return
base_type::_in->flush();
return count;
}
// Update the average level
avgLvl = agcRate*level + agcRateInv*avgLvl;
// Handle phase reference
if (sym == 1) {
// Output the symbols (DEBUG ONLY)
memcpy(corrIn, _in->readBuf, 2048 * sizeof(dsp::complex_t));
fftwf_execute(plan);
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
int outCount = 0;
dsp::complex_t pi4 = { cos(3.1415926535*0.25), sin(3.1415926535*0.25) };
for (int i = -767; i < 768; i++) {
if (!i) { continue; }
int cid0 = ((i-1) >= 0) ? (i-1) : 2048+(i-1);
int cid1 = (i >= 0) ? i : 2048+i;;
out.writeBuf[outCount++] = pi4 * (corrOut[cid1] * corrOut[cid0].conj()) * (1.0f/(amps[cid0]*amps[cid0]));
}
out.swap(outCount);
// Multiply the samples with the conjugated phase reference signal
volk_32fc_x2_multiply_32fc((lv_32fc_t*)corrIn, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)conjRef, 2048);
// Compute the FFT of the product
fftwf_execute(plan);
// Compute the amplitude of the bins
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
// Locate highest power bin
uint32_t peakId;
volk_32f_index_max_32u(&peakId, amps, 2048);
// Obtain the value of the bins next to the peak
float peakL = amps[(peakId + 2047) % 2048];
float peakR = amps[(peakId + 1) % 2048];
// Compute the integer frequency offset
float offInt = (peakId < 1024) ? (float)peakId : ((float)peakId - 2048.0f);
// Compute the frequency offset in rad/samp
float off = 3.1415926535f * (offInt + ((peakR - peakL) / (peakR + peakL))) * (1.0f / 1024.0f);
// Run control loop
offset -= 0.1f*off;
flog::debug("Offset: {} Hz, Error: {} Hz, Avg Level: {}", offset * (0.5f/3.1415926535f)*2.048e6, off * (0.5f/3.1415926535f)*2.048e6, avgLvl);
}
// Increment the symbol counter
sym++;
// Flush the input stream and return
base_type::_in->flush();
return count;
}
protected:
fftwf_plan plan;
float* amps;
dsp::complex_t* conjRef;
dsp::complex_t* corrIn;
dsp::complex_t* corrOut;
int sym;
float offset = 0.0f;
float avgLvl = 0.0f;
float agcRate;
float agcRateInv;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
#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>
#include "ofdm.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("sync5.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, 2048, 504, 1e-3, INPUT_SAMPLE_RATE, 1e-6, 0.01, 0.005);
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::ofdm::CyclicTimeSync csync;
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();
}

View File

@ -0,0 +1,324 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/phase_control_loop.h>
#include <dsp/taps/windowed_sinc.h>
#include <dsp/multirate/polyphase_bank.h>
#include <dsp/math/step.h>
namespace dsp::ofdm {
class CyclicTimeSync : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t> ;
public:
CyclicTimeSync() {}
CyclicTimeSync(stream<complex_t>* in, int fftSize, int cpSize, double usefulSymbolTime, double samplerate,
double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
init(in, fftSize, cpSize, usefulSymbolTime, samplerate, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount);
}
~CyclicTimeSync() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::multirate::freePolyphaseBank(interpBank);
buffer::free(corrSampCache);
buffer::free(corrProdCache);
buffer::free(buffer);
}
void init(stream<complex_t>* in, int fftSize, int cpSize, double usefulSymbolTime, double samplerate,
double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
// Save parameters
this->fftSize = fftSize;
this->cpSize = cpSize;
period = fftSize + cpSize;
// Compute the interpolator settings
omega = (usefulSymbolTime * samplerate) / (double)fftSize;
this->omegaGain = omegaGain;
this->muGain = muGain;
this->omegaRelLimit = omegaRelLimit;
this->interpPhaseCount = interpPhaseCount;
this->interpTapCount = interpTapCount;
// Compute the correlator AGC settings
// TODO: Compute it using he FFT and CP sizes
this->corrAgcRate = 1e-4;
corrAgcInvRate = 1.0f - corrAgcRate;
// Initialize the control loop
pcl.init(muGain, omegaGain, 0.0, 0.0, 1.0, omega, omega * (1.0 - omegaRelLimit), omega * (1.0 + omegaRelLimit));
// Generate the interpolation taps
generateInterpTaps();
// Allocate the buffers
corrSampCache = buffer::alloc<complex_t>(fftSize);
corrProdCache = buffer::alloc<complex_t>(cpSize);
buffer = buffer::alloc<complex_t>(STREAM_BUFFER_SIZE + interpTapCount);
bufStart = &buffer[interpTapCount - 1];
// Clear the buffers
buffer::clear(corrSampCache, fftSize);
buffer::clear(corrProdCache, cpSize);
buffer::clear(buffer, interpTapCount - 1);
base_type::init(in);
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
this->omegaGain = omegaGain;
pcl.setCoefficients(muGain, omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
this->muGain = muGain;
pcl.setCoefficients(muGain, omegaGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
this->omegaRelLimit = omegaRelLimit;
pcl.setFreqLimits(omega * (1.0 - omegaRelLimit), omega * (1.0 + omegaRelLimit));
}
void setInterpParams(int interpPhaseCount, int interpTapCount) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
this->interpPhaseCount = interpPhaseCount;
this->interpTapCount = interpTapCount;
dsp::multirate::freePolyphaseBank(interpBank);
buffer::free(buffer);
generateInterpTaps();
buffer = buffer::alloc<complex_t>(STREAM_BUFFER_SIZE + interpTapCount);
bufStart = &buffer[interpTapCount - 1];
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
pcl.phase = 0.0f;
pcl.freq = omega;
// TODO: The rest
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Copy data to work buffer
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(complex_t));
// Process all samples
while (offset < count) {
// Get the cache slots
complex_t* sampSlot = &corrSampCache[corrSampCacheId++];
complex_t* prodSlot = &corrProdCache[corrProdCacheId++];
corrSampCacheId %= fftSize;
corrProdCacheId %= cpSize;
// Compute the interpolated sample
complex_t sample;
int phase = std::clamp<int>(floorf(pcl.phase * (float)interpPhaseCount), 0, interpPhaseCount - 1);
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&sample, (lv_32fc_t*)&buffer[offset], interpBank.phases[phase], interpTapCount);
// Write the sample to the output
if (outCount >= cpSize) {
out.writeBuf[outCount - cpSize] = sample;
}
// Send out a symbol when it's fully received
if ((++outCount) >= fftSize+cpSize) {
if (!out.swap(outCount)) { break; }
outCount = 0;
}
// Run autocorrelation
complex_t prod = sample.conj()*(*sampSlot);
corr += prod;
corr -= *prodSlot;
// Write back the new sample and product value to the cache
*sampSlot = sample;
*prodSlot = prod;
// Compute the correlation level
float corrLvl = corr.amplitude();
// Detect peak in autocorrelation (TODO: level check maybe not needed now that corrPeak is reset to corrLvl)
if (corrLvl > corrAvg && corrLvl > corrPeak) {
// Save the current correlation as the peak
corrPeak = corrLvl;
// Save the value of the previous correlation as the left side of the peak
corrPeakL = corrLast;
// Reset the peak distance counter
sincePeak = 0;
}
// The first sample after a peak is the right-side sample
if (sincePeak == 1) {
corrPeakR = corrLvl;
}
else if (sincePeak == cpSize) {
// Start the useful symbol counter
sinceCp = 0;
// Compute the fractional error (TODO: Probably very inaccurate with noise, use real slopes instead)
if (corrPeakL > corrPeakR) {
float maxSlope = corrPeakR - corrPeak;
float slope = corrPeak - corrPeakL;
fracErr = std::clamp<float>(0.5f * (1.0f + slope / maxSlope), -0.5f, 0.5f);
}
else {
float maxSlope = corrPeak - corrPeakL;
float slope = corrPeakR - corrPeak;
fracErr = std::clamp<float>(-0.5f * (1.0f + slope / maxSlope), -0.5f, 0.5f);
}
}
else if (sincePeak == fftSize) {
// Reset the peak detector
corrPeak = corrAvg;
}
// NOTE: THIS IS ONLY NEEDED FOR DAB
// Detect a wider-than-normal distance to adapt the output counter
else if (sincePeak == 2656) {
// Reset the output counter
outCount = 50;
}
// Last sample of useful symbol
if (sinceCp == fftSize) {
// If the fractional error is valid, run closed-loop
float err = 0.0f;
if (!std::isnan(fracErr)) {
// Compute the measured period using the distance to the last symbol
float measuredPeriod = (float)sinceLastSym - fracErr;
// NOTE: THIS IS ONLY NEEDED FOR DAB
if (measuredPeriod > 3828.0f) {
// Null symbol
err = measuredPeriod - (2552.0f+2656.0f);
}
else {
// Regular symbol
err = measuredPeriod - period;
}
err = std::clamp<float>(err, -10.0f, 10.0f);
// Run the control loop in closed-loop mode
pcl.advance(err);
}
else {
// Otherwise, run open-loop
pcl.advancePhase();
}
// printf("%d\n", outCount);
// Nudge the symbol window if it's too out of sync
if (outCount > 100) {
// TODO: MOVE THE LAST SAMPLES OR THE SYMBOL WILL BE CORRUPTED!
outCount = 50;
flog::debug("NUDGE!");
}
// Reset the period counter
sinceLastSym = 0;
}
else {
// Run the control loop in open-loop mode
pcl.advancePhase();
}
// Update the offset and phase
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
// Update the last correlation level
corrLast = corrLvl;
// Update correlation AGC
corrAvg = corrAvg*corrAgcInvRate + corrLvl*corrAgcRate;
// Increment the distance counters (TODO: Check if they happen at the right point, eg. after being reset to zero)
sincePeak++;
sinceLastSym++;
sinceCp++;
}
// Prepare offset for next buffer of samples
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (interpTapCount - 1) * sizeof(complex_t));
// Swap if some data was generated
base_type::_in->flush();
return count;
}
protected:
void generateInterpTaps() {
double bw = 0.5 / (double)interpPhaseCount;
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(interpPhaseCount * interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, interpPhaseCount);
interpBank = dsp::multirate::buildPolyphaseBank<float>(interpPhaseCount, lp);
taps::free(lp);
}
// OFDM Configuration
int fftSize;
int cpSize;
float period;
// Interpolator
dsp::multirate::PolyphaseBank<float> interpBank;
int interpPhaseCount;
int interpTapCount;
int offset = 0;
complex_t* buffer = NULL;
complex_t* bufStart;
// Control loop
loop::PhaseControlLoop<float, false> pcl;
double omega;
double omegaGain;
double muGain;
double omegaRelLimit;
float fracErr = 0.0f;
// Autocorrelator
complex_t corr = {0.0f, 0.0f};
complex_t* corrSampCache = NULL;
complex_t* corrProdCache = NULL;
int corrSampCacheId = 0;
int corrProdCacheId = 0;
float corrAgcRate;
float corrAgcInvRate;
float corrAvg = 0;
float corrLast = 0;
float corrPeakR = 0;
float corrPeak = 0;
float corrPeakL = 0;
// Peak detection
int sincePeak = 0;
int sinceLastSym = 0;
int sinceCp = 0;
// Other shit to categorize
int outCount = 0;
};
};

View File

@ -0,0 +1,31 @@
cyclicLen = 4
usefulLen = 12
A = 0*12 + 1*13 + 2*14 + 3*15
B = 1*13 + 2*14 + 3*15 + 4*16 = A - 0*12 + 4*16
C = 2*14 + 3*15 + 4*16 + 5*17 = B - 1*13 + 5*17
D = 3*15 + 4*16 + 5*17 + 6*18 = C - 2*14 + 6*18
E = 4*16 + 5*17 + 6*18 + 7*19 = D - 3*15 + 7*19
F = 5*17 + 6*18 + 7*19 + 8*20 = E - 4*16 + 8*20
G = 6*18 + 7*19 + 8*20 + 9*21 = F - 5*17 + 9*21
H = 7*19 + 8*20 + 9*21 + 10*22 = G - 6*18 + 10*22
I = 8*20 + 9*21 + 10*22 + 11*23 = H - 7*19 + 11*23
J = 9*21 + 10*22 + 11*23 + 12*24 = I - 8*20 + 12*24
K = 10*22 + 11*23 + 12*24 + 13*25 = J - 9*21 + 13*25
L = 11*23 + 12*24 + 13*25 + 14*26 = K - 10*22 + 14*26
M = 12*24 + 13*25 + 14*26 + 15*27 = L - 11*23 + 15*27
N = 13*25 + 14*26 + 15*27 + 16*28 = M - 12*24 + 16*28
O = 14*26 + 15*27 + 16*28 + 17*29 = N - 13*25 + 17*29
P = 15*27 + 16*28 + 17*29 + 18*30 = O - 14*26 + 18*30
Q = 16*28 + 17*29 + 18*30 + 19*31 = P - 15*27 + 19*31
R = 17*29 + 18*30 + 19*31 + 20*32 = Q - 16*28 + 20*32
S = 18*30 + 19*31 + 20*32 + 21*33 = R - 17*29 + 21*33
T = 19*31 + 20*32 + 21*33 + 22*34 = S - 18*30 + 22*34
U = 20*32 + 21*33 + 22*34 + 23*35 = T - 19*31 + 23*35
Conclusion:
sampCacheLen = usefulLen
prodCacheLen = cyclicLen
Peak correlation occurs when the current interpolated value is the last FFT sample

View File

@ -0,0 +1,397 @@
5209
2553
2551
2551
2552
2551
2553
2555
2549
2552
2552
2554
2550
2553
2553
2550
2553
2552
2550
2552
2548
2558
2551
2546
2559
2551
2552
2552
2551
2554
2555
2549
2550
2549
2557
2551
2554
2550
2552
2546
2559
2551
2554
2550
2550
2557
2550
2556
2543
2556
2551
2554
2551
2553
2554
2550
2551
2552
2552
2553
2550
2553
2546
2558
2552
2552
2552
2550
2555
2551
2551
2554
2553
2553
2549
2552
5208
2554
2550
2552
2551
2553
2551
2553
2551
2553
2550
2554
2553
2551
2553
2552
2546
2557
2551
2553
2552
2551
2553
2551
2548
2557
2553
2551
2562
2539
2559
2548
2551
2551
2550
2556
2551
2554
2550
2552
2552
2548
2556
2550
2554
2553
2553
2550
2552
2552
2550
2555
2558
2545
2552
2553
2548
2555
2552
2552
2557
2550
2548
2553
2554
2550
2552
2547
2558
2551
2552
2552
2552
2555
2549
2552
5208
2552
2552
2552
2550
2555
2550
2553
2553
2551
2552
2547
2557
2552
2552
2552
2552
2552
2552
2552
2546
2558
2551
2552
2553
2551
2553
2553
2552
2551
2552
2552
2551
2553
2552
2552
2552
2552
2552
2552
2556
2548
2551
2555
2550
2552
2555
2549
2552
2552
2550
2553
2551
2554
2552
2551
2553
2553
2551
2548
2550
2555
2554
2553
2556
2547
2552
2554
2552
2550
2552
2552
2553
2552
2551
2552
5209
2553
2551
2551
2547
2558
2550
2553
2552
2553
2552
2549
2551
2557
2549
2554
2551
2554
2550
2553
2553
2551
2551
2548
2559
2549
2552
2552
2552
2551
2551
2550
2557
2551
2552
2555
2549
2557
2549
2550
2553
2549
2554
2553
2553
2550
2552
2553
2550
2553
2553
2551
2547
2557
2552
2552
2551
2553
2549
2555
2556
2550
2550
2553
2551
2552
2553
2551
2552
2552
2552
2553
2551
2541
2563
2552
5210
2550
2551
2552
2553
2553
2551
2552
2552
2552
2552
2558
2547
2551
2552
2552
2552
2552
2550
2554
2551
2553
2552
2551
2553
2551
2553
2553
2550
2551
2555
2550
2552
2552
2553
2553
2550
2552
2553
2550
2553
2558
2546
2553
2554
2550
2552
2553
2548
2554
2552
2552
2552
2551
2554
2550
2553
2553
2550
2551
2552
2554
2553
2551
2551
2555
2550
2553
2552
2550
2554
2550
2553
2553
2553
2550
5210
2553
2549
2553
2551
2552
2552
2556
2548
2549
2555
2551
2557
2548
2552
2552
2552

View File

@ -24,12 +24,13 @@ namespace demod {
class Demodulator {
public:
virtual ~Demodulator() {}
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) = 0;
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) = 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;

View File

@ -7,13 +7,13 @@ namespace demod {
public:
AM() {}
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
}
~AM() { stop(); }
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
_config = config;
@ -68,6 +68,8 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "AM"; }

View File

@ -7,15 +7,15 @@ namespace demod {
public:
CW() {}
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
}
~CW() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
this->_config = config;
this->afbwChangeHandler = afbwChangeHandler;
@ -74,6 +74,8 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "CW"; }

View File

@ -7,15 +7,15 @@ namespace demod {
public:
DSB() {}
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
}
~DSB() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
_config = config;
@ -61,6 +61,8 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "DSB"; }

View File

@ -7,15 +7,15 @@ namespace demod {
public:
LSB() {}
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
}
~LSB() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
_config = config;
@ -61,6 +61,8 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "LSB"; }

View File

@ -7,13 +7,13 @@ namespace demod {
public:
NFM() {}
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
}
~NFM() { stop(); }
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
this->_config = config;
@ -57,6 +57,8 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "FM"; }

View File

@ -7,18 +7,17 @@ namespace demod {
public:
RAW() {}
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
}
~RAW() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
audioSampleRate = 48000;
// TODO: This needs to be selectable
audioSampleRate = audioSR;
// Define structure
c2s.init(input);
@ -40,6 +39,10 @@ namespace demod {
c2s.setInput(input);
}
void AFSampRateChanged(double newSR) {
audioSampleRate = newSR;
}
// ============= INFO =============
const char* getName() { return "RAW"; }

View File

@ -8,15 +8,15 @@ namespace demod {
public:
USB() {}
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
}
~USB() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
_config = config;
@ -62,6 +62,8 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "USB"; }

View File

@ -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) : diag(0.5, 4096) {
init(name, config, input, bandwidth);
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() {
@ -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) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
this->name = name;
_config = config;
@ -252,6 +252,8 @@ namespace demod {
demod.setInput(input);
}
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "WFM"; }

View File

@ -81,16 +81,30 @@ 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
stream = sigpath::streamManager.createStream(name, afChain.out, 48000);
srChangeHandler.ctx = this;
srChangeHandler.handler = sampleRateChangeHandler;
stream.init(afChain.out, &srChangeHandler, audioSampleRate);
sigpath::sinkManager.registerStream(name, &stream);
// 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);
@ -101,10 +115,11 @@ public:
~RadioModule() {
core::modComManager.unregisterInterface(name);
gui::menu.removeEntry(name);
stream.stop();
if (enabled) {
disable();
}
sigpath::streamManager.destroyStream(stream);
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
@ -116,7 +131,9 @@ 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() {
@ -124,7 +141,6 @@ public:
ifChain.stop();
if (selectedDemod) { selectedDemod->stop(); }
afChain.stop();
stream->stopDSP();
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
vfo = NULL;
}
@ -297,7 +313,7 @@ private:
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
// Initialize
demod->init(name, &config, ifChain.out, bw);
demod->init(name, &config, ifChain.out, bw, stream.getSampleRate());
return demod;
}
@ -321,34 +337,22 @@ private:
}
void selectDemod(demod::Demodulator* demod) {
// Stop the IF chain
ifChain.stop();
// Stop the current demodulator
// Stopcurrently selected demodulator and select new
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
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;
}
// Select the new demodulator
selectedDemod = demod;
// Give the demodulator the most recent audio SR
selectedDemod->AFSampRateChanged(audioSampleRate);
// 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();
@ -436,30 +440,21 @@ private:
// Configure AF chain
if (postProcEnabled) {
// Configure resampler
deemp.setSamplerate(selectedDemod->getAFSampleRate());
afChain.stop();
resamp.setInSamplerate(selectedDemod->getAFSampleRate());
setAudioSampleRate(audioSampleRate);
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// 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();
}
@ -475,12 +470,37 @@ 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();
@ -564,6 +584,11 @@ 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; }
@ -572,14 +597,16 @@ private:
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
RadioModule* _this = (RadioModule*)ctx;
if (!_this->enabled || !_this->selectedDemod) { return; }
// If no demod is selected, reject the command
if (!_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) {
else if (code == RADIO_IFACE_CMD_SET_MODE && in && _this->enabled) {
int* _in = (int*)in;
_this->selectDemodByID((DemodID)*_in);
}
@ -587,7 +614,7 @@ private:
float* _out = (float*)out;
*_out = _this->bandwidth;
}
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in) {
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in && _this->enabled) {
float* _in = (float*)in;
if (_this->bandwidthLocked) { return; }
_this->setBandwidth(*_in);
@ -596,7 +623,7 @@ private:
bool* _out = (bool*)out;
*_out = _this->squelchEnabled;
}
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in) {
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in && _this->enabled) {
bool* _in = (bool*)in;
_this->setSquelchEnabled(*_in);
}
@ -604,7 +631,7 @@ private:
float* _out = (float*)out;
*_out = _this->squelchLevel;
}
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in) {
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in && _this->enabled) {
float* _in = (float*)in;
_this->setSquelchLevel(*_in);
}
@ -618,6 +645,7 @@ private:
// Handlers
EventHandler<double> onUserChangedBandwidthHandler;
EventHandler<float> srChangeHandler;
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
@ -632,9 +660,10 @@ 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;
std::shared_ptr<MasterStream> stream;
SinkManager::Stream stream;
demod::Demodulator* selectedDemod = NULL;

View File

@ -0,0 +1,8 @@
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/")

View File

@ -0,0 +1,139 @@
#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_() {
}

View File

@ -0,0 +1,74 @@
#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;
}
}

View File

@ -0,0 +1,71 @@
#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;
};
}

View File

@ -0,0 +1,37 @@
#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);
}
}

View File

@ -0,0 +1,42 @@
#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];
};
}

View File

@ -0,0 +1,137 @@
#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;
}
}

View File

@ -0,0 +1,87 @@
#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;
};
}

View File

@ -0,0 +1,126 @@
#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];
}
}

View File

@ -0,0 +1,89 @@
#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;
};
}

View File

@ -0,0 +1,194 @@
#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;
}
}

View File

@ -0,0 +1,69 @@
#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;
};
}

View File

@ -0,0 +1,169 @@
#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
};
}

View File

@ -0,0 +1,82 @@
#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;
};
}

View File

@ -0,0 +1,177 @@
#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;
}
}

View File

@ -0,0 +1,69 @@
#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;
};
}

View File

@ -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
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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
@ -25,10 +25,30 @@ 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
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 ..

View File

@ -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
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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
@ -25,10 +25,30 @@ 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
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 ..

View File

@ -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
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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
@ -25,10 +25,30 @@ 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
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
make VERBOSE=1 -j2
cd ..

View File

@ -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
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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
@ -25,10 +25,30 @@ 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
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 ..

View File

@ -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
libcodec2-dev libudev-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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 a more recent libusb version
@ -51,6 +51,26 @@ 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
@ -66,7 +86,7 @@ echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
make VERBOSE=1 -j2
# Generate package

View File

@ -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
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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
@ -25,10 +25,30 @@ 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
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 ..

View File

@ -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
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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
@ -25,10 +25,30 @@ 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
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 ..

View File

@ -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
libcodec2-dev autoconf libtool xxd libspdlog-dev
# Install SDRPlay libraries
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
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
@ -25,10 +25,30 @@ 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
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 ..

View File

@ -0,0 +1,4 @@
FROM ubuntu:noble
ENV DEBIAN_FRONTEND=noninteractive
COPY do_build.sh /root
RUN chmod +x /root/do_build.sh

View File

@ -0,0 +1,55 @@
#!/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'

View File

@ -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.1.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control
echo Version: 1.2.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

View File

@ -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.1.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
# ========================= Install binaries =========================
@ -35,11 +35,13 @@ 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/file_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/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

View File

@ -24,6 +24,9 @@ cp 'C:/Program Files/PothosSDR/bin/bladeRF.dll' sdrpp_windows_x64/
cp $build_dir/source_modules/file_source/Release/file_source.dll sdrpp_windows_x64/modules/
cp $build_dir/source_modules/fobossdr_source/Release/fobossdr_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/RigExpert/Fobos/bin/fobos.dll' sdrpp_windows_x64/
cp $build_dir/source_modules/hackrf_source/Release/hackrf_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/PothosSDR/bin/hackrf.dll' sdrpp_windows_x64/
@ -39,6 +42,11 @@ 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/

View File

@ -249,7 +249,6 @@ 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)) {
@ -284,8 +283,11 @@ 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)) {
@ -294,6 +296,7 @@ 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();
@ -476,9 +479,9 @@ private:
sprintf(monStr, "%02d", ltm->tm_mon + 1);
sprintf(yearStr, "%02d", ltm->tm_year + 1900);
if (core::modComManager.getModuleName(name) == "radio") {
int mode;
int mode = -1;
core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
modeStr = radioModeToString[mode];
if (mode >= 0) { modeStr = radioModeToString[mode]; };
}
// Replace in template

View File

@ -41,14 +41,13 @@ 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, run:
Then, use apt to install it:
```sh
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
sudo apt install path/to/the/sdrpp_debian_amd64.deb
```
If `libvolk2-dev` is not available, use `libvolk1-dev`.
**IMPORTANT: You must install the drivers for your SDR. Follow instructions from your manufacturer as to how to do this on your particular distro.**
### Arch-based
@ -325,12 +324,15 @@ 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 | Beta | 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 | ✅ | ✅ | ✅ |
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_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 | ✅ | ✅ | ✅ |
@ -338,9 +340,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
@ -357,6 +359,7 @@ Modules in beta are still included in releases for the most part but not enabled
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:|
| 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 | ⛔ | ✅ | ⛔ |

View File

@ -0,0 +1,645 @@
{
"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"
}
]
}

View File

@ -2,8 +2,8 @@
"name": "France",
"country_name": "France",
"country_code": "FR",
"author_name": "Fred F4EED",
"author_url": "http://f4eed.wordpress.com",
"author_name": "Fred F4EED, Armand31",
"author_url": "http://f4eed.wordpress.com, https://github.com/Armand31",
"bands": [
{
"name": "137KHz - Radioamateur",
@ -355,7 +355,7 @@
"end": 54000000
},
{
"name": "Bande FM - Radiodif.",
"name": "Radiodiffusion - Bande FM",
"type": "broadcast",
"start": 80000000,
"end": 108000000
@ -396,6 +396,12 @@
"start": 162362500,
"end": 162587500
},
{
"name": "Radiodiffusion - Bande DAB",
"type": "broadcast",
"start": 174000000,
"end": 223000000
},
{
"name": "Military Aviation",
"type": "military",
@ -408,6 +414,12 @@
"start": 240000000,
"end": 270000000
},
{
"name": "Police (TETRAPOL)",
"type": "military",
"start": 380000000,
"end": 400000000
},
{
"name": "70cm - Radioamateur",
"type": "amateur",
@ -420,12 +432,24 @@
"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",

View File

@ -0,0 +1,285 @@
{
"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
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -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,51 +16,36 @@ SDRPP_MOD_INFO{
/* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 2, 0,
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
bool operator==(const RtAudio::DeviceInfo& a, const RtAudio::DeviceInfo& b) {
return a.name == b.name;
}
class AudioSink : public Sink {
class AudioSink : SinkManager::Sink {
public:
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);
AudioSink(SinkManager::Stream* stream, std::string streamName) {
_stream = stream;
_streamName = streamName;
s2m.init(_stream->sinkOut);
monoPacker.init(&s2m.out, 512);
stereoPacker.init(stream, 512);
stereoPacker.init(_stream->sinkOut, 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)) {
// 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);
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);
// List devices
RtAudio::DeviceInfo info;
#if RTAUDIO_VERSION_MAJOR >= 6
for (int i : audio.getDeviceIds()) {
@ -75,13 +60,15 @@ public:
#endif
if (info.outputChannels == 0) { continue; }
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
devList.define(i, info.name, info);
devList.push_back(info);
deviceIds.push_back(i);
txtDevList += info.name;
txtDevList += '\0';
}
catch (const std::exception& e) {
flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what());
}
}
selectByName(device);
}
@ -115,92 +102,67 @@ public:
}
void selectById(int id) {
// Update ID
devId = id;
selectedDevName = devList[id].name;
// List samplerates and select default SR
char buf[256];
sampleRates.clear();
const auto& srList = devList[id].sampleRates;
unsigned int defaultSr = devList[id].preferredSampleRate;
for (auto& sr : srList) {
if (sr == defaultSr) {
srId = sampleRates.size();
sampleRate = sr;
}
sprintf(buf, "%d", sr);
sampleRates.define(sr, buf, sr);
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;
}
// // 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();
sampleRate = config.conf[_streamName]["devices"][devList[id].name];
config.release(created);
// Lock the sink
auto lck = entry->getLock();
sampleRates = devList[id].sampleRates;
sampleRatesTxt = "";
char buf[256];
bool found = false;
unsigned int defaultId = 0;
unsigned int defaultSr = devList[id].preferredSampleRate;
for (int i = 0; i < sampleRates.size(); i++) {
if (sampleRates[i] == sampleRate) {
found = true;
srId = i;
}
if (sampleRates[i] == defaultSr) {
defaultId = i;
}
sprintf(buf, "%d", sampleRates[i]);
sampleRatesTxt += buf;
sampleRatesTxt += '\0';
}
if (!found) {
sampleRate = defaultSr;
srId = defaultId;
}
// Stop the sink DSP
// TODO: Only if the sink DSP is running, otherwise you risk starting it when it shouldn't
entry->stopDSP();
_stream->setSampleRate(sampleRate);
// Stop the sink
if (running) { doStop(); }
// Update stream samplerate
entry->setSamplerate(sampleRate);
// Start the DSP
entry->startDSP();
// Start the sink
if (running) { doStart(); }
}
void showMenu() {
void menuHandler() {
float menuWidth = ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_dev_" + stringId).c_str(), &devId, devList.txt)) {
if (ImGui::Combo(("##_audio_sink_dev_" + _streamName).c_str(), &devId, txtDevList.c_str())) {
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_" + stringId).c_str(), &srId, sampleRates.txt)) {
if (ImGui::Combo(("##_audio_sink_sr_" + _streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
sampleRate = sampleRates[srId];
// 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);
_stream->setSampleRate(sampleRate);
if (running) {
doStop();
doStart();
}
config.acquire();
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
config.release(true);
}
}
@ -223,12 +185,12 @@ public:
private:
bool doStart() {
RtAudio::StreamParameters parameters;
parameters.deviceId = devList.key(devId);
parameters.deviceId = deviceIds[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(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
@ -267,34 +229,44 @@ 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;
OptionList<unsigned int, RtAudio::DeviceInfo> devList;
OptionList<unsigned int, unsigned int> sampleRates;
std::vector<RtAudio::DeviceInfo> devList;
std::vector<unsigned int> deviceIds;
std::string txtDevList;
std::vector<unsigned int> sampleRates;
std::string sampleRatesTxt;
unsigned int sampleRate = 48000;
RtAudio audio;
};
class AudioSinkModule : public ModuleManager::Instance, public SinkProvider {
class AudioSinkModule : public ModuleManager::Instance {
public:
AudioSinkModule(std::string name) {
this->name = name;
sigpath::streamManager.registerSinkProvider("Audio", this);
provider.create = create_sink;
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Audio", provider);
}
~AudioSinkModule() {
sigpath::streamManager.unregisterSinkProvider(this);
// Unregister sink, this will automatically stop and delete all instances of the audio sink
sigpath::sinkManager.unregisterSinkProvider("Audio");
}
void postInit() {}
@ -311,13 +283,14 @@ public:
return enabled;
}
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:
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
}
private:
std::string name;
bool enabled = true;
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {
@ -339,4 +312,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}
}

View File

@ -3,14 +3,13 @@
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/stream.h>
#include <signal_path/sink.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())
@ -19,97 +18,84 @@ SDRPP_MOD_INFO{
/* Name: */ "network_sink",
/* Description: */ "Network sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 2, 0,
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
enum SinkMode {
enum {
SINK_MODE_TCP,
SINK_MODE_UDP
};
const char* sinkModesTxt = "TCP\0UDP\0";
class NetworkSink : public Sink {
class NetworkSink : SinkManager::Sink {
public:
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);
NetworkSink(SinkManager::Stream* stream, std::string streamName) {
_stream = stream;
_streamName = streamName;
// 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);
// 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;
}
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];
// Init DSP
packer.init(stream, 512);
packer.init(_stream->sinkOut, 512);
s2m.init(&packer.out);
monoSink.init(&s2m.out, monoHandler, this);
stereoSink.init(&packer.out, stereoHandler, this);
// 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());
}
if (config.conf[stringId].contains("port")) {
port = config.conf[stringId]["port"];
}
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 (config.conf[stringId].contains("samplerate")) {
int nSr = config.conf[stringId]["samplerate"];
if (samplerates.keyExists(nSr)) {
sampleRate = samplerates.value(samplerates.keyId(nSr));
}
else {
sampleRate = 48000;
}
}
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);
// Create a list of sample rates
for (int sr = 12000; sr < 200000; sr += 12000) {
sampleRates.push_back(sr);
}
for (int sr = 11025; sr < 192000; sr += 11025) {
sampleRates.push_back(sr);
}
// Set samplerate ID
srId = samplerates.valueId(sampleRate);
// 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 (sr == 48000.0) { _48kId = id; }
id++;
}
if (!found) {
srId = _48kId;
sampleRate = 48000.0;
}
_stream->setSampleRate(sampleRate);
// Start if needed
if (startNow) { startServer(); }
@ -136,30 +122,30 @@ public:
running = false;
}
void showMenu() {
void menuHandler() {
float menuWidth = ImGui::GetContentRegionAvail().x;
bool listening = (listener && listener->isListening()) || (conn && conn->isOpen());
if (listening) { style::beginDisabled(); }
if (ImGui::InputText(CONCAT("##_network_sink_host_", stringId), hostname, 1023)) {
if (ImGui::InputText(CONCAT("##_network_sink_host_", _streamName), hostname, 1023)) {
config.acquire();
config.conf[stringId]["hostname"] = hostname;
config.conf[_streamName]["hostname"] = hostname;
config.release(true);
}
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputInt(CONCAT("##_network_sink_port_", stringId), &port, 0, 0)) {
if (ImGui::InputInt(CONCAT("##_network_sink_port_", _streamName), &port, 0, 0)) {
config.acquire();
config.conf[stringId]["port"] = port;
config.conf[_streamName]["port"] = port;
config.release(true);
}
ImGui::LeftLabel("Protocol");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_network_sink_mode_", stringId), &modeId, sinkModesTxt)) {
if (ImGui::Combo(CONCAT("##_network_sink_mode_", _streamName), &modeId, sinkModesTxt)) {
config.acquire();
config.conf[stringId]["mode"] = modeId;
config.conf[_streamName]["protocol"] = modeId;
config.release(true);
}
@ -167,33 +153,33 @@ public:
ImGui::LeftLabel("Samplerate");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_network_sink_sr_", stringId), &srId, samplerates.txt)) {
sampleRate = samplerates.value(srId);
entry->setSamplerate(sampleRate);
if (ImGui::Combo(CONCAT("##_network_sink_sr_", _streamName), &srId, sampleRatesTxt.c_str())) {
sampleRate = sampleRates[srId];
_stream->setSampleRate(sampleRate);
packer.setSampleCount(sampleRate / 60);
config.acquire();
config.conf[stringId]["samplerate"] = sampleRate;
config.conf[_streamName]["sampleRate"] = sampleRate;
config.release(true);
}
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", stringId), &stereo)) {
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", _streamName), &stereo)) {
stop();
start();
config.acquire();
config.conf[stringId]["stereo"] = stereo;
config.conf[_streamName]["stereo"] = stereo;
config.release(true);
}
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
stopServer();
config.acquire();
config.conf[stringId]["running"] = false;
config.conf[_streamName]["listening"] = false;
config.release(true);
}
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
startServer();
config.acquire();
config.conf[stringId]["running"] = true;
config.conf[_streamName]["listening"] = true;
config.release(true);
}
@ -231,14 +217,19 @@ private:
}
void startServer() {
if (modeId == SINK_MODE_TCP) {
listener = net::listen(hostname, port);
if (listener) {
listener->acceptAsync(clientHandler, this);
try {
if (modeId == SINK_MODE_TCP) {
listener = net::listen(hostname, port);
if (listener) {
listener->acceptAsync(clientHandler, this);
}
}
else {
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
}
}
else {
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
catch (const std::exception& e) {
flog::error("Failed to open socket: {}", e.what());
}
}
@ -285,40 +276,47 @@ private:
_this->listener->acceptAsync(clientHandler, _this);
}
// DSP
SinkManager::Stream* _stream;
dsp::buffer::Packer<dsp::stereo_t> packer;
dsp::convert::StereoToMono s2m;
dsp::sink::Handler<float> monoSink;
dsp::sink::Handler<dsp::stereo_t> stereoSink;
OptionList<std::string, SinkMode> modes;
OptionList<int, double> samplerates;
std::string _streamName;
char hostname[1024];
int port = 7355;
SinkMode mode = SINK_MODE_TCP;
int modeId;
int sampleRate = 48000;
int srId;
bool stereo = false;
int srId = 0;
bool running = false;
char hostname[1024];
int port = 4242;
int modeId = 1;
std::vector<unsigned int> sampleRates;
std::string sampleRatesTxt;
unsigned int sampleRate = 48000;
bool stereo = false;
int16_t* netBuf;
net::Listener listener;
net::Conn conn;
std::mutex connMtx;
};
class NetworkSinkModule : public ModuleManager::Instance, SinkProvider {
class NetworkSinkModule : public ModuleManager::Instance {
public:
NetworkSinkModule(std::string name) {
// Register self as provider
sigpath::streamManager.registerSinkProvider("Network", this);
this->name = name;
provider.create = create_sink;
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Network", provider);
}
~NetworkSinkModule() {
// Unregister self
sigpath::streamManager.unregisterSinkProvider(this);
// Unregister sink, this will automatically stop and delete all instances of the audio sink
sigpath::sinkManager.unregisterSinkProvider("Network");
}
void postInit() {}
@ -335,13 +333,14 @@ public:
return enabled;
}
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:
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new NetworkSink(stream, streamName));
}
private:
std::string name;
bool enabled = true;
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {

View File

@ -152,7 +152,6 @@ public:
// Update samplerate from ID
sampleRate = sampleRates[srId];
core::setInputSampleRate(sampleRate);
}
private:
@ -232,6 +231,7 @@ 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,6 +253,7 @@ 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(); }

View File

@ -0,0 +1,25 @@
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 ()

View File

@ -0,0 +1,309 @@
#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);
}
}

View File

@ -0,0 +1,53 @@
#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);
}

View File

@ -0,0 +1,272 @@
#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
}

View File

@ -0,0 +1,622 @@
/*
* 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);
}

View File

@ -0,0 +1,58 @@
/*
* 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);

View File

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.13)
project(fobossdr_source)
file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})
if (MSVC)
# Lib path
target_link_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/lib/")
target_include_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/include/")
target_link_libraries(fobossdr_source PRIVATE fobos)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBFOBOS REQUIRED libfobos)
target_include_directories(fobossdr_source PRIVATE ${LIBFOBOS_INCLUDE_DIRS})
target_link_directories(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARY_DIRS})
target_link_libraries(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARIES})
endif ()

View File

@ -0,0 +1,560 @@
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
#include <gui/smgui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <utils/optionlist.h>
#include <atomic>
#include <fobos.h>
SDRPP_MOD_INFO{
/* Name: */ "fobossdr_source",
/* Description: */ "FobosSDR Source Module",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
#define CONCAT(a, b) ((std::string(a) + b).c_str())
// Work around for the fobos API not including
#define FOBOS_LNA_GAIN_MIN 1
#define FOBOS_LNA_GAIN_MAX 3
#define FOBOS_VGA_GAIN_MIN 0
#define FOBOS_VGA_GAIN_MAX 31
class FobosSDRSourceModule : public ModuleManager::Instance {
public:
FobosSDRSourceModule(std::string name) {
this->name = name;
sampleRate = 50000000.0;
// Initialize the DDC
ddc.init(&ddcIn, 50e6, 50e6, 50e6, 0.0);
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &ddc.out;
// Refresh devices
refresh();
// Select device from config
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
select(devSerial);
sigpath::sourceManager.registerSource("FobosSDR", &handler);
}
~FobosSDRSourceModule() {
// Nothing to do
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
enum Port {
PORT_RF,
PORT_HF1,
PORT_HF2
};
private:
std::string getBandwdithScaled(double bw) {
char buf[1024];
if (bw >= 1000000.0) {
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
}
else if (bw >= 1000.0) {
sprintf(buf, "%.1lfKHz", bw / 1000.0);
}
else {
sprintf(buf, "%.1lfHz", bw);
}
return std::string(buf);
}
void refresh() {
devices.clear();
// Get device list
char serials[1024];
memset(serials, 0, sizeof(serials));
int devCount = fobos_rx_list_devices(serials);
if (devCount < 0) {
flog::error("Failed to get device list: {}", devCount);
return;
}
// If no device, give up
if (!devCount) { return; }
// Generate device entries
const char* _serials = serials;
int index = 0;
while (*_serials) {
// Read serial until space
std::string serial = "";
while (*_serials) {
// Get a character
char c = *(_serials++);
// If it's a space, we're done
if (c == ' ') { break; }
// Otherwise, add it to the string
serial += c;
}
// Create entry
devices.define(serial, serial, index++);
}
}
void select(const std::string& serial) {
// If there are no devices, give up
if (devices.empty()) {
selectedSerial.clear();
return;
}
// If the serial was not found, select the first available serial
if (!devices.keyExists(serial)) {
select(devices.key(0));
return;
}
// Get the ID in the list
int id = devices.keyId(serial);
selectedDevId = devices[id];
// Open the device
fobos_dev_t* dev;
int err = fobos_rx_open(&dev, selectedDevId);
if (err) {
flog::error("Failed to open device: {}", err);
return;
}
// Get a list of supported samplerates
double srList[128];
unsigned int srCount;
err = fobos_rx_get_samplerates(dev, srList, &srCount);
if (err) {
flog::error("Failed to get samplerate list: {}", err);
return;
}
// Generate samplerate list
samplerates.clear();
for (int i = 0; i < srCount; i++) {
std::string str = getBandwdithScaled(srList[i]);
samplerates.define(srList[i], str, srList[i]);
}
// Add some custom samplerates
samplerates.define(5e6, "5.0MHz", 5e6);
samplerates.define(2.5e6, "2.5MHz", 2.5e6);
samplerates.define(1.25e6, "1.25MHz", 1.25e6);
// Define the ports
ports.clear();
ports.define("rf", "RF", PORT_RF);
ports.define("hf1", "HF1", PORT_HF1);
ports.define("hf2", "HF2", PORT_HF2);
// Define clock sources
clockSources.clear();
clockSources.define("internal", "Internal", 0);
clockSources.define("external", "External", 1);
// Close the device
fobos_rx_close(dev);
// Save serial number
selectedSerial = serial;
devId = id;
// Load default options
sampleRate = 50e6;
srId = samplerates.valueId(sampleRate);
port = PORT_RF;
portId = ports.valueId(port);
clkSrcId = clockSources.nameId("Internal");
lnaGain = 0;
vgaGain = 0;
// Load config
config.acquire();
if (config.conf["devices"][selectedSerial].contains("samplerate")) {
int desiredSr = config.conf["devices"][selectedSerial]["samplerate"];
if (samplerates.keyExists(desiredSr)) {
srId = samplerates.keyId(desiredSr);
sampleRate = samplerates[srId];
}
}
if (config.conf["devices"][selectedSerial].contains("port")) {
std::string desiredPort = config.conf["devices"][selectedSerial]["port"];
if (ports.keyExists(desiredPort)) {
portId = ports.keyId(desiredPort);
port = ports[portId];
}
}
if (config.conf["devices"][selectedSerial].contains("clkSrc")) {
std::string desiredClkSrc = config.conf["devices"][selectedSerial]["clkSrc"];
if (clockSources.keyExists(desiredClkSrc)) {
clkSrcId = clockSources.keyId(desiredClkSrc);
}
}
if (config.conf["devices"][selectedSerial].contains("lnaGain")) {
lnaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["lnaGain"], FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX);
}
if (config.conf["devices"][selectedSerial].contains("vgaGain")) {
vgaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["vgaGain"], FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX);
}
config.release();
// Update the samplerate
core::setInputSampleRate(sampleRate);
}
static void menuSelected(void* ctx) {
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("FobosSDRSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
flog::info("FobosSDRSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
if (_this->running) { return; }
// Open the device
int err = fobos_rx_open(&_this->openDev, _this->selectedDevId);
if (err) {
flog::error("Failed to open device: {}", err);
return;
}
// Get the selected port
_this->port = _this->ports[_this->portId];
// Configure the device
double actualSr, actualFreq;
fobos_rx_set_samplerate(_this->openDev, (_this->sampleRate >= 50e6) ? _this->sampleRate : 50e6, &actualSr);
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
fobos_rx_set_direct_sampling(_this->openDev, _this->port != PORT_RF);
fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]);
fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain);
fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain);
// Configure the DDC
if (_this->port == PORT_RF && _this->sampleRate >= 50e6) {
// Set the frequency
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
}
else if (_this->port == PORT_RF) {
// Set the frequency
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
// Configure and start the DDC for decimation only
_this->ddc.setInSamplerate(actualSr);
_this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate);
_this->ddc.setOffset(0.0);
_this->ddc.start();
}
else {
// Configure and start the DDC
_this->ddc.setInSamplerate(actualSr);
_this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate);
_this->ddc.setOffset(_this->freq);
_this->ddc.start();
}
// Compute buffer size (Lower than usual, but it's a workaround for their API having broken streaming)
_this->bufferSize = _this->sampleRate / 400.0;
// Start streaming
err = fobos_rx_start_sync(_this->openDev, _this->bufferSize);
if (err) {
flog::error("Failed to start stream: {}", err);
return;
}
// Start worker
_this->run = true;
_this->workerThread = std::thread(&FobosSDRSourceModule::worker, _this);
_this->running = true;
flog::info("FobosSDRSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
// Stop worker
_this->run = false;
if (_this->port == PORT_RF && _this->sampleRate >= 50e6) {
_this->ddc.out.stopWriter();
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
_this->ddc.out.clearWriteStop();
}
else {
_this->ddcIn.stopWriter();
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
_this->ddcIn.clearWriteStop();
}
// Stop streaming
fobos_rx_stop_sync(_this->openDev);
// Stop the DDC
_this->ddc.stop();
// Close the device
fobos_rx_close(_this->openDev);
flog::info("FobosSDRSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
if (_this->running) {
if (_this->port == PORT_RF) {
double actual; // Dummy, don't care
fobos_rx_set_frequency(_this->openDev, freq, &actual);
}
else {
_this->ddc.setOffset(freq);
}
}
_this->freq = freq;
flog::info("FobosSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_fobossdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
_this->select(_this->devices.key(_this->devId));
core::setInputSampleRate(_this->sampleRate);
config.acquire();
config.conf["device"] = _this->selectedSerial;
config.release(true);
}
if (SmGui::Combo(CONCAT("##_fobossdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
_this->sampleRate = _this->samplerates.value(_this->srId);
core::setInputSampleRate(_this->sampleRate);
if (!_this->selectedSerial.empty()) {
config.acquire();
config.conf["devices"][_this->selectedSerial]["samplerate"] = _this->samplerates.key(_this->srId);
config.release(true);
}
}
SmGui::SameLine();
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_fobossdr_refr_", _this->name))) {
_this->refresh();
_this->select(_this->selectedSerial);
core::setInputSampleRate(_this->sampleRate);
}
SmGui::LeftLabel("Antenna Port");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_fobossdr_port_", _this->name), &_this->portId, _this->ports.txt)) {
if (!_this->selectedSerial.empty()) {
config.acquire();
config.conf["devices"][_this->selectedSerial]["port"] = _this->ports.key(_this->portId);
config.release(true);
}
}
if (_this->running) { SmGui::EndDisabled(); }
SmGui::LeftLabel("Clock Source");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_fobossdr_clk_", _this->name), &_this->clkSrcId, _this->clockSources.txt)) {
if (_this->running) {
fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]);
}
if (!_this->selectedSerial.empty()) {
config.acquire();
config.conf["devices"][_this->selectedSerial]["clkSrc"] = _this->clockSources.key(_this->clkSrcId);
config.release(true);
}
}
if (_this->port == PORT_RF) {
SmGui::LeftLabel("LNA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_fobossdr_lna_gain_", _this->name), &_this->lnaGain, FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX)) {
if (_this->running) {
fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (!_this->selectedSerial.empty()) {
config.acquire();
config.conf["devices"][_this->selectedSerial]["lnaGain"] = _this->lnaGain;
config.release(true);
}
}
SmGui::LeftLabel("VGA Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_fobossdr_vga_gain_", _this->name), &_this->vgaGain, FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX)) {
if (_this->running) {
fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (!_this->selectedSerial.empty()) {
config.acquire();
config.conf["devices"][_this->selectedSerial]["vgaGain"] = _this->vgaGain;
config.release(true);
}
}
}
}
void worker() {
// Select different processing depending on the mode
if (port == PORT_RF && sampleRate >= 50e6) {
while (run) {
// Read samples
unsigned int sampCount = 0;
int err = fobos_rx_read_sync(openDev, (float*)ddc.out.writeBuf, &sampCount);
if (err) { break; }
// Send out samples to the core
if (!ddc.out.swap(sampCount)) { break; }
}
}
else if (port == PORT_RF) {
while (run) {
// Read samples
unsigned int sampCount = 0;
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
if (err) { break; }
// Send samples to the DDC
if (!ddcIn.swap(sampCount)) { break; }
}
}
else if (port == PORT_HF1) {
while (run) {
// Read samples
unsigned int sampCount = 0;
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
if (err) { break; }
// Null out the HF2 samples
for (int i = 0; i < sampCount; i++) {
ddcIn.writeBuf[i].im = 0.0f;
}
// Send samples to the DDC
if (!ddcIn.swap(sampCount)) { break; }
}
}
else if (port == PORT_HF2) {
while (run) {
// Read samples
unsigned int sampCount = 0;
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
if (err) { break; }
// Null out the HF2 samples
for (int i = 0; i < sampCount; i++) {
ddcIn.writeBuf[i].re = 0.0f;
}
// Send samples to the DDC
if (!ddcIn.swap(sampCount)) { break; }
}
}
}
std::string name;
bool enabled = true;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
OptionList<std::string, int> devices;
OptionList<int, double> samplerates;
OptionList<std::string, Port> ports;
OptionList<std::string, int> clockSources;
int devId = 0;
int srId = 0;
int portId = 0;
int clkSrcId = 0;
Port port;
int lnaGain = 0;
int vgaGain = 0;
std::string selectedSerial;
int selectedDevId;
fobos_dev_t* openDev;
int bufferSize;
std::thread workerThread;
std::atomic<bool> run = false;
dsp::stream<dsp::complex_t> ddcIn;
dsp::channel::RxVFO ddc;
};
MOD_EXPORT void _INIT_() {
json def = json({});
def["devices"] = json({});
def["device"] = "";
config.setPath(core::args["root"].s() + "/fobossdr_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new FobosSDRSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (FobosSDRSourceModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -304,6 +304,7 @@ 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);

View File

@ -0,0 +1,17 @@
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 ()

View File

@ -0,0 +1,510 @@
#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
}

View File

@ -118,7 +118,6 @@ private:
// Update host samplerate
sampleRate = samplerates.key(srId);
core::setInputSampleRate(sampleRate);
}
static void menuSelected(void* ctx) {
@ -199,6 +198,7 @@ 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,6 +225,7 @@ private:
std::string mac = config.conf["device"];
config.release();
_this->selectMac(mac);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->running) { SmGui::EndDisabled(); }

View File

@ -210,7 +210,6 @@ public:
// Update samplerate
sampleRate = srList[srId];
core::setInputSampleRate(sampleRate);
// Close device
perseus_close(dev);
@ -329,6 +328,7 @@ private:
if (SmGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devList.txt)) {
std::string serial = _this->devList.key(_this->devId);
_this->select(serial);
core::setInputSampleRate(_this->sampleRate);
config.acquire();
config.conf["device"] = serial;
config.release(true);

View File

@ -244,9 +244,6 @@ private:
bwId = 0;
bandwidth = bandwidths.value(bwId);
}
// Update core samplerate
core::setInputSampleRate(samplerate);
}
static void menuSelected(void* ctx) {
@ -351,6 +348,7 @@ private:
SmGui::ForceSync();
if (SmGui::Combo("##plutosdr_dev_sel", &_this->devId, _this->devices.txt)) {
_this->select(_this->devices.key(_this->devId));
core::setInputSampleRate(_this->samplerate);
config.acquire();
config.conf["device"] = _this->devices.key(_this->devId);
config.release(true);
@ -373,7 +371,7 @@ private:
if (SmGui::Button(CONCAT("Refresh##_pluto_refr_", _this->name))) {
_this->refresh();
_this->select(_this->devDesc);
core::setInputSampleRate(_this->samplerate);
}
if (_this->running) { SmGui::EndDisabled(); }

View File

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.13)
project(rfnm_source)
file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})
if (MSVC)
# Lib path
target_link_directories(rfnm_source PRIVATE "C:/Program Files/RFNM/lib/")
target_include_directories(rfnm_source PUBLIC "C:/Program Files/RFNM/include/")
target_link_libraries(rfnm_source PRIVATE rfnm)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBRFNM REQUIRED librfnm)
target_include_directories(rfnm_source PRIVATE ${LIBRFNM_INCLUDE_DIRS})
target_link_directories(rfnm_source PRIVATE ${LIBRFNM_LIBRARY_DIRS})
target_link_libraries(rfnm_source PRIVATE ${LIBRFNM_LIBRARIES})
endif ()

View File

@ -0,0 +1,512 @@
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
#include <gui/smgui.h>
#include <signal_path/signal_path.h>
#include <librfnm/librfnm.h>
#include <core.h>
#include <utils/optionlist.h>
#include <atomic>
SDRPP_MOD_INFO{
/* Name: */ "rfnm_source",
/* Description: */ "RFNM Source Module",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
#define CONCAT(a, b) ((std::string(a) + b).c_str())
class RFNMSourceModule : public ModuleManager::Instance {
public:
RFNMSourceModule(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("RFNM", &handler);
}
~RFNMSourceModule() {
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
void refresh() {
devices.clear();
auto list = librfnm::find(librfnm_transport::LIBRFNM_TRANSPORT_USB);
for (const auto& info : list) {
// Format device name
std::string devName = "RFNM ";
devName += info.motherboard.user_readable_name;
devName += " [";
devName += (char*)info.motherboard.serial_number;
devName += ']';
// Save device
devices.define((char*)info.motherboard.serial_number, devName, (char*)info.motherboard.serial_number);
}
}
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;
}
// Open the device
librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, serial);
// Define samplerates
samplerates.clear();
samplerates.define(61440000, "61.44 MHz", 2);
samplerates.define(122880000, "122.88 MHz", 1);
// Define daughterboards
daughterboards.clear();
for (int i = 0; i < 2; i++) {
// If not present, skip
if (!dev->s->hwinfo.daughterboard[i].board_id) { continue; }
// Format the daughterboard name
std::string name = (i ? "[SEC] " : "[PRI] ") + std::string(dev->s->hwinfo.daughterboard[i].user_readable_name);
// Add the daughterboard to the list
daughterboards.define(name, name, i);
}
// Load options (TODO)
srId = samplerates.keyId(61440000);
dgbId = 0;
// Select the daughterboard
selectDaughterboard(dev, 0);
// Update samplerate
sampleRate = samplerates.key(srId);
// Close device
delete dev;
// Save serial number
selectedSerial = serial;
}
struct PathConfig {
rfnm_rf_path path;
int chId;
uint16_t appliesCh;
bool operator==(const PathConfig& b) const {
return b.path == path;
}
};
void selectDaughterboard(librfnm* dev, int id) {
// If no daugherboard is populated, give up
if (!dev->s->hwinfo.daughterboard[0].board_id && !dev->s->hwinfo.daughterboard[1].board_id) {
flog::error("The selected device has no daughterboards");
return;
}
// If the ID is not populated, select the other one
if (id >= 2 || !dev->s->hwinfo.daughterboard[id].board_id) {
selectDaughterboard(dev, 1 - id);
}
// Compute the channel offset
int offset = 0;
for (int i = 0; i < id; i++) {
offset += dev->s->hwinfo.daughterboard[i].rx_ch_cnt;
}
// Define antenna paths by going through all channels
paths.clear();
int count = dev->s->hwinfo.daughterboard[id].rx_ch_cnt;
for (int i = 0; i < count; i++) {
// Go through each possible path
for (int j = 0; j < 10; j++) {
// If it's the null path, stop searching
rfnm_rf_path path = dev->s->rx.ch[offset + i].path_possible[j];
if (path == RFNM_PATH_NULL) { continue; }
// Get the path
PathConfig pc = { path, offset + i, (uint16_t)(1 << (offset + i + 8))};
// If it's not in the list, add it
if (!paths.valueExists(pc)) {
std::string name = librfnm::rf_path_to_string(pc.path);
std::string capName = name;
if (std::islower(capName[0])) { capName[0] = std::toupper(capName[0]); }
paths.define(name, capName, pc);
}
}
// Get the preferred path
PathConfig preferred_pc = { dev->s->rx.ch[offset + i].path_preferred, 0, 0 };
// Make sure the path is accessible or give up
if (!paths.valueExists(preferred_pc)) { continue; }
// Set this channel as the channel of its prefered path (cursed af but lazy)
const PathConfig& pc = paths.value(paths.valueId(preferred_pc));
((PathConfig*)&pc)->chId = offset + i;
((PathConfig*)&pc)->appliesCh = (uint16_t)(1 << (offset + i + 8));
}
// Dump antenna paths
for (int i = 0; i < paths.size(); i++) {
flog::debug("PATH[{}]: Name={}, Ch={}, Path={}", i, paths.name(i), paths.value(i).chId, (int)paths.value(i).path);
}
// Load configuration (TODO)
selectedPath = paths.key(0);
// Select antenna path
selectPath(dev, id, selectedPath);
// Save selected daughterboard
dgbId = id;
}
void selectPath(librfnm* dev, int dgbId, const std::string& path) {
// If the path doesn't exist, select the first path
if (!paths.keyExists(path)) {
selectPath(dev, dgbId, paths.key(0));
}
// Save selected path
selectedPath = path;
pathId = paths.keyId(path);
currentPath = paths.value(pathId);
// Define bandwidths
bandwidths.clear();
bandwidths.define(-1, "Auto", -1);
for (int i = 1; i <= 100; i++) {
char buf[128];
sprintf(buf, "%d MHz", i);
bandwidths.define(i, buf, i);
}
// Get gain range
gainMin = dev->s->rx.ch[currentPath.chId].gain_range.min;
gainMax = dev->s->rx.ch[currentPath.chId].gain_range.max;
}
static void menuSelected(void* ctx) {
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("RFNMSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
flog::info("RFNMSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
if (_this->running) { return; }
// Open the device
_this->openDev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial);
// Configure the stream
_this->bufferSize = -1;
_this->openDev->rx_stream(librfnm_stream_format::LIBRFNM_STREAM_FORMAT_CS16, &_this->bufferSize);
if (_this->bufferSize <= 0) {
flog::error("Failed to configure stream");
}
// Allocate and queue buffers
flog::debug("BUFFER SIZE: {}", _this->bufferSize);
for (int i = 0; i < LIBRFNM_MIN_RX_BUFCNT; i++) {
_this->rxBuf[i].buf = dsp::buffer::alloc<uint8_t>(_this->bufferSize);
_this->openDev->rx_qbuf(&_this->rxBuf[i]);
}
// Flush buffers
_this->openDev->rx_flush();
// Configure the device
_this->openDev->s->rx.ch[_this->currentPath.chId].enable = RFNM_CH_ON;
_this->openDev->s->rx.ch[_this->currentPath.chId].samp_freq_div_n = _this->samplerates[_this->srId];
_this->openDev->s->rx.ch[_this->currentPath.chId].freq = _this->freq;
_this->openDev->s->rx.ch[_this->currentPath.chId].gain = _this->gain;
_this->openDev->s->rx.ch[_this->currentPath.chId].rfic_lpf_bw = 100;
_this->openDev->s->rx.ch[_this->currentPath.chId].fm_notch = _this->fmNotch ? rfnm_fm_notch::RFNM_FM_NOTCH_ON : rfnm_fm_notch::RFNM_FM_NOTCH_OFF;
_this->openDev->s->rx.ch[_this->currentPath.chId].path = _this->currentPath.path;
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
if (fail != rfnm_api_failcode::RFNM_API_OK) {
flog::error("Failed to configure device: {}", (int)fail);
}
// Start worker
_this->run = true;
_this->workerThread = std::thread(&RFNMSourceModule::worker, _this);
_this->running = true;
flog::info("RFNMSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
RFNMSourceModule* _this = (RFNMSourceModule*)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 RX streaming
_this->openDev->rx_stream_stop();
// Disable channel
_this->openDev->s->rx.ch[_this->currentPath.chId].enable = RFNM_CH_OFF;
_this->openDev->set(_this->currentPath.appliesCh);
// Flush buffers
_this->openDev->rx_flush();
// Close device
delete _this->openDev;
// Free buffers
for (int i = 0; i < LIBRFNM_MIN_RX_BUFCNT; i++) {
dsp::buffer::free(_this->rxBuf[i].buf);
}
flog::info("RFNMSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
if (_this->running) {
_this->openDev->s->rx.ch[_this->currentPath.chId].freq = freq;
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
if (fail != rfnm_api_failcode::RFNM_API_OK) {
flog::error("Failed to tune: {}", (int)fail);
}
}
_this->freq = freq;
flog::info("RFNMSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_rfnm_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("##_rfnm_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
_this->sampleRate = _this->samplerates.key(_this->srId);
core::setInputSampleRate(_this->sampleRate);
// TODO: Save
}
SmGui::SameLine();
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_rfnm_refr_", _this->name))) {
_this->refresh();
_this->select(_this->selectedSerial);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->daughterboards.size() > 1) {
SmGui::LeftLabel("Daughterboard");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_rfnm_dgb_sel_", _this->name), &_this->dgbId, _this->daughterboards.txt)) {
// Open the device
librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial);
// Select the daughterboard
_this->selectDaughterboard(dev, _this->dgbId);
// Close device
delete dev;
// TODO: Save
}
}
if (_this->paths.size() > 1) {
SmGui::LeftLabel("Antenna Path");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_rfnm_path_sel_", _this->name), &_this->pathId, _this->paths.txt)) {
// Open the device
librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial);
// Select the atennna path
_this->selectPath(dev, _this->dgbId, _this->paths.key(_this->pathId));
// Close device
delete dev;
// TODO: Save
}
}
if (_this->running) { SmGui::EndDisabled(); }
SmGui::LeftLabel("Bandwidth");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_rfnm_bw_sel_", _this->name), &_this->bwId, _this->bandwidths.txt)) {
if (_this->running) {
// TODO: Set
}
// TODO: Save
}
SmGui::LeftLabel("Gain");
SmGui::FillWidth();
if (SmGui::SliderInt(CONCAT("##_rfnm_gain_", _this->name), &_this->gain, _this->gainMin, _this->gainMax)) {
if (_this->running) {
_this->openDev->s->rx.ch[_this->currentPath.chId].gain = _this->gain;
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
}
// TODO: Save
}
if (SmGui::Checkbox(CONCAT("FM Notch##_rfnm_", _this->name), &_this->fmNotch)) {
if (_this->running) {
_this->openDev->s->rx.ch[_this->currentPath.chId].fm_notch = _this->fmNotch ? rfnm_fm_notch::RFNM_FM_NOTCH_ON : rfnm_fm_notch::RFNM_FM_NOTCH_OFF;
rfnm_api_failcode fail = _this->openDev->set(_this->currentPath.appliesCh);
}
// TODO: Save
}
}
void worker() {
librfnm_rx_buf* lrxbuf;
int sampCount = bufferSize/4;
uint8_t ch = (1 << currentPath.chId);
// Define number of buffers per swap to maintain 200 fps
int maxBufCount = STREAM_BUFFER_SIZE / sampCount;
int bufCount = (sampleRate / sampCount) / 200;
if (bufCount <= 0) { bufCount = 1; }
if (bufCount > maxBufCount) { bufCount = maxBufCount; }
int count = 0;
while (run) {
// Receive a buffer
auto fail = openDev->rx_dqbuf(&lrxbuf, ch, 1000);
if (fail == rfnm_api_failcode::RFNM_API_DQBUF_NO_DATA) {
flog::error("Dequeue buffer didn't have any data");
continue;
}
else if (fail) { break; }
// Convert buffer to CF32
volk_16i_s32f_convert_32f((float*)&stream.writeBuf[(count++)*sampCount], (int16_t*)lrxbuf->buf, 32768.0f, sampCount * 2);
// Reque buffer
openDev->rx_qbuf(lrxbuf);
// Swap data
if (count >= bufCount) {
if (!stream.swap(count*sampCount)) { break; }
count = 0;
}
}
flog::debug("Worker exiting");
}
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<std::string, int> daughterboards;
OptionList<std::string, PathConfig> paths;
OptionList<int, int> bandwidths;
OptionList<int, int> samplerates;
int gainMin = 0;
int gainMax = 0;
int devId = 0;
int dgbId = 0;
int pathId = 0;
int srId = 0;
int bwId = 0;
int gain = 0;
bool fmNotch = false;
std::string selectedSerial;
librfnm* openDev;
int bufferSize = -1;
std::string selectedPath;
PathConfig currentPath;
librfnm_rx_buf rxBuf[LIBRFNM_MIN_RX_BUFCNT];
std::atomic<bool> run = false;
std::thread workerThread;
};
MOD_EXPORT void _INIT_() {
// Nothing here
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new RFNMSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (RFNMSourceModule*)instance;
}
MOD_EXPORT void _END_() {
// Nothing here
}

View File

@ -411,8 +411,6 @@ public:
agcSetPoint = config.conf["devices"][selectedName]["agcSetPoint"];
}
core::setInputSampleRate(sampleRate);
// Per device options
if (openDev.hwVer == SDRPLAY_RSP1_ID) {
// No config to load
@ -688,6 +686,7 @@ private:
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##sdrplay_dev", _this->name), &_this->devId, _this->devListTxt.c_str())) {
_this->selectById(_this->devId);
core::setInputSampleRate(_this->sampleRate);
config.acquire();
config.conf["device"] = _this->devNameList[_this->devId];
config.release(true);
@ -711,6 +710,7 @@ private:
if (SmGui::Button(CONCAT("Refresh##sdrplay_refresh", _this->name))) {
_this->refresh();
_this->selectByName(_this->selectedName);
core::setInputSampleRate(_this->sampleRate);
}
SmGui::LeftLabel("Bandwidth");

View File

@ -9,8 +9,13 @@
#include <gui/smgui.h>
#include <utils/optionlist.h>
#include <codecvt>
#include <locale>
#include <aaroniartsaapi.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
@ -28,10 +33,13 @@ public:
SpectranSourceModule(std::string name) {
this->name = name;
if (AARTSAAPI_Init(AARTSAAPI_MEMORY_MEDIUM) != AARTSAAPI_OK) {
AARTSAAPI_Result res;
if ((res = AARTSAAPI_Init(AARTSAAPI_MEMORY_MEDIUM)) != AARTSAAPI_OK) {
flog::error("Failed to initialize the RTSAAPI: {}", (uint32_t)res);
return;
}
if (AARTSAAPI_Open(&api) != AARTSAAPI_OK) {
if ((res = AARTSAAPI_Open(&api)) != AARTSAAPI_OK) {
flog::error("Failed to open the RTSAAPI: {}", (uint32_t)res);
return;
}
@ -189,7 +197,6 @@ public:
// Set samplerate
samplerate = sampleRateList.value(srId);
core::setInputSampleRate(samplerate.effective);
// Close device
AARTSAAPI_CloseDevice(&api, &dev);
@ -329,9 +336,10 @@ private:
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_spectran_dev_", _this->name), &_this->devId, _this->devList.txt)) {
_this->selectSerial(_this->devList.key(_this->devId));
core::setInputSampleRate(_this->samplerate.effective);
}
// TODO: SR sel
if (SmGui::Combo(CONCAT("##_spectran_sr_", _this->name), &_this->srId, _this->sampleRateList.txt)) {
_this->samplerate = _this->sampleRateList.value(_this->srId);
core::setInputSampleRate(_this->samplerate.effective);
@ -447,13 +455,16 @@ private:
void updateRef() {
// Get and update bounds
AARTSAAPI_Config config;
AARTSAAPI_ConfigInfo refInfo;
AARTSAAPI_ConfigFind(&dev, &croot, &config, L"main/reflevel");
AARTSAAPI_ConfigGetInfo(&dev, &config, &refInfo);
AARTSAAPI_Config config = {};
AARTSAAPI_ConfigInfo refInfo = {};
auto res = AARTSAAPI_ConfigFind(&dev, &croot, &config, L"main/reflevel");
flog::debug("Res A: {}", res);
res = AARTSAAPI_ConfigGetInfo(&dev, &config, &refInfo);
flog::debug("Res B: {}", res);
minRef = refInfo.minValue;
maxRef = refInfo.maxValue;
refStep = refInfo.stepValue;
flog::debug("Gain: {} -> {}", refInfo.minValue, refInfo.maxValue);
refLevel = std::clamp<float>(refLevel, minRef, maxRef);
// Apply new ref level
@ -547,4 +558,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}
}

View File

@ -211,7 +211,6 @@ public:
// Apply samplerate
sampleRate = samplerates.key(srId);
core::setInputSampleRate(sampleRate);
}
void setBandwidth(double bw) {
@ -335,6 +334,7 @@ private:
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_usrp_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
_this->select(_this->devices.key(_this->devId));
core::setInputSampleRate(_this->sampleRate);
if (!_this->selectedSer.empty()) {
config.acquire();
config.conf["device"] = _this->devices.key(_this->devId);
@ -357,10 +357,8 @@ private:
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_usrp_refr_", _this->name))) {
_this->refresh();
config.acquire();
std::string ser = config.conf["device"];
config.release();
_this->select(ser);
_this->select(_this->selectedSer);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->channels.size() > 1) {

View File

@ -1,5 +1,4 @@
#include <core.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
return sdrpp_main(argc, argv);