mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2026-04-20 07:12:43 +00:00
Compare commits
148 Commits
debug
...
65a0e11d3d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65a0e11d3d | ||
|
|
4a48a0c09f | ||
|
|
9c32a68892 | ||
|
|
4658a1ade6 | ||
|
|
b1d94775fe | ||
|
|
2a3fb12f61 | ||
|
|
e82202ea74 | ||
|
|
981f53aa4e | ||
|
|
c216b88366 | ||
|
|
f65e4afb4a | ||
|
|
51ae5628b3 | ||
|
|
9feaf02673 | ||
|
|
5d9f6dc341 | ||
|
|
cf7dd85f92 | ||
|
|
8b8eda301b | ||
|
|
4558e73be5 | ||
|
|
40808c60e4 | ||
|
|
b5d227fecf | ||
|
|
6c00834ddd | ||
|
|
f67fa0c66c | ||
|
|
a94e2d6712 | ||
|
|
981bd1695a | ||
|
|
dd9b8db6c9 | ||
|
|
543c60ccbc | ||
|
|
2dd8c6cea4 | ||
|
|
9457ad9369 | ||
|
|
fccd72b5f8 | ||
|
|
e75cc7be6f | ||
|
|
aa2b4b1c58 | ||
|
|
64315ebc61 | ||
|
|
553204b801 | ||
|
|
2a84ed202c | ||
|
|
f90e2d53a7 | ||
|
|
993bf9128c | ||
|
|
d9ff7eaa12 | ||
|
|
32b289953d | ||
|
|
5178de0849 | ||
|
|
5c355e21f5 | ||
|
|
d020640b7c | ||
|
|
e0f9053229 | ||
|
|
395186ffb0 | ||
|
|
0e77a9f4ab | ||
|
|
4799d0e3a8 | ||
|
|
6c6f4264b2 | ||
|
|
ea3675da47 | ||
|
|
afef9f57ab | ||
|
|
e1de2daca8 | ||
|
|
69bd6b0f3a | ||
|
|
fea3fc2563 | ||
|
|
7bccc67311 | ||
|
|
4310bbb1ea | ||
|
|
d811a839ff | ||
|
|
46bcba7594 | ||
|
|
45e4286f38 | ||
|
|
c266a37a6b | ||
|
|
895199ae94 | ||
|
|
d62426364a | ||
|
|
5ded73ce71 | ||
|
|
cf3e15d285 | ||
|
|
59ca2cf1c0 | ||
|
|
9bb4aeda14 | ||
|
|
304d5c42cc | ||
|
|
b914587228 | ||
|
|
3c1d0c7422 | ||
|
|
11f87e0fe2 | ||
|
|
e192cb963b | ||
|
|
fe407a2f27 | ||
|
|
6891d0bb0f | ||
|
|
b835d07573 | ||
|
|
f205d97b52 | ||
|
|
628dcfcce0 | ||
|
|
d1e7cc56b4 | ||
|
|
334860c963 | ||
|
|
69161253e8 | ||
|
|
5ab3428b90 | ||
|
|
7f002f6276 | ||
|
|
a728403a3f | ||
|
|
0f1d2da3b7 | ||
|
|
6d0b65c27f | ||
|
|
f640cdcb6a | ||
|
|
80a90e13d9 | ||
|
|
3982db73d3 | ||
|
|
bd64f07a20 | ||
|
|
c9950d9331 | ||
|
|
9bc609f4e4 | ||
|
|
bcc8e20e66 | ||
|
|
b07e828fed | ||
|
|
bd24a4a5eb | ||
|
|
fe4a7b32a7 | ||
|
|
0e1ab29b5d | ||
|
|
fbbafddd3d | ||
|
|
1cbc8ec6f5 | ||
|
|
9f65e3ec71 | ||
|
|
08f3a7d201 | ||
|
|
9ce62f8885 | ||
|
|
caeaa2d46c | ||
|
|
7ae030a3a6 | ||
|
|
1b27379a3d | ||
|
|
e52123038e | ||
|
|
ec8c60111d | ||
|
|
f61799cf5f | ||
|
|
17eccf5156 | ||
|
|
e835c8dd9a | ||
|
|
acb1be121c | ||
|
|
0fa89614bb | ||
|
|
256affd918 | ||
|
|
e80cdbf248 | ||
|
|
75e66226c3 | ||
|
|
c2f0e756a5 | ||
|
|
79dd5bdcbb | ||
|
|
6dce28345c | ||
|
|
fe9ac6c9a1 | ||
|
|
bfdfa2b30b | ||
|
|
46e98b9b03 | ||
|
|
e674a73771 | ||
|
|
118e1fbff0 | ||
|
|
bcadb36232 | ||
|
|
554ba2f596 | ||
|
|
949fde022d | ||
|
|
123e34d250 | ||
|
|
f1c7010437 | ||
|
|
33a7795de1 | ||
|
|
fe7299c18a | ||
|
|
8a9e0abcc2 | ||
|
|
13abe4860b | ||
|
|
981592fa19 | ||
|
|
582750f79b | ||
|
|
36f2a083ce | ||
|
|
d753135a61 | ||
|
|
07744e5bae | ||
|
|
9ec78da7ac | ||
|
|
0066994899 | ||
|
|
93ab51bf2f | ||
|
|
f9d7d20073 | ||
|
|
e81db5d85c | ||
|
|
5c3a66642b | ||
|
|
36492e799a | ||
|
|
0110dfbef6 | ||
|
|
0b5a2ff786 | ||
|
|
ce0f1f05ae | ||
|
|
46a5ff8ac5 | ||
|
|
03559b928b | ||
|
|
206ce6e8c3 | ||
|
|
d7a1f46af0 | ||
|
|
89e6e4f7ad | ||
|
|
6ced9b15c3 | ||
|
|
0de189a7b7 | ||
|
|
309717b5f8 |
297
.github/workflows/build_all.yml
vendored
297
.github/workflows/build_all.yml
vendored
@@ -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
|
||||
|
||||
@@ -43,7 +50,7 @@ jobs:
|
||||
run: 7z x ${{runner.workspace}}/SDRplay.zip -o"C:/Program Files/"
|
||||
|
||||
- name: Download codec2
|
||||
run: git clone https://github.com/AlexandreRouma/codec2
|
||||
run: git clone https://github.com/drowe67/codec2
|
||||
|
||||
- name: Prepare MinGW
|
||||
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
|
||||
@@ -58,17 +65,26 @@ 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: Install libhydrasdr
|
||||
run: git clone https://github.com/hydrasdr/rfone_host; cd rfone_host/ ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:\Program Files\PothosSDR\lib\libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIR=C:\Program Files\PothosSDR\include\libusb-1.0" "-DTHREADS_PTHREADS_WIN32_LIBRARY=C:\Program Files\PothosSDR\lib\pthreadVC2.lib" "-DTHREADS_PTHREADS_INCLUDE_DIR=C:\Program Files\PothosSDR\include" ; 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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@@ -85,7 +101,7 @@ jobs:
|
||||
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
||||
|
||||
build_macos_intel:
|
||||
runs-on: macos-12
|
||||
runs-on: macos-15-intel
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -94,7 +110,7 @@ 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 --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 ../../
|
||||
@@ -106,20 +122,29 @@ jobs:
|
||||
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 ../../
|
||||
|
||||
- name: Install libad9361
|
||||
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install LimeSuite
|
||||
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: Install libhydrasdr
|
||||
run: git clone https://github.com/hydrasdr/rfone_host && cd rfone_host && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && 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_POLICY_VERSION_MINIMUM=3.5 -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 -DOPT_BUILD_HYDRASDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@@ -136,7 +161,7 @@ jobs:
|
||||
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
|
||||
|
||||
build_macos_arm:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -145,7 +170,7 @@ 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 ../../
|
||||
@@ -157,7 +182,7 @@ jobs:
|
||||
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 ../../
|
||||
|
||||
- name: Install libad9361
|
||||
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install LimeSuite
|
||||
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 ../../
|
||||
@@ -165,12 +190,21 @@ 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: Install libhydrasdr
|
||||
run: git clone https://github.com/hydrasdr/rfone_host && cd rfone_host && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && 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_POLICY_VERSION_MINIMUM=3.5 -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 -DOPT_BUILD_HYDRASDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@@ -186,29 +220,7 @@ jobs:
|
||||
name: sdrpp_macos_arm
|
||||
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
|
||||
|
||||
build_debian_buster:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && 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_debian_buster_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_bullseye:
|
||||
build_debian_bullseye_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -230,7 +242,29 @@ jobs:
|
||||
name: sdrpp_debian_bullseye_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_bookworm:
|
||||
build_debian_bullseye_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && 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_debian_bullseye_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_bookworm_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -252,7 +286,73 @@ jobs:
|
||||
name: sdrpp_debian_bookworm_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_sid:
|
||||
build_debian_bookworm_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_bookworm_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_trixie_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_trixie && 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_debian_trixie_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_trixie_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_trixie && 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_debian_trixie_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_sid_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -274,7 +374,29 @@ jobs:
|
||||
name: sdrpp_debian_sid_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_focal:
|
||||
build_debian_sid_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && 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_debian_sid_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_focal_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -296,7 +418,29 @@ jobs:
|
||||
name: sdrpp_ubuntu_focal_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_jammy:
|
||||
build_ubuntu_focal_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && 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_focal_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_jammy_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -317,15 +461,15 @@ jobs:
|
||||
with:
|
||||
name: sdrpp_ubuntu_jammy_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_mantic:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
build_ubuntu_jammy_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_mantic && docker build . --tag sdrpp_build
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && 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
|
||||
@@ -337,10 +481,10 @@ jobs:
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_mantic_amd64
|
||||
name: sdrpp_ubuntu_jammy_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_noble:
|
||||
build_ubuntu_noble_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -362,32 +506,27 @@ jobs:
|
||||
name: sdrpp_ubuntu_noble_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_raspios_bullseye_armhf:
|
||||
runs-on: ARM
|
||||
build_ubuntu_noble_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: make VERBOSE=1 -j3
|
||||
- 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: Create Dev Archive
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb
|
||||
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_raspios_bullseye_armhf
|
||||
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
|
||||
name: sdrpp_ubuntu_noble_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_android:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -417,7 +556,26 @@ jobs:
|
||||
path: ${{runner.workspace}}/sdrpp.apk
|
||||
|
||||
create_full_archive:
|
||||
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android']
|
||||
needs: [
|
||||
'build_windows',
|
||||
'build_macos_intel',
|
||||
'build_macos_arm',
|
||||
'build_debian_bullseye_amd64',
|
||||
'build_debian_bullseye_aarch64',
|
||||
'build_debian_bookworm_amd64',
|
||||
'build_debian_bookworm_aarch64',
|
||||
'build_debian_trixie_amd64',
|
||||
'build_debian_trixie_aarch64',
|
||||
'build_debian_sid_amd64',
|
||||
'build_debian_sid_aarch64',
|
||||
'build_ubuntu_focal_amd64',
|
||||
'build_ubuntu_focal_aarch64',
|
||||
'build_ubuntu_jammy_amd64',
|
||||
'build_ubuntu_jammy_aarch64',
|
||||
'build_ubuntu_noble_amd64',
|
||||
'build_ubuntu_noble_aarch64',
|
||||
'build_android'
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -430,15 +588,20 @@ jobs:
|
||||
mv sdrpp_windows_x64/sdrpp_windows_x64.zip sdrpp_all/ &&
|
||||
mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
|
||||
mv sdrpp_macos_arm/sdrpp_macos_arm.zip sdrpp_all/ &&
|
||||
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
|
||||
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
|
||||
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
|
||||
mv sdrpp_debian_bullseye_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_aarch64.deb &&
|
||||
mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
|
||||
mv sdrpp_debian_bookworm_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_aarch64.deb &&
|
||||
mv sdrpp_debian_trixie_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_trixie_amd64.deb &&
|
||||
mv sdrpp_debian_trixie_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_trixie_aarch64.deb &&
|
||||
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
|
||||
mv sdrpp_debian_sid_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_aarch64.deb &&
|
||||
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
||||
mv sdrpp_ubuntu_focal_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_aarch64.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_jammy_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_aarch64.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_ubuntu_noble_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_aarch64.deb &&
|
||||
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,4 +16,8 @@ poggers_decoder
|
||||
m17_decoder/libcorrect
|
||||
SDR++.app
|
||||
android/deps
|
||||
android/app/assets
|
||||
android/app/assets
|
||||
source_modules/dragonlabs_source
|
||||
source_modules/badgesdr_source
|
||||
decoder_modules/mystery_decoder
|
||||
decoder_modules/tetra_decoder
|
||||
@@ -15,8 +15,12 @@ option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)
|
||||
option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF)
|
||||
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
|
||||
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
||||
option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF)
|
||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
|
||||
option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF)
|
||||
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_HYDRASDR_SOURCE "Build HydraSDR Source Module (Dependencies: libhydrasdr)" OFF)
|
||||
option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF)
|
||||
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
|
||||
option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF)
|
||||
@@ -41,13 +45,16 @@ option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Depend
|
||||
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
|
||||
|
||||
# Decoders
|
||||
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" ON)
|
||||
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_VOR_RECEIVER "VOR beacon receiver" OFF)
|
||||
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||
|
||||
# Misc
|
||||
@@ -63,6 +70,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")
|
||||
@@ -139,14 +147,30 @@ if (OPT_BUILD_FILE_SOURCE)
|
||||
add_subdirectory("source_modules/file_source")
|
||||
endif (OPT_BUILD_FILE_SOURCE)
|
||||
|
||||
if (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
add_subdirectory("source_modules/fobossdr_source")
|
||||
endif (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HACKRF_SOURCE)
|
||||
add_subdirectory("source_modules/hackrf_source")
|
||||
endif (OPT_BUILD_HACKRF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HAROGIC_SOURCE)
|
||||
add_subdirectory("source_modules/harogic_source")
|
||||
endif (OPT_BUILD_HAROGIC_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HERMES_SOURCE)
|
||||
add_subdirectory("source_modules/hermes_source")
|
||||
endif (OPT_BUILD_HERMES_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HYDRASDR_SOURCE)
|
||||
add_subdirectory("source_modules/hydrasdr_source")
|
||||
endif (OPT_BUILD_HYDRASDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_KCSDR_SOURCE)
|
||||
add_subdirectory("source_modules/kcsdr_source")
|
||||
endif (OPT_BUILD_KCSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_LIMESDR_SOURCE)
|
||||
add_subdirectory("source_modules/limesdr_source")
|
||||
endif (OPT_BUILD_LIMESDR_SOURCE)
|
||||
@@ -179,6 +203,10 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
add_subdirectory("source_modules/rtl_tcp_source")
|
||||
endif (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDDC_SOURCE)
|
||||
add_subdirectory("source_modules/sddc_source")
|
||||
endif (OPT_BUILD_SDDC_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
add_subdirectory("source_modules/sdrpp_server_source")
|
||||
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
@@ -235,6 +263,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)
|
||||
@@ -259,6 +291,14 @@ 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_VOR_RECEIVER)
|
||||
add_subdirectory("decoder_modules/vor_receiver")
|
||||
endif (OPT_BUILD_VOR_RECEIVER)
|
||||
|
||||
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
@@ -312,6 +352,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 ()
|
||||
|
||||
|
||||
@@ -354,4 +409,4 @@ endif ()
|
||||
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
|
||||
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
|
||||
# Create headers target
|
||||
# Create headers target
|
||||
|
||||
@@ -10,11 +10,11 @@ android {
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.2.0"
|
||||
versionName "1.2.1"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF"
|
||||
arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF", "-DOPT_BUILD_HYDRASDR_SOURCE=ON"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# AI USE IS FORBIDDEN
|
||||
|
||||
Use of AI for the creation of code, pull requests or issues is forbidden.
|
||||
|
||||
Submitting code, pull requests or issues generated by AI will result in a permanent ban from all of my (Alexandre Rouma) repositories.
|
||||
|
||||
# Pull Requests
|
||||
|
||||
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
|
||||
@@ -64,4 +70,4 @@ Please follow this guide to properly format the JSON files for custom color maps
|
||||
|
||||
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
|
||||
|
||||
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
||||
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace backend {
|
||||
extern const std::vector<DevVIDPID> AIRSPY_VIDPIDS;
|
||||
extern const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS;
|
||||
extern const std::vector<DevVIDPID> HACKRF_VIDPIDS;
|
||||
extern const std::vector<DevVIDPID> HYDRASDR_VIDPIDS;
|
||||
extern const std::vector<DevVIDPID> RTL_SDR_VIDPIDS;
|
||||
|
||||
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids);
|
||||
|
||||
@@ -408,6 +408,11 @@ namespace backend {
|
||||
{ 0x1d50, 0xcc15 }
|
||||
};
|
||||
|
||||
const std::vector<DevVIDPID> HYDRASDR_VIDPIDS = {
|
||||
{ 0x1d50, 0x60a1 },
|
||||
{ 0x38af, 0x0001 }
|
||||
};
|
||||
|
||||
const std::vector<DevVIDPID> RTL_SDR_VIDPIDS = {
|
||||
{ 0x0bda, 0x2832 },
|
||||
{ 0x0bda, 0x2838 },
|
||||
|
||||
@@ -99,7 +99,10 @@ namespace backend {
|
||||
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
|
||||
|
||||
#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4)
|
||||
glfwWindowHintString(GLFW_WAYLAND_APP_ID, "sdrpp");
|
||||
#endif
|
||||
|
||||
// Create window with graphics context
|
||||
monitor = glfwGetPrimaryMonitor();
|
||||
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||
|
||||
@@ -147,12 +147,12 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["menuElements"][3]["name"] = "Sinks";
|
||||
defConfig["menuElements"][3]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][3]["name"] = "Frequency Manager";
|
||||
defConfig["menuElements"][3]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][4]["name"] = "VFO Color";
|
||||
defConfig["menuElements"][4]["name"] = "Frequency Manager";
|
||||
defConfig["menuElements"][4]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][5]["name"] = "VFO Color";
|
||||
defConfig["menuElements"][5]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][6]["name"] = "Band Plan";
|
||||
defConfig["menuElements"][6]["open"] = true;
|
||||
|
||||
@@ -173,16 +173,26 @@ 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"]["HydraSDR Source"]["module"] = "hydrasdr_source";
|
||||
defConfig["moduleInstances"]["HydraSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Network Source"]["module"] = "network_source";
|
||||
defConfig["moduleInstances"]["Network Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
|
||||
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source";
|
||||
defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
|
||||
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
|
||||
@@ -193,8 +203,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";
|
||||
@@ -220,12 +234,19 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
|
||||
defConfig["modules"] = json::array();
|
||||
|
||||
defConfig["offsetMode"] = (int)0; // Off
|
||||
defConfig["offset"] = 0.0;
|
||||
defConfig["offsets"]["SpyVerter"] = 120000000.0;
|
||||
defConfig["offsets"]["Ham-It-Up"] = 125000000.0;
|
||||
defConfig["offsets"]["MMDS S-band (1998MHz)"] = -1998000000.0;
|
||||
defConfig["offsets"]["DK5AV X-Band"] = -6800000000.0;
|
||||
defConfig["offsets"]["Ku LNB (9750MHz)"] = -9750000000.0;
|
||||
defConfig["offsets"]["Ku LNB (10700MHz)"] = -10700000000.0;
|
||||
|
||||
defConfig["selectedOffset"] = "None";
|
||||
defConfig["manualOffset"] = 0.0;
|
||||
defConfig["showMenu"] = true;
|
||||
defConfig["showWaterfall"] = true;
|
||||
defConfig["source"] = "";
|
||||
defConfig["decimationPower"] = 0;
|
||||
defConfig["decimation"] = 1;
|
||||
defConfig["iqCorrection"] = false;
|
||||
defConfig["invertIQ"] = false;
|
||||
|
||||
@@ -276,6 +297,7 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
core::configManager.conf["modules"][modCount++] = "airspyhf_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "hackrf_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "hermes_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "hydrasdr_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "plutosdr_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "rfspace_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "rtl_sdr_source.so";
|
||||
@@ -306,12 +328,18 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
|
||||
// Remove unused elements
|
||||
auto items = core::configManager.conf.items();
|
||||
auto newConf = core::configManager.conf;
|
||||
bool configCorrected = false;
|
||||
for (auto const& item : items) {
|
||||
if (!defConfig.contains(item.key())) {
|
||||
flog::info("Unused key in config {0}, repairing", item.key());
|
||||
core::configManager.conf.erase(item.key());
|
||||
newConf.erase(item.key());
|
||||
configCorrected = true;
|
||||
}
|
||||
}
|
||||
if (configCorrected) {
|
||||
core::configManager.conf = newConf;
|
||||
}
|
||||
|
||||
// Update to new module representation in config if needed
|
||||
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {
|
||||
|
||||
@@ -37,9 +37,12 @@ namespace sdrpp_credits {
|
||||
const char* hardwareDonators[] = {
|
||||
"Aaronia AG",
|
||||
"Airspy",
|
||||
"Alex 4Z5LV",
|
||||
"Analog Devices",
|
||||
"CaribouLabs",
|
||||
"Deepace",
|
||||
"Ettus Research",
|
||||
"Harogic",
|
||||
"Howard Su",
|
||||
"MicroPhase",
|
||||
"Microtelecom",
|
||||
@@ -47,6 +50,7 @@ namespace sdrpp_credits {
|
||||
"Nuand",
|
||||
"RFNM",
|
||||
"RFspace",
|
||||
"RigExpert",
|
||||
"RTL-SDRblog",
|
||||
"SDRplay"
|
||||
};
|
||||
@@ -66,15 +70,18 @@ namespace sdrpp_credits {
|
||||
"Flinger Films",
|
||||
"Frank Werner (HB9FXQ)",
|
||||
"gringogrigio",
|
||||
"Jandro",
|
||||
"Jeff Moe",
|
||||
"Joe Cupano",
|
||||
"KD1SQ",
|
||||
"Kezza",
|
||||
"Krys Kamieniecki",
|
||||
"Lee Donaghy",
|
||||
"Lee KD1SQ",
|
||||
"Lee (KD1SQ)",
|
||||
".lozenge. (Hank Hill)",
|
||||
"Martin Herren (HB9FXX)",
|
||||
"NeoVilsonWong",
|
||||
"Nitin (VU2JEK)",
|
||||
"ON4MU",
|
||||
"Passion-Radio.com",
|
||||
"Paul Maine",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <volk/volk.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp::buffer {
|
||||
template<class T>
|
||||
|
||||
@@ -5,8 +5,6 @@ namespace dsp {
|
||||
template <class T>
|
||||
class Source : public block {
|
||||
public:
|
||||
Source() {}
|
||||
|
||||
Source() { init(); }
|
||||
|
||||
virtual ~Source() {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <volk/volk.h>
|
||||
#include "../buffer/buffer.h"
|
||||
|
||||
namespace dsp {
|
||||
template<class T>
|
||||
|
||||
@@ -5,113 +5,120 @@
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/style.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <gui/dialogs/dialog_box.h>
|
||||
|
||||
namespace sourcemenu {
|
||||
int offsetMode = 0;
|
||||
int sourceId = 0;
|
||||
double customOffset = 0.0;
|
||||
double effectiveOffset = 0.0;
|
||||
int decimationPower = 0;
|
||||
EventHandler<std::string> sourcesChangedHandler;
|
||||
EventHandler<std::string> sourceUnregisterHandler;
|
||||
OptionList<std::string, std::string> sources;
|
||||
std::string selectedSource;
|
||||
|
||||
int decimId = 0;
|
||||
OptionList<int, int> decimations;
|
||||
|
||||
bool iqCorrection = false;
|
||||
bool invertIQ = false;
|
||||
|
||||
EventHandler<std::string> sourceRegisteredHandler;
|
||||
EventHandler<std::string> sourceUnregisterHandler;
|
||||
EventHandler<std::string> sourceUnregisteredHandler;
|
||||
int offsetId = 0;
|
||||
double manualOffset = 0.0;
|
||||
std::string selectedOffset;
|
||||
double effectiveOffset = 0.0;
|
||||
OptionList<std::string, double> offsets;
|
||||
std::map<std::string, double> namedOffsets;
|
||||
|
||||
std::vector<std::string> sourceNames;
|
||||
std::string sourceNamesTxt;
|
||||
std::string selectedSource;
|
||||
bool showAddOffsetDialog = false;
|
||||
char newOffsetName[1024];
|
||||
double newOffset = 0.0;
|
||||
|
||||
bool showDelOffsetDialog = false;
|
||||
std::string delOffsetName = "";
|
||||
|
||||
// Offset IDs
|
||||
enum {
|
||||
OFFSET_MODE_NONE,
|
||||
OFFSET_MODE_CUSTOM,
|
||||
OFFSET_MODE_SPYVERTER,
|
||||
OFFSET_MODE_HAM_IT_UP,
|
||||
OFFSET_MODE_MMDS_SB_1998,
|
||||
OFFSET_MODE_DK5AV_XB,
|
||||
OFFSET_MODE_KU_LNB_9750,
|
||||
OFFSET_MODE_KU_LNB_10700,
|
||||
_OFFSET_MODE_COUNT
|
||||
OFFSET_ID_NONE,
|
||||
OFFSET_ID_MANUAL,
|
||||
OFFSET_ID_CUSTOM_BASE
|
||||
};
|
||||
|
||||
const char* offsetModesTxt = "None\0"
|
||||
"Custom\0"
|
||||
"SpyVerter\0"
|
||||
"Ham-It-Up\0"
|
||||
"MMDS S-band (1998MHz)\0"
|
||||
"DK5AV X-Band\0"
|
||||
"Ku LNB (9750MHz)\0"
|
||||
"Ku LNB (10700MHz)\0";
|
||||
|
||||
const char* decimationStages = "None\0"
|
||||
"2\0"
|
||||
"4\0"
|
||||
"8\0"
|
||||
"16\0"
|
||||
"32\0"
|
||||
"64\0";
|
||||
|
||||
void updateOffset() {
|
||||
if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; }
|
||||
else if (offsetMode == OFFSET_MODE_SPYVERTER) {
|
||||
effectiveOffset = 120000000;
|
||||
} // 120MHz Up-conversion
|
||||
else if (offsetMode == OFFSET_MODE_HAM_IT_UP) {
|
||||
effectiveOffset = 125000000;
|
||||
} // 125MHz Up-conversion
|
||||
else if (offsetMode == OFFSET_MODE_MMDS_SB_1998) {
|
||||
effectiveOffset = -1998000000;
|
||||
} // 1.998GHz Down-conversion
|
||||
else if (offsetMode == OFFSET_MODE_DK5AV_XB) {
|
||||
effectiveOffset = -6800000000;
|
||||
} // 6.8GHz Down-conversion
|
||||
else if (offsetMode == OFFSET_MODE_KU_LNB_9750) {
|
||||
effectiveOffset = -9750000000;
|
||||
} // 9.750GHz Down-conversion
|
||||
else if (offsetMode == OFFSET_MODE_KU_LNB_10700) {
|
||||
effectiveOffset = -10700000000;
|
||||
} // 10.7GHz Down-conversion
|
||||
else {
|
||||
// Compute the effective offset
|
||||
switch (offsetId) {
|
||||
case OFFSET_ID_NONE:
|
||||
effectiveOffset = 0;
|
||||
break;
|
||||
case OFFSET_ID_MANUAL:
|
||||
effectiveOffset = manualOffset;
|
||||
break;
|
||||
default:
|
||||
effectiveOffset = namedOffsets[offsets.name(offsetId)];
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply it
|
||||
sigpath::sourceManager.setTuningOffset(effectiveOffset);
|
||||
}
|
||||
|
||||
void selectOffsetById(int id) {
|
||||
// Update the offset mode
|
||||
offsetId = id;
|
||||
selectedOffset = offsets.name(id);
|
||||
|
||||
// Update the offset
|
||||
updateOffset();
|
||||
}
|
||||
|
||||
void selectOffsetByName(const std::string& name) {
|
||||
// If the name doesn't exist, select 'None'
|
||||
if (!offsets.nameExists(name)) {
|
||||
selectOffsetById(OFFSET_ID_NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select using the ID associated with the name
|
||||
selectOffsetById(offsets.nameId(name));
|
||||
}
|
||||
|
||||
void refreshSources() {
|
||||
sourceNames = sigpath::sourceManager.getSourceNames();
|
||||
sourceNamesTxt.clear();
|
||||
// Get sources
|
||||
auto sourceNames = sigpath::sourceManager.getSourceNames();
|
||||
|
||||
// Define source options
|
||||
sources.clear();
|
||||
for (auto name : sourceNames) {
|
||||
sourceNamesTxt += name;
|
||||
sourceNamesTxt += '\0';
|
||||
sources.define(name, name, name);
|
||||
}
|
||||
}
|
||||
|
||||
void selectSource(std::string name) {
|
||||
if (sourceNames.empty()) {
|
||||
// If there is no source, give up
|
||||
if (sources.empty()) {
|
||||
sourceId = 0;
|
||||
selectedSource.clear();
|
||||
return;
|
||||
}
|
||||
auto it = std::find(sourceNames.begin(), sourceNames.end(), name);
|
||||
if (it == sourceNames.end()) {
|
||||
selectSource(sourceNames[0]);
|
||||
|
||||
// If a source with the given name doesn't exist, select the first source instead
|
||||
if (!sources.valueExists(name)) {
|
||||
selectSource(sources.value(0));
|
||||
return;
|
||||
}
|
||||
sourceId = std::distance(sourceNames.begin(), it);
|
||||
selectedSource = sourceNames[sourceId];
|
||||
sigpath::sourceManager.selectSource(sourceNames[sourceId]);
|
||||
|
||||
// Update the GUI variables
|
||||
sourceId = sources.valueId(name);
|
||||
selectedSource = name;
|
||||
|
||||
// Select the source module
|
||||
sigpath::sourceManager.selectSource(name);
|
||||
}
|
||||
|
||||
void onSourceRegistered(std::string name, void* ctx) {
|
||||
void onSourcesChanged(std::string name, void* ctx) {
|
||||
// Update the source list
|
||||
refreshSources();
|
||||
|
||||
if (selectedSource.empty()) {
|
||||
sourceId = 0;
|
||||
selectSource(sourceNames[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
|
||||
// Reselect the current source
|
||||
selectSource(selectedSource);
|
||||
}
|
||||
|
||||
void onSourceUnregister(std::string name, void* ctx) {
|
||||
@@ -120,60 +127,173 @@ namespace sourcemenu {
|
||||
// TODO: Stop everything
|
||||
}
|
||||
|
||||
void onSourceUnregistered(std::string name, void* ctx) {
|
||||
refreshSources();
|
||||
void reloadOffsets() {
|
||||
// Clear list
|
||||
offsets.clear();
|
||||
namedOffsets.clear();
|
||||
|
||||
if (sourceNames.empty()) {
|
||||
selectedSource = "";
|
||||
return;
|
||||
// Define special offset modes
|
||||
offsets.define("None", OFFSET_ID_NONE);
|
||||
offsets.define("Manual", OFFSET_ID_MANUAL);
|
||||
|
||||
// Acquire the config file
|
||||
core::configManager.acquire();
|
||||
|
||||
// Load custom offsets
|
||||
auto ofs = core::configManager.conf["offsets"].items();
|
||||
for (auto& o : ofs) {
|
||||
namedOffsets[o.key()] = (double)o.value();
|
||||
}
|
||||
|
||||
if (name == selectedSource) {
|
||||
sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1);
|
||||
selectSource(sourceNames[sourceId]);
|
||||
return;
|
||||
// Define custom offsets
|
||||
for (auto& [name, offset] : namedOffsets) {
|
||||
offsets.define(name, offsets.size());
|
||||
}
|
||||
|
||||
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
|
||||
// Release the config file
|
||||
core::configManager.release();
|
||||
}
|
||||
|
||||
void init() {
|
||||
// Load offset modes
|
||||
reloadOffsets();
|
||||
|
||||
// Define decimation values
|
||||
decimations.define(1, "None", 1);
|
||||
decimations.define(2, "2x", 2);
|
||||
decimations.define(4, "4x", 4);
|
||||
decimations.define(8, "8x", 8);
|
||||
decimations.define(16, "16x", 16);
|
||||
decimations.define(32, "32x", 32);
|
||||
decimations.define(64, "64x", 64);
|
||||
|
||||
// Acquire the config file
|
||||
core::configManager.acquire();
|
||||
std::string selected = core::configManager.conf["source"];
|
||||
customOffset = core::configManager.conf["offset"];
|
||||
offsetMode = core::configManager.conf["offsetMode"];
|
||||
decimationPower = core::configManager.conf["decimationPower"];
|
||||
|
||||
// Load other settings
|
||||
std::string selectedSource = core::configManager.conf["source"];
|
||||
manualOffset = core::configManager.conf["manualOffset"];
|
||||
std::string selectedOffset = core::configManager.conf["selectedOffset"];
|
||||
iqCorrection = core::configManager.conf["iqCorrection"];
|
||||
invertIQ = core::configManager.conf["invertIQ"];
|
||||
int decimation = core::configManager.conf["decimation"];
|
||||
if (decimations.keyExists(decimation)) {
|
||||
decimId = decimations.keyId(decimation);
|
||||
}
|
||||
|
||||
// Release the config file
|
||||
core::configManager.release();
|
||||
|
||||
// Select the source module
|
||||
refreshSources();
|
||||
selectSource(selectedSource);
|
||||
|
||||
// Update frontend settings
|
||||
sigpath::iqFrontEnd.setDCBlocking(iqCorrection);
|
||||
sigpath::iqFrontEnd.setInvertIQ(invertIQ);
|
||||
updateOffset();
|
||||
sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
|
||||
selectOffsetByName(selectedOffset);
|
||||
|
||||
refreshSources();
|
||||
selectSource(selected);
|
||||
sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
|
||||
|
||||
sourceRegisteredHandler.handler = onSourceRegistered;
|
||||
// Register handlers
|
||||
sourcesChangedHandler.handler = onSourcesChanged;
|
||||
sourceUnregisterHandler.handler = onSourceUnregister;
|
||||
sourceUnregisteredHandler.handler = onSourceUnregistered;
|
||||
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler);
|
||||
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourcesChangedHandler);
|
||||
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
|
||||
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler);
|
||||
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourcesChangedHandler);
|
||||
}
|
||||
|
||||
core::configManager.release();
|
||||
void addOffset(const std::string& name, double offset) {
|
||||
// Acquire the config file
|
||||
core::configManager.acquire();
|
||||
|
||||
// Define a new offset
|
||||
core::configManager.conf["offsets"][name] = offset;
|
||||
|
||||
// Acquire the config file
|
||||
core::configManager.release(true);
|
||||
|
||||
// Reload the offsets
|
||||
reloadOffsets();
|
||||
|
||||
// Attempt to re-select the same one
|
||||
selectOffsetByName(selectedOffset);
|
||||
}
|
||||
|
||||
void delOffset(const std::string& name) {
|
||||
// Acquire the config file
|
||||
core::configManager.acquire();
|
||||
|
||||
// Define a new offset
|
||||
core::configManager.conf["offsets"].erase(name);
|
||||
|
||||
// Acquire the config file
|
||||
core::configManager.release(true);
|
||||
|
||||
// Reload the offsets
|
||||
reloadOffsets();
|
||||
|
||||
// Attempt to re-select the same one
|
||||
selectOffsetByName(selectedOffset);
|
||||
}
|
||||
|
||||
bool addOffsetDialog() {
|
||||
bool open = true;
|
||||
gui::mainWindow.lockWaterfallControls = true;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
const char* id = "Add offset##sdrpp_add_offset_dialog_";
|
||||
ImGui::OpenPopup(id);
|
||||
|
||||
if (ImGui::BeginPopup(id, ImGuiWindowFlags_NoResize)) {
|
||||
ImGui::LeftLabel("Name");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
ImGui::InputText("##sdrpp_add_offset_name", newOffsetName, 1023);
|
||||
|
||||
ImGui::LeftLabel("Offset");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
ImGui::InputDouble("##sdrpp_add_offset_offset", &newOffset);
|
||||
|
||||
bool nameExists = offsets.nameExists(newOffsetName);
|
||||
bool reservedName = !strcmp(newOffsetName, "None") || !strcmp(newOffsetName, "Manual");
|
||||
bool denyApply = !newOffsetName[0] || nameExists || reservedName;
|
||||
|
||||
if (nameExists) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "An offset with the given name already exists.");
|
||||
}
|
||||
else if (reservedName) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "The given name is reserved.");
|
||||
}
|
||||
|
||||
if (denyApply) { style::beginDisabled(); }
|
||||
if (ImGui::Button("Apply")) {
|
||||
addOffset(newOffsetName, newOffset);
|
||||
open = false;
|
||||
}
|
||||
if (denyApply) { style::endDisabled(); }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
open = false;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return open;
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float itemWidth = ImGui::GetContentRegionAvail().x;
|
||||
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
|
||||
float spacing = lineHeight - ImGui::GetTextLineHeight();
|
||||
bool running = gui::mainWindow.sdrIsRunning();
|
||||
|
||||
if (running) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(itemWidth);
|
||||
if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) {
|
||||
selectSource(sourceNames[sourceId]);
|
||||
if (ImGui::Combo("##source", &sourceId, sources.txt)) {
|
||||
std::string newSource = sources.value(sourceId);
|
||||
selectSource(newSource);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["source"] = sourceNames[sourceId];
|
||||
core::configManager.conf["source"] = newSource;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
@@ -196,21 +316,45 @@ namespace sourcemenu {
|
||||
}
|
||||
|
||||
ImGui::LeftLabel("Offset mode");
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) {
|
||||
updateOffset();
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX() - 2.0f*(lineHeight + 1.5f*spacing));
|
||||
if (ImGui::Combo("##_sdrpp_offset", &offsetId, offsets.txt)) {
|
||||
selectOffsetById(offsetId);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["offsetMode"] = offsetMode;
|
||||
core::configManager.conf["selectedOffset"] = offsets.key(offsetId);
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
|
||||
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::BeginDisabled(); }
|
||||
if (ImGui::Button("-##_sdrpp_offset_del_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
|
||||
delOffsetName = selectedOffset;
|
||||
showDelOffsetDialog = true;
|
||||
}
|
||||
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::EndDisabled(); }
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
|
||||
if (ImGui::Button("+##_sdrpp_offset_add_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
|
||||
strcpy(newOffsetName, "New Offset");
|
||||
showAddOffsetDialog = true;
|
||||
}
|
||||
|
||||
// Offset delete confirmation
|
||||
if (ImGui::GenericDialog("sdrpp_del_offset_confirm", showDelOffsetDialog, GENERIC_DIALOG_BUTTONS_YES_NO, []() {
|
||||
ImGui::Text("Deleting offset named \"%s\". Are you sure?", delOffsetName.c_str());
|
||||
}) == GENERIC_DIALOG_BUTTON_YES) {
|
||||
delOffset(delOffsetName);
|
||||
}
|
||||
|
||||
// Offset add diaglog
|
||||
if (showAddOffsetDialog) { showAddOffsetDialog = addOffsetDialog(); }
|
||||
|
||||
ImGui::LeftLabel("Offset");
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
||||
if (offsetMode == OFFSET_MODE_CUSTOM) {
|
||||
if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) {
|
||||
ImGui::FillWidth();
|
||||
if (offsetId == OFFSET_ID_MANUAL) {
|
||||
if (ImGui::InputDouble("##freq_offset", &manualOffset, 1.0, 100.0)) {
|
||||
updateOffset();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["offset"] = customOffset;
|
||||
core::configManager.conf["manualOffset"] = manualOffset;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
@@ -222,11 +366,11 @@ namespace sourcemenu {
|
||||
|
||||
if (running) { style::beginDisabled(); }
|
||||
ImGui::LeftLabel("Decimation");
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) {
|
||||
sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo("##source_decim", &decimId, decimations.txt)) {
|
||||
sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["decimationPower"] = decimationPower;
|
||||
core::configManager.conf["decimation"] = decimations.key(decimId);
|
||||
core::configManager.release(true);
|
||||
}
|
||||
if (running) { style::endDisabled(); }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <backend.h>
|
||||
#include <utils/hrfreq.h>
|
||||
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
@@ -90,6 +91,7 @@ void FrequencySelect::moveCursorToDigit(int i) {
|
||||
|
||||
void FrequencySelect::draw() {
|
||||
auto window = ImGui::GetCurrentWindow();
|
||||
auto io = ImGui::GetIO();
|
||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||
ImVec2 cursorPos = ImGui::GetCursorPos();
|
||||
widgetPos.x += window->Pos.x + cursorPos.x;
|
||||
@@ -132,7 +134,7 @@ void FrequencySelect::draw() {
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
int mw = ImGui::GetIO().MouseWheel;
|
||||
int mw = io.MouseWheel;
|
||||
bool onDigit = false;
|
||||
bool hovered = false;
|
||||
|
||||
@@ -174,7 +176,7 @@ void FrequencySelect::draw() {
|
||||
moveCursorToDigit(i + 1);
|
||||
}
|
||||
|
||||
auto chars = ImGui::GetIO().InputQueueCharacters;
|
||||
auto chars = io.InputQueueCharacters;
|
||||
|
||||
// For each keyboard characters, type it
|
||||
for (int j = 0; j < chars.Size; j++) {
|
||||
@@ -194,6 +196,34 @@ void FrequencySelect::draw() {
|
||||
}
|
||||
}
|
||||
digitHovered = hovered;
|
||||
|
||||
if (isInArea(mousePos, digitTopMins[0], digitBottomMaxs[11])) {
|
||||
bool shortcutKey = io.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
|
||||
bool ctrlOnly = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
|
||||
bool shiftOnly = (io.KeyMods == ImGuiKeyModFlags_Shift);
|
||||
bool copy = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_C)) || (ctrlOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
|
||||
bool paste = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_V)) || (shiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
|
||||
if (copy) {
|
||||
// Convert the freqency to a string
|
||||
std::string freqStr = hrfreq::toString(frequency);
|
||||
|
||||
// Write it to the clipboard
|
||||
ImGui::SetClipboardText(freqStr.c_str());
|
||||
}
|
||||
if (paste) {
|
||||
// Attempt to parse the clipboard as a number
|
||||
const char* clip = ImGui::GetClipboardText();
|
||||
|
||||
// If the clipboard is not empty, attempt to parse it
|
||||
if (clip) {
|
||||
double newFreq;
|
||||
if (hrfreq::fromString(clip, newFreq)) {
|
||||
setFrequency(abs(newFreq));
|
||||
frequencyChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t freq = 0;
|
||||
|
||||
@@ -85,7 +85,7 @@ void SourceManager::tune(double freq) {
|
||||
return;
|
||||
}
|
||||
// TODO: No need to always retune the hardware in Panadapter mode
|
||||
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
|
||||
selectedHandler->tuneHandler(abs(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset), selectedHandler->ctx);
|
||||
onRetune.emit(freq);
|
||||
currentFreq = freq;
|
||||
}
|
||||
|
||||
120
core/src/utils/hrfreq.cpp
Normal file
120
core/src/utils/hrfreq.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "hrfreq.h"
|
||||
#include <utils/flog.h>
|
||||
|
||||
namespace hrfreq {
|
||||
|
||||
|
||||
std::string toString(double freq) {
|
||||
// Determine the scale
|
||||
int maxDecimals = 0;
|
||||
const char* suffix = "Hz";
|
||||
if (freq >= 1e9) {
|
||||
freq /= 1e9;
|
||||
maxDecimals = 9;
|
||||
suffix = "GHz";
|
||||
}
|
||||
else if (freq >= 1e6) {
|
||||
freq /= 1e6;
|
||||
maxDecimals = 6;
|
||||
suffix = "MHz";
|
||||
}
|
||||
else if (freq >= 1e3) {
|
||||
freq /= 1e3;
|
||||
maxDecimals = 3;
|
||||
suffix = "KHz";
|
||||
}
|
||||
|
||||
// Convert to string (TODO: Not sure if limiting the decimals rounds)
|
||||
char numBuf[128];
|
||||
int numLen = sprintf(numBuf, "%0.*lf", maxDecimals, freq);
|
||||
|
||||
// If there is a decimal point, remove the useless zeros
|
||||
if (maxDecimals) {
|
||||
for (int i = numLen-1; i >= 0; i--) {
|
||||
bool dot = (numBuf[i] == '.');
|
||||
if (numBuf[i] != '0' && !dot) { break; }
|
||||
numBuf[i] = 0;
|
||||
if (dot) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
// Concat the suffix
|
||||
char finalBuf[128];
|
||||
sprintf(finalBuf, "%s%s", numBuf, suffix);
|
||||
|
||||
// Return the final string
|
||||
return finalBuf;
|
||||
}
|
||||
|
||||
bool isNumeric(char c) {
|
||||
return std::isdigit(c) || c == '+' || c == '-' || c == '.' || c == ',';
|
||||
}
|
||||
|
||||
bool fromString(const std::string& str, double& freq) {
|
||||
// Skip non-numeric characters
|
||||
int i = 0;
|
||||
char c;
|
||||
for (; i < str.size(); i++) {
|
||||
if (isNumeric(str[i])) { break; }
|
||||
}
|
||||
|
||||
// Extract the numeric part
|
||||
std::string numeric;
|
||||
for (; i < str.size(); i++) {
|
||||
// Get the character
|
||||
c = str[i];
|
||||
|
||||
// If it's a letter, stop
|
||||
if (std::isalpha(c)) { break; }
|
||||
|
||||
// If isn't numeric, skip it
|
||||
if (!isNumeric(c)) { continue; }
|
||||
|
||||
// If it's a comma, skip it for now. This enforces a dot as a decimal point
|
||||
if (c == ',') { continue; }
|
||||
|
||||
// Add the character to the numeric string
|
||||
numeric += c;
|
||||
}
|
||||
|
||||
// Attempt to parse the numeric part
|
||||
double num;
|
||||
try {
|
||||
num = std::stod(numeric);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
flog::error("Failed to parse numeric part: '{}'", numeric);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no more text is available, assume the numeric part gives a frequency in Hz
|
||||
if (i == str.size()) {
|
||||
flog::warn("No unit given, assuming it's Hz");
|
||||
freq = num;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scale the numeric value depending on the first scale character
|
||||
char scale = std::toupper(str[i]);
|
||||
switch (scale) {
|
||||
case 'G':
|
||||
num *= 1e9;
|
||||
break;
|
||||
case 'M':
|
||||
num *= 1e6;
|
||||
break;
|
||||
case 'K':
|
||||
num *= 1e3;
|
||||
break;
|
||||
case 'H':
|
||||
break;
|
||||
default:
|
||||
flog::warn("Unknown frequency scale: '{}'", scale);
|
||||
break;
|
||||
}
|
||||
|
||||
// Return the frequency
|
||||
freq = num;
|
||||
return true; // TODO
|
||||
}
|
||||
}
|
||||
19
core/src/utils/hrfreq.h
Normal file
19
core/src/utils/hrfreq.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace hrfreq {
|
||||
/**
|
||||
* Convert a frequency to a human-readable string.
|
||||
* @param freq Frequency in Hz.
|
||||
* @return Human-readable representation of the frequency.
|
||||
*/
|
||||
std::string toString(double freq);
|
||||
|
||||
/**
|
||||
* Convert a human-readable representation of a frequency to a frequency value.
|
||||
* @param str String containing the human-readable frequency.
|
||||
* @param freq Value to write the decoded frequency to.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool fromString(const std::string& str, double& freq);
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_STR "1.2.0"
|
||||
#define VERSION_STR "1.2.1"
|
||||
66
decoder_modules/atv_decoder/src/amplitude.h
Normal file
66
decoder_modules/atv_decoder/src/amplitude.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include <dsp/processor.h>
|
||||
#include <dsp/math/fast_atan2.h>
|
||||
#include <dsp/math/hz_to_rads.h>
|
||||
#include <dsp/math/normalize_phase.h>
|
||||
|
||||
namespace dsp::demod {
|
||||
class Amplitude : public Processor<complex_t, float> {
|
||||
using base_type = Processor<complex_t, float>;
|
||||
public:
|
||||
Amplitude() {}
|
||||
|
||||
Amplitude(stream<complex_t>* in, double deviation) { init(in, deviation); }
|
||||
|
||||
Amplitude(stream<complex_t>* in, double deviation, double samplerate) { init(in, deviation, samplerate); }
|
||||
|
||||
|
||||
virtual void init(stream<complex_t>* in, double deviation) {
|
||||
_invDeviation = 1.0 / deviation;
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
virtual void init(stream<complex_t>* in, double deviation, double samplerate) {
|
||||
init(in, math::hzToRads(deviation, samplerate));
|
||||
}
|
||||
|
||||
void setDeviation(double deviation) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_invDeviation = 1.0 / deviation;
|
||||
}
|
||||
|
||||
void setDeviation(double deviation, double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_invDeviation = 1.0 / math::hzToRads(deviation, samplerate);
|
||||
}
|
||||
|
||||
inline int process(int count, complex_t* in, float* out) {
|
||||
volk_32fc_magnitude_32f(out, (lv_32fc_t*)in, count);
|
||||
volk_32f_s32f_multiply_32f(out, out, -1.0f, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
phase = 0.0f;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!base_type::out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
float _invDeviation;
|
||||
float phase = 0.0f;
|
||||
};
|
||||
}
|
||||
176
decoder_modules/atv_decoder/src/burst_blanking.txt
Normal file
176
decoder_modules/atv_decoder/src/burst_blanking.txt
Normal file
@@ -0,0 +1,176 @@
|
||||
617 + 6 -> I
|
||||
|
||||
304 + 6 -> II
|
||||
|
||||
616 + 6 -> III
|
||||
|
||||
305 + 6 -> IV
|
||||
|
||||
|
||||
|
||||
304
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
|
||||
616
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
313
|
||||
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
0
|
||||
|
||||
304
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
616
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
313
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
0
|
||||
304
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
616
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
313
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
0
|
||||
304
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
616
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
313
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
0
|
||||
304
|
||||
305
|
||||
306
|
||||
307
|
||||
308
|
||||
309
|
||||
310
|
||||
311
|
||||
312
|
||||
616
|
||||
617
|
||||
618
|
||||
619
|
||||
620
|
||||
621
|
||||
622
|
||||
623
|
||||
624
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/loop/pll.h>
|
||||
#include "chrominance_filter.h"
|
||||
|
||||
// TODO: Should be 60 but had to try something
|
||||
#define BURST_START (63+CHROMA_FIR_DELAY)
|
||||
#define BURST_END (BURST_START+28)
|
||||
|
||||
#define A_PHASE ((135.0/180.0)*FL_M_PI)
|
||||
#define B_PHASE ((-135.0/180.0)*FL_M_PI)
|
||||
|
||||
namespace dsp::loop {
|
||||
class ChromaPLL : public PLL {
|
||||
using base_type = PLL;
|
||||
public:
|
||||
ChromaPLL() {}
|
||||
|
||||
ChromaPLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
|
||||
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
|
||||
}
|
||||
|
||||
inline int process(int count, complex_t* in, complex_t* out, bool aphase = false) {
|
||||
// Process the pre-burst section
|
||||
for (int i = 0; i < BURST_START; i++) {
|
||||
out[i] = in[i] * math::phasor(-pcl.phase);
|
||||
pcl.advancePhase();
|
||||
}
|
||||
|
||||
// Process the burst itself
|
||||
if (aphase) {
|
||||
for (int i = BURST_START; i < BURST_END; i++) {
|
||||
complex_t outVal = in[i] * math::phasor(-pcl.phase);
|
||||
out[i] = outVal;
|
||||
pcl.advance(math::normalizePhase(outVal.phase() - A_PHASE));
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = BURST_START; i < BURST_END; i++) {
|
||||
complex_t outVal = in[i] * math::phasor(-pcl.phase);
|
||||
out[i] = outVal;
|
||||
pcl.advance(math::normalizePhase(outVal.phase() - B_PHASE));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Process the post-burst section
|
||||
for (int i = BURST_END; i < count; i++) {
|
||||
out[i] = in[i] * math::phasor(-pcl.phase);
|
||||
pcl.advancePhase();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
inline int processBlank(int count, complex_t* in, complex_t* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = in[i] * math::phasor(-pcl.phase);
|
||||
pcl.advancePhase();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/types.h>
|
||||
|
||||
inline const dsp::complex_t CHROMA_FIR[] = {
|
||||
{-0.000005461290583903, -0.000011336784355655},
|
||||
{ 0.000020060944485414, 0.000009851315045203},
|
||||
{-0.000034177222729438, 0.000007245841504981},
|
||||
{ 0.000027694034878705, -0.000033114740542635},
|
||||
{-0.000001217597841648, 0.000039141482370942},
|
||||
{-0.000008324593371228, -0.000011315001355976},
|
||||
{-0.000038085228233509, -0.000010585909953738},
|
||||
{ 0.000114833396071141, -0.000047778708840608},
|
||||
{-0.000115428390169113, 0.000205816198882814},
|
||||
{-0.000055467806072871, -0.000356692479491626},
|
||||
{ 0.000349316846854190, 0.000326162940234916},
|
||||
{-0.000558465829929114, -0.000048001521408724},
|
||||
{ 0.000488176200631416, -0.000319593757302922},
|
||||
{-0.000169437838021935, 0.000501610900725908},
|
||||
{-0.000131793335799502, -0.000373003580727547},
|
||||
{ 0.000166817395492786, 0.000105930895534474},
|
||||
{ 0.000030499908326112, -0.000003048682668943},
|
||||
{-0.000174999505027919, 0.000168008090089458},
|
||||
{ 0.000054431163395030, -0.000385174790951272},
|
||||
{ 0.000215876012859739, 0.000372695852521209},
|
||||
{-0.000325534912280750, -0.000130173041693966},
|
||||
{ 0.000154951430569290, -0.000045395998708328},
|
||||
{ 0.000054324657659002, -0.000076028700470037},
|
||||
{ 0.000015664427565764, 0.000348002612845696},
|
||||
{-0.000345943017888332, -0.000402175417043307},
|
||||
{ 0.000568731727879741, 0.000112347863435682},
|
||||
{-0.000416485880859085, 0.000211750352828909},
|
||||
{ 0.000087462353623011, -0.000188197153014309},
|
||||
{-0.000032082305030264, -0.000136804226080664},
|
||||
{ 0.000379089999045955, 0.000303466839685362},
|
||||
{-0.000726760198519770, -0.000007022279302816},
|
||||
{ 0.000619888661818195, -0.000476871323359809},
|
||||
{-0.000151885493742993, 0.000595641190573181},
|
||||
{-0.000100626407015494, -0.000227947144491108},
|
||||
{-0.000201935458823941, -0.000107628631934340},
|
||||
{ 0.000680260922139900, -0.000120771182888852},
|
||||
{-0.000666108629277491, 0.000744775901128973},
|
||||
{ 0.000067236591919755, -0.001044125966364420},
|
||||
{ 0.000447037274751822, 0.000651912509450913},
|
||||
{-0.000262675893448686, -0.000082499729563337},
|
||||
{-0.000349821460486320, 0.000132102793530818},
|
||||
{ 0.000507024815168287, -0.000837598610490618},
|
||||
{ 0.000163814255478652, 0.001346530693477834},
|
||||
{-0.000970457632383793, -0.000968411010101160},
|
||||
{ 0.000974834882891140, 0.000116507082762032},
|
||||
{-0.000225464280571542, 0.000137131865995708},
|
||||
{-0.000211542240694642, 0.000563783548428947},
|
||||
{-0.000414412310798766, -0.001309793399193736},
|
||||
{ 0.001497010004594478, 0.001021907858926259},
|
||||
{-0.001752019159639658, 0.000116536066154131},
|
||||
{ 0.000872822027879430, -0.000783952720205569},
|
||||
{-0.000032439446797970, 0.000184988059956734},
|
||||
{ 0.000446259382722895, 0.000833040920509238},
|
||||
{-0.001741577737284306, -0.000764423771425237},
|
||||
{ 0.002306569133792772, -0.000593352416441601},
|
||||
{-0.001336084746214192, 0.001744394557524181},
|
||||
{-0.000015810020735495, -0.001342809547658260},
|
||||
{ 0.000007636494885364, 0.000009498318627546},
|
||||
{ 0.001403876768349702, 0.000326101441888391},
|
||||
{-0.002351020828600226, 0.001098649819278302},
|
||||
{ 0.001389314639579544, -0.002746943712072884},
|
||||
{ 0.000526319899588909, 0.002635084366837732},
|
||||
{-0.001109526585744687, -0.000950323796527721},
|
||||
{-0.000307792427984886, -0.000013203419520794},
|
||||
{ 0.001737955094951111, -0.001247368808692850},
|
||||
{-0.000974502437588420, 0.003352512117661680},
|
||||
{-0.001462571137390936, -0.003635296917435679},
|
||||
{ 0.002783459090201693, 0.001604420226187745},
|
||||
{-0.001471518558760170, 0.000211117948702137},
|
||||
{-0.000575340825070194, 0.000601820846100026},
|
||||
{ 0.000302090333345692, -0.003088058972305493},
|
||||
{ 0.002496092353182990, 0.003912508340989065},
|
||||
{-0.004645661091012423, -0.001630427298020200},
|
||||
{ 0.003556824805628799, -0.001209822327859352},
|
||||
{-0.000744999556260706, 0.001143238699138109},
|
||||
{ 0.000144278726929409, 0.001638049051599065},
|
||||
{-0.003025291044450178, -0.003226370992887968},
|
||||
{ 0.006047866290490120, 0.000927406808799887},
|
||||
{-0.005338456415106141, 0.003008811999350399},
|
||||
{ 0.001642959659014839, -0.003972384205231079},
|
||||
{ 0.000273874932822212, 0.000977326273749033},
|
||||
{ 0.002315022846573390, 0.001695671268241410},
|
||||
{-0.006240953957978884, 0.000207330368698293},
|
||||
{ 0.006164252120861735, -0.005177351717451013},
|
||||
{-0.001560310257561104, 0.007437030759707700},
|
||||
{-0.002131333814462852, -0.004317129694157112},
|
||||
{ 0.000280518918541908, 0.000134405998842553},
|
||||
{ 0.004612116481180659, -0.001024468120657814},
|
||||
{-0.005599300279638699, 0.006828277067771868},
|
||||
{ 0.000228879728552504, -0.010675998154712657},
|
||||
{ 0.005692081512980654, 0.007582243186569848},
|
||||
{-0.005100500569859509, -0.001364751685737153},
|
||||
{-0.000902490398043454, 0.000385770160220703},
|
||||
{ 0.003673858819546609, -0.006701685283451640},
|
||||
{ 0.002079056046131593, 0.012568579063417429},
|
||||
{-0.010730008156911677, -0.009826454574016218},
|
||||
{ 0.012092401380903161, 0.000921764172237851},
|
||||
{-0.004714530989129091, 0.003151948807627123},
|
||||
{-0.001055930168838909, 0.003228576712467020},
|
||||
{-0.004343270165991213, -0.011924332879354394},
|
||||
{ 0.016499994418955999, 0.010255324919126899},
|
||||
{-0.021047239750251585, 0.002309419513135448},
|
||||
{ 0.011855513874047341, -0.011604071033866310},
|
||||
{-0.000777842281358575, 0.005916341648175263},
|
||||
{ 0.004380939277688377, 0.007397670455730446},
|
||||
{-0.021891594662401131, -0.008509480947490166},
|
||||
{ 0.032787638290674201, -0.009950745850861956},
|
||||
{-0.021022579272463194, 0.030030850567389102},
|
||||
{-0.001508145650189953, -0.027571914870304640},
|
||||
{ 0.004056649693022923, 0.004624901687718579},
|
||||
{ 0.025728742586666287, 0.004824671348397606},
|
||||
{-0.058002700931665603, 0.030198618296813803},
|
||||
{ 0.043631619628438784, -0.096308304333327280},
|
||||
{ 0.033451363423624300, 0.136687079396426990},
|
||||
{-0.129387018420204200, -0.101540513046619400},
|
||||
{ 0.172881344826560730, -0.000000000000005297},
|
||||
{-0.129387018420198010, 0.101540513046627330},
|
||||
{ 0.033451363423615862, -0.136687079396429050},
|
||||
{ 0.043631619628444723, 0.096308304333324601},
|
||||
{-0.058002700931667456, -0.030198618296810247},
|
||||
{ 0.025728742586665992, -0.004824671348399184},
|
||||
{ 0.004056649693022639, -0.004624901687718827},
|
||||
{-0.001508145650188251, 0.027571914870304734},
|
||||
{-0.021022579272465047, -0.030030850567387805},
|
||||
{ 0.032787638290674812, 0.009950745850859947},
|
||||
{-0.021891594662400610, 0.008509480947491507},
|
||||
{ 0.004380939277687923, -0.007397670455730714},
|
||||
{-0.000777842281358940, -0.005916341648175215},
|
||||
{ 0.011855513874048058, 0.011604071033865578},
|
||||
{-0.021047239750251731, -0.002309419513134139},
|
||||
{ 0.016499994418955360, -0.010255324919127926},
|
||||
{-0.004343270165990471, 0.011924332879354665},
|
||||
{-0.001055930168839110, -0.003228576712466955},
|
||||
{-0.004714530989129287, -0.003151948807626830},
|
||||
{ 0.012092401380903103, -0.000921764172238603},
|
||||
{-0.010730008156911072, 0.009826454574016881},
|
||||
{ 0.002079056046130817, -0.012568579063417559},
|
||||
{ 0.003673858819547020, 0.006701685283451416},
|
||||
{-0.000902490398043478, -0.000385770160220647},
|
||||
{-0.005100500569859424, 0.001364751685737466},
|
||||
{ 0.005692081512980187, -0.007582243186570198},
|
||||
{ 0.000228879728553163, 0.010675998154712643},
|
||||
{-0.005599300279639117, -0.006828277067771524},
|
||||
{ 0.004612116481180722, 0.001024468120657532},
|
||||
{ 0.000280518918541900, -0.000134405998842571},
|
||||
{-0.002131333814462586, 0.004317129694157243},
|
||||
{-0.001560310257561563, -0.007437030759707604},
|
||||
{ 0.006164252120862052, 0.005177351717450635},
|
||||
{-0.006240953957978898, -0.000207330368697911},
|
||||
{ 0.002315022846573286, -0.001695671268241552},
|
||||
{ 0.000273874932822152, -0.000977326273749050},
|
||||
{ 0.001642959659015084, 0.003972384205230976},
|
||||
{-0.005338456415106324, -0.003008811999350072},
|
||||
{ 0.006047866290490063, -0.000927406808800258},
|
||||
{-0.003025291044449980, 0.003226370992888153},
|
||||
{ 0.000144278726929308, -0.001638049051599074},
|
||||
{-0.000744999556260777, -0.001143238699138063},
|
||||
{ 0.003556824805628873, 0.001209822327859134},
|
||||
{-0.004645661091012323, 0.001630427298020484},
|
||||
{ 0.002496092353182751, -0.003912508340989219},
|
||||
{ 0.000302090333345882, 0.003088058972305475},
|
||||
{-0.000575340825070231, -0.000601820846099991},
|
||||
{-0.001471518558760183, -0.000211117948702046},
|
||||
{ 0.002783459090201593, -0.001604420226187919},
|
||||
{-0.001462571137390710, 0.003635296917435769},
|
||||
{-0.000974502437588628, -0.003352512117661619},
|
||||
{ 0.001737955094951189, 0.001247368808692742},
|
||||
{-0.000307792427984885, 0.000013203419520814},
|
||||
{-0.001109526585744628, 0.000950323796527789},
|
||||
{ 0.000526319899588746, -0.002635084366837765},
|
||||
{ 0.001389314639579712, 0.002746943712072799},
|
||||
{-0.002351020828600294, -0.001098649819278158},
|
||||
{ 0.001403876768349682, -0.000326101441888477},
|
||||
{ 0.000007636494885364, -0.000009498318627546},
|
||||
{-0.000015810020735412, 0.001342809547658261},
|
||||
{-0.001336084746214299, -0.001744394557524099},
|
||||
{ 0.002306569133792808, 0.000593352416441460},
|
||||
{-0.001741577737284259, 0.000764423771425344},
|
||||
{ 0.000446259382722843, -0.000833040920509266},
|
||||
{-0.000032439446797982, -0.000184988059956732},
|
||||
{ 0.000872822027879478, 0.000783952720205515},
|
||||
{-0.001752019159639665, -0.000116536066154024},
|
||||
{ 0.001497010004594416, -0.001021907858926351},
|
||||
{-0.000414412310798685, 0.001309793399193761},
|
||||
{-0.000211542240694677, -0.000563783548428934},
|
||||
{-0.000225464280571550, -0.000137131865995694},
|
||||
{ 0.000974834882891133, -0.000116507082762092},
|
||||
{-0.000970457632383734, 0.000968411010101219},
|
||||
{ 0.000163814255478569, -0.001346530693477844},
|
||||
{ 0.000507024815168339, 0.000837598610490586},
|
||||
{-0.000349821460486328, -0.000132102793530797},
|
||||
{-0.000262675893448681, 0.000082499729563353},
|
||||
{ 0.000447037274751782, -0.000651912509450940},
|
||||
{ 0.000067236591919819, 0.001044125966364416},
|
||||
{-0.000666108629277537, -0.000744775901128932},
|
||||
{ 0.000680260922139908, 0.000120771182888810},
|
||||
{-0.000201935458823935, 0.000107628631934352},
|
||||
{-0.000100626407015480, 0.000227947144491114},
|
||||
{-0.000151885493743030, -0.000595641190573172},
|
||||
{ 0.000619888661818225, 0.000476871323359771},
|
||||
{-0.000726760198519770, 0.000007022279302861},
|
||||
{ 0.000379089999045936, -0.000303466839685386},
|
||||
{-0.000032082305030256, 0.000136804226080666},
|
||||
{ 0.000087462353623023, 0.000188197153014303},
|
||||
{-0.000416485880859098, -0.000211750352828883},
|
||||
{ 0.000568731727879734, -0.000112347863435717},
|
||||
{-0.000345943017888307, 0.000402175417043329},
|
||||
{ 0.000015664427565742, -0.000348002612845697},
|
||||
{ 0.000054324657659007, 0.000076028700470034},
|
||||
{ 0.000154951430569292, 0.000045395998708319},
|
||||
{-0.000325534912280742, 0.000130173041693986},
|
||||
{ 0.000215876012859716, -0.000372695852521222},
|
||||
{ 0.000054431163395054, 0.000385174790951269},
|
||||
{-0.000174999505027930, -0.000168008090089447},
|
||||
{ 0.000030499908326113, 0.000003048682668941},
|
||||
{ 0.000166817395492779, -0.000105930895534485},
|
||||
{-0.000131793335799479, 0.000373003580727555},
|
||||
{-0.000169437838021966, -0.000501610900725898},
|
||||
{ 0.000488176200631435, 0.000319593757302892},
|
||||
{-0.000558465829929111, 0.000048001521408758},
|
||||
{ 0.000349316846854170, -0.000326162940234938},
|
||||
{-0.000055467806072849, 0.000356692479491629},
|
||||
{-0.000115428390169126, -0.000205816198882806},
|
||||
{ 0.000114833396071144, 0.000047778708840601},
|
||||
{-0.000038085228233508, 0.000010585909953741},
|
||||
{-0.000008324593371228, 0.000011315001355977},
|
||||
{-0.000001217597841650, -0.000039141482370942},
|
||||
{ 0.000027694034878707, 0.000033114740542633},
|
||||
{-0.000034177222729439, -0.000007245841504979},
|
||||
{ 0.000020060944485413, -0.000009851315045204},
|
||||
{-0.000005461290583903, 0.000011336784355656},
|
||||
};
|
||||
|
||||
#define CHROMA_FIR_SIZE (sizeof(CHROMA_FIR)/sizeof(dsp::complex_t))
|
||||
#define CHROMA_FIR_DELAY ((CHROMA_FIR_SIZE-1)/2)
|
||||
131
decoder_modules/atv_decoder/src/filters.h
Normal file
131
decoder_modules/atv_decoder/src/filters.h
Normal file
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
#include <dsp/types.h>
|
||||
|
||||
const dsp::complex_t CHROMA_BANDPASS[123] = {
|
||||
{ -0.000007675039564594f, -0.000017362992335168f },
|
||||
{ 0.000050180791439308f, -0.000005054021864311f },
|
||||
{ -0.000022529111707761f, 0.000102942513429095f },
|
||||
{ -0.000157609487484146f, -0.000092618697641464f },
|
||||
{ 0.000205649042029007f, -0.000181710515677257f },
|
||||
{ 0.000143445458895462f, 0.000331994546004200f },
|
||||
{ -0.000414693079508517f, 0.000038265188132615f },
|
||||
{ 0.000090081630021837f, -0.000395731646002122f },
|
||||
{ 0.000257705918065856f, 0.000154354504676150f },
|
||||
{ -0.000064051192147575f, 0.000055648228186439f },
|
||||
{ 0.000089938060647145f, 0.000213032074676941f },
|
||||
{ -0.000604775098099200f, 0.000050706635726124f },
|
||||
{ 0.000223309865890358f, -0.000944433958755193f },
|
||||
{ 0.001049943574694384f, 0.000640863688898729f },
|
||||
{ -0.000983491651119595f, 0.000840133365053179f },
|
||||
{ -0.000417178588714773f, -0.001011686459999295f },
|
||||
{ 0.000616677332283103f, -0.000046513429902547f },
|
||||
{ 0.000018549463752019f, -0.000075619948809012f },
|
||||
{ 0.000734408386201158f, 0.000456742966201638f },
|
||||
{ -0.001192460562555901f, 0.001001510577200253f },
|
||||
{ -0.000729137747758392f, -0.001811046261815935f },
|
||||
{ 0.001878272869910273f, -0.000125879189667096f },
|
||||
{ -0.000312873903977849f, 0.001230889889574772f },
|
||||
{ -0.000142534831707354f, -0.000090307321579771f },
|
||||
{ -0.000942796972567241f, 0.000778470227412111f },
|
||||
{ -0.000945381510920278f, -0.002406055808135091f },
|
||||
{ 0.003537159230775561f, -0.000207350791625892f },
|
||||
{ -0.000956199555190230f, 0.003634225577771235f },
|
||||
{ -0.002543835202533561f, -0.001641705037372486f },
|
||||
{ 0.001064108471592447f, -0.000863770138941644f },
|
||||
{ -0.000335799601479829f, -0.000876091753216939f },
|
||||
{ 0.003390761989356699f, -0.000170321604912419f },
|
||||
{ -0.001408130728751909f, 0.005175554625981795f },
|
||||
{ -0.005203055300834108f, -0.003419861284250694f },
|
||||
{ 0.004342719678657084f, -0.003465264906764298f },
|
||||
{ 0.001143432997855297f, 0.003059520699490539f },
|
||||
{ 0.000304096484476364f, -0.000012725974706621f },
|
||||
{ -0.001193870642975282f, 0.004247469277548632f },
|
||||
{ -0.006681021498855877f, -0.004471771356204969f },
|
||||
{ 0.007965721969864534f, -0.006247895626072559f },
|
||||
{ 0.003365883969059717f, 0.009241201835481184f },
|
||||
{ -0.006835562188141396f, 0.000228798228738161f },
|
||||
{ 0.000409900284971528f, -0.001412838961851673f },
|
||||
{ -0.004331406608345981f, -0.002951876085350234f },
|
||||
{ 0.009290089917766562f, -0.007161958719089258f },
|
||||
{ 0.005418326020709935f, 0.015272361365960607f },
|
||||
{ -0.017077565432843410f, 0.000428641984774326f },
|
||||
{ 0.003850771342644978f, -0.012869517593577566f },
|
||||
{ 0.004380859690202961f, 0.003039552423897447f },
|
||||
{ 0.004761181766399753f, -0.003607421240356480f },
|
||||
{ 0.005926935731028822f, 0.017160134858844222f },
|
||||
{ -0.028153584885925551f, 0.000471042980325370f },
|
||||
{ 0.009655944938035437f, -0.031314555422639050f },
|
||||
{ 0.023930146568136038f, 0.016901617811072800f },
|
||||
{ -0.012998853255109976f, 0.009678807314399702f },
|
||||
{ 0.002043176559434885f, 0.006079907699564680f },
|
||||
{ -0.036686455817128191f, 0.000306882557812233f },
|
||||
{ 0.021529138474771701f, -0.067800343150283604f },
|
||||
{ 0.085421344938160879f, 0.061409588050754214f },
|
||||
{ -0.108166660998898100f, 0.079141989828113088f },
|
||||
{ -0.047617308971534079f, -0.145721049254261960f },
|
||||
{ 0.160079041453427080f, -0.000000000000000427f },
|
||||
{ -0.047617308971533295f, 0.145721049254262240f },
|
||||
{ -0.108166660998898530f, -0.079141989828112505f },
|
||||
{ 0.085421344938160546f, -0.061409588050754672f },
|
||||
{ 0.021529138474772065f, 0.067800343150283493f },
|
||||
{ -0.036686455817128191f, -0.000306882557812037f },
|
||||
{ 0.002043176559434853f, -0.006079907699564691f },
|
||||
{ -0.012998853255110026f, -0.009678807314399631f },
|
||||
{ 0.023930146568135951f, -0.016901617811072928f },
|
||||
{ 0.009655944938035604f, 0.031314555422638994f },
|
||||
{ -0.028153584885925554f, -0.000471042980325220f },
|
||||
{ 0.005926935731028730f, -0.017160134858844253f },
|
||||
{ 0.004761181766399772f, 0.003607421240356455f },
|
||||
{ 0.004380859690202943f, -0.003039552423897470f },
|
||||
{ 0.003850771342645046f, 0.012869517593577545f },
|
||||
{ -0.017077565432843413f, -0.000428641984774235f },
|
||||
{ 0.005418326020709854f, -0.015272361365960637f },
|
||||
{ 0.009290089917766600f, 0.007161958719089209f },
|
||||
{ -0.004331406608345964f, 0.002951876085350257f },
|
||||
{ 0.000409900284971536f, 0.001412838961851670f },
|
||||
{ -0.006835562188141398f, -0.000228798228738125f },
|
||||
{ 0.003365883969059667f, -0.009241201835481201f },
|
||||
{ 0.007965721969864567f, 0.006247895626072517f },
|
||||
{ -0.006681021498855855f, 0.004471771356205005f },
|
||||
{ -0.001193870642975304f, -0.004247469277548626f },
|
||||
{ 0.000304096484476364f, 0.000012725974706619f },
|
||||
{ 0.001143432997855281f, -0.003059520699490545f },
|
||||
{ 0.004342719678657102f, 0.003465264906764274f },
|
||||
{ -0.005203055300834089f, 0.003419861284250722f },
|
||||
{ -0.001408130728751936f, -0.005175554625981787f },
|
||||
{ 0.003390761989356700f, 0.000170321604912401f },
|
||||
{ -0.000335799601479825f, 0.000876091753216940f },
|
||||
{ 0.001064108471592452f, 0.000863770138941638f },
|
||||
{ -0.002543835202533552f, 0.001641705037372499f },
|
||||
{ -0.000956199555190250f, -0.003634225577771230f },
|
||||
{ 0.003537159230775563f, 0.000207350791625874f },
|
||||
{ -0.000945381510920265f, 0.002406055808135096f },
|
||||
{ -0.000942796972567245f, -0.000778470227412106f },
|
||||
{ -0.000142534831707354f, 0.000090307321579771f },
|
||||
{ -0.000312873903977856f, -0.001230889889574770f },
|
||||
{ 0.001878272869910274f, 0.000125879189667086f },
|
||||
{ -0.000729137747758382f, 0.001811046261815939f },
|
||||
{ -0.001192460562555906f, -0.001001510577200246f },
|
||||
{ 0.000734408386201156f, -0.000456742966201642f },
|
||||
{ 0.000018549463752019f, 0.000075619948809012f },
|
||||
{ 0.000616677332283103f, 0.000046513429902543f },
|
||||
{ -0.000417178588714767f, 0.001011686459999298f },
|
||||
{ -0.000983491651119600f, -0.000840133365053174f },
|
||||
{ 0.001049943574694380f, -0.000640863688898734f },
|
||||
{ 0.000223309865890363f, 0.000944433958755192f },
|
||||
{ -0.000604775098099200f, -0.000050706635726121f },
|
||||
{ 0.000089938060647144f, -0.000213032074676941f },
|
||||
{ -0.000064051192147576f, -0.000055648228186438f },
|
||||
{ 0.000257705918065856f, -0.000154354504676151f },
|
||||
{ 0.000090081630021839f, 0.000395731646002121f },
|
||||
{ -0.000414693079508517f, -0.000038265188132613f },
|
||||
{ 0.000143445458895461f, -0.000331994546004200f },
|
||||
{ 0.000205649042029008f, 0.000181710515677256f },
|
||||
{ -0.000157609487484145f, 0.000092618697641465f },
|
||||
{ -0.000022529111707761f, -0.000102942513429094f },
|
||||
{ 0.000050180791439308f, 0.000005054021864311f },
|
||||
{ -0.000007675039564594f, 0.000017362992335168f }
|
||||
};
|
||||
|
||||
#define CHROMA_BANDPASS_SIZE (sizeof(CHROMA_BANDPASS)/sizeof(dsp::complex_t))
|
||||
#define CHROMA_BANDPASS_DELAY (CHROMA_BANDPASS_SIZE/2)
|
||||
@@ -5,6 +5,33 @@
|
||||
#include <dsp/multirate/polyphase_bank.h>
|
||||
#include <dsp/math/step.h>
|
||||
|
||||
#define LINE_SIZE 945
|
||||
|
||||
#define SYNC_LEN 70
|
||||
#define SYNC_SIDE_LEN 17
|
||||
#define SYNC_L_START (LINE_SIZE - SYNC_SIDE_LEN)
|
||||
#define SYNC_R_START (SYNC_LEN/2)
|
||||
#define SYNC_R_END (SYNC_R_START + (SYNC_LEN/2) + SYNC_SIDE_LEN)
|
||||
#define SYNC_HALF_LEN ((SYNC_LEN/2) + SYNC_SIDE_LEN)
|
||||
|
||||
#define EQUAL_LEN 35
|
||||
|
||||
#define HBLANK_START SYNC_LEN
|
||||
#define HBLANK_END 155
|
||||
#define HBLANK_LEN (HBLANK_END - HBLANK_START + 1)
|
||||
|
||||
#define SYNC_LEVEL (-0.428)
|
||||
|
||||
#define COLORBURST_START 84
|
||||
#define COLORBURST_LEN 33
|
||||
|
||||
#define MAX_LOCK 1000
|
||||
|
||||
dsp::complex_t PHASE_REF[2] = {
|
||||
{ -0.707106781186547f, 0.707106781186547f },
|
||||
{ -0.707106781186547f, -0.707106781186547f }
|
||||
};
|
||||
|
||||
class LineSync : public dsp::Processor<float, float> {
|
||||
using base_type = dsp::Processor<float, float>;
|
||||
public:
|
||||
@@ -27,41 +54,17 @@ public:
|
||||
_interpPhaseCount = interpPhaseCount;
|
||||
_interpTapCount = interpTapCount;
|
||||
|
||||
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
|
||||
generateInterpTaps();
|
||||
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
|
||||
bufStart = &buffer[_interpTapCount - 1];
|
||||
|
||||
// TODO: Needs tuning, so do the gains
|
||||
maxPeriod = (int32_t)(1.0001 * (float)(1 << 30));
|
||||
minPeriod = (int32_t)(0.9999 * (float)(1 << 30));
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setOmegaGain(double omegaGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_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);
|
||||
_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);
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
|
||||
}
|
||||
|
||||
void setSyncLevel(float level) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
syncLevel = level;
|
||||
}
|
||||
|
||||
void setInterpParams(int interpPhaseCount, int interpTapCount) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
@@ -81,8 +84,7 @@ public:
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
offset = 0;
|
||||
pcl.phase = 0.0f;
|
||||
pcl.freq = _omega;
|
||||
phase = 0;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
@@ -93,61 +95,120 @@ public:
|
||||
// Copy data to work buffer
|
||||
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(float));
|
||||
|
||||
if (test2) {
|
||||
test2 = false;
|
||||
offset += 5;
|
||||
}
|
||||
|
||||
// Process all samples
|
||||
// Process samples while they are available
|
||||
while (offset < count) {
|
||||
// Calculate new output value
|
||||
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
|
||||
float outVal;
|
||||
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
|
||||
base_type::out.writeBuf[outCount++] = outVal;
|
||||
// While the offset is negative, out put zeros
|
||||
while (offset < 0 && pixel < LINE_SIZE) {
|
||||
// Output a zero
|
||||
base_type::out.writeBuf[pixel++] = 0.0f;
|
||||
|
||||
// If the end of the line is reached, process it and determin error
|
||||
float error = 0;
|
||||
if (outCount >= 720) {
|
||||
// Compute averages.
|
||||
// Increment the phase
|
||||
phase += period;
|
||||
offset += (phase >> 30);
|
||||
phase &= 0x3FFFFFFF;
|
||||
}
|
||||
|
||||
// Process as much of a line as possible
|
||||
while (offset < count && pixel < LINE_SIZE) {
|
||||
// Compute the output sample
|
||||
volk_32f_x2_dot_prod_32f(&base_type::out.writeBuf[pixel++], &buffer[offset], interpBank.phases[(phase >> 23) & 0x7F], _interpTapCount);
|
||||
|
||||
// Increment the phase
|
||||
phase += period;
|
||||
offset += (phase >> 30);
|
||||
phase &= 0x3FFFFFFF;
|
||||
}
|
||||
|
||||
// If the line is done, process it
|
||||
if (pixel == LINE_SIZE) {
|
||||
// Compute averages. (TODO: Try faster method)
|
||||
float left = 0.0f, right = 0.0f;
|
||||
for (int i = (720-17); i < 720; i++) {
|
||||
int lc = 0, rc = 0;
|
||||
for (int i = SYNC_L_START; i < LINE_SIZE; i++) {
|
||||
left += base_type::out.writeBuf[i];
|
||||
lc++;
|
||||
}
|
||||
for (int i = 0; i < 27; i++) {
|
||||
for (int i = 0; i < SYNC_R_START; i++) {
|
||||
left += base_type::out.writeBuf[i];
|
||||
lc++;
|
||||
}
|
||||
for (int i = 27; i < (54+17); i++) {
|
||||
for (int i = SYNC_R_START; i < SYNC_R_END; i++) {
|
||||
right += base_type::out.writeBuf[i];
|
||||
}
|
||||
left *= (1.0f/44.0f);
|
||||
right *= (1.0f/44.0f);
|
||||
|
||||
// If the sync is present, compute error
|
||||
if ((left < syncLevel && right < syncLevel) && !forceLock) {
|
||||
error = (left + syncBias - right);
|
||||
locked = true;
|
||||
}
|
||||
else {
|
||||
locked = false;
|
||||
rc++;
|
||||
}
|
||||
|
||||
if (++counter >= 100) {
|
||||
counter = 0;
|
||||
//flog::warn("Left: {}, Right: {}, Error: {}, Freq: {}, Phase: {}", left, right, error, pcl.freq, pcl.phase);
|
||||
// Compute the error
|
||||
float error = (left - right) * (1.0f/((float)SYNC_HALF_LEN));
|
||||
|
||||
// Compute the change in phase and frequency due to the error
|
||||
float periodDelta = error * _omegaGain;
|
||||
float phaseDelta = error * _muGain;
|
||||
|
||||
// Normalize the phase delta (TODO: Make faster)
|
||||
while (phaseDelta <= -1.0f) {
|
||||
phaseDelta += 1.0f;
|
||||
offset--;
|
||||
}
|
||||
while (phaseDelta >= 1.0f) {
|
||||
phaseDelta -= 1.0f;
|
||||
offset++;
|
||||
}
|
||||
|
||||
// Update the period (TODO: Clamp error*omegaGain to prevent weird shit with corrupt samples)
|
||||
period += (int32_t)(periodDelta * (float)(1 << 30));
|
||||
period = std::clamp<uint32_t>(period, minPeriod, maxPeriod);
|
||||
|
||||
// Update the phase
|
||||
phase += (int32_t)(phaseDelta * (float)(1 << 30));
|
||||
|
||||
// Normalize the phase
|
||||
uint32_t overflow = phase >> 30;
|
||||
if (overflow) {
|
||||
if (error < 0) {
|
||||
offset -= 4 - overflow;
|
||||
}
|
||||
else {
|
||||
offset += overflow;
|
||||
}
|
||||
}
|
||||
phase &= 0x3FFFFFFF;
|
||||
|
||||
// Find the lowest value
|
||||
float lowest = INFINITY;
|
||||
int lowestId = -1;
|
||||
for (int i = 0; i < LINE_SIZE; i++) {
|
||||
float val = base_type::out.writeBuf[i];
|
||||
if (val < lowest) {
|
||||
lowest = val;
|
||||
lowestId = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the the line is in lock
|
||||
bool lineLocked = (lowestId < SYNC_R_END || lowestId >= SYNC_L_START);
|
||||
|
||||
// Update the lock status based on the line lock
|
||||
if (!lineLocked && locked) {
|
||||
locked--;
|
||||
}
|
||||
else if (lineLocked && locked < MAX_LOCK) {
|
||||
locked++;
|
||||
}
|
||||
|
||||
// If not locked, attempt to lock by forcing the sync to happen at the right spot
|
||||
// TODO: This triggers waaaay too easily at low SNR
|
||||
if (!locked && fastLock) {
|
||||
offset += lowestId - SYNC_R_START;
|
||||
locked = MAX_LOCK / 2;
|
||||
}
|
||||
|
||||
// Output line
|
||||
if (!base_type::out.swap(outCount)) { break; }
|
||||
outCount = 0;
|
||||
if (!base_type::out.swap(LINE_SIZE)) { break; }
|
||||
pixel = 0;
|
||||
}
|
||||
|
||||
// Advance symbol offset and phase
|
||||
pcl.advance(error);
|
||||
float delta = floorf(pcl.phase);
|
||||
offset += delta;
|
||||
pcl.phase -= delta;
|
||||
}
|
||||
|
||||
// Get the offset ready for the next buffer
|
||||
offset -= count;
|
||||
|
||||
// Update delay buffer
|
||||
@@ -155,16 +216,15 @@ public:
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
return outCount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool locked = false;
|
||||
bool test2 = false;
|
||||
float syncBias = 0;
|
||||
|
||||
float syncBias = 0.0f;
|
||||
bool forceLock = false;
|
||||
uint32_t period = (0x800072F3 >> 1);//(1 << 31) + 1;
|
||||
|
||||
int counter = 0;
|
||||
int locked = 0;
|
||||
bool fastLock = true;
|
||||
|
||||
protected:
|
||||
void generateInterpTaps() {
|
||||
@@ -175,7 +235,6 @@ protected:
|
||||
}
|
||||
|
||||
dsp::multirate::PolyphaseBank<float> interpBank;
|
||||
dsp::loop::PhaseControlLoop<double, false> pcl;
|
||||
|
||||
double _omega;
|
||||
double _omegaGain;
|
||||
@@ -183,11 +242,14 @@ protected:
|
||||
double _omegaRelLimit;
|
||||
int _interpPhaseCount;
|
||||
int _interpTapCount;
|
||||
|
||||
int offset = 0;
|
||||
int outCount = 0;
|
||||
float* buffer;
|
||||
float* bufStart;
|
||||
|
||||
|
||||
uint32_t phase = 0;
|
||||
uint32_t maxPeriod;
|
||||
uint32_t minPeriod;
|
||||
float syncLevel = -0.03f;
|
||||
|
||||
int offset = 0;
|
||||
int pixel = 0;
|
||||
};
|
||||
@@ -16,9 +16,13 @@
|
||||
#include <dsp/filter/fir.h>
|
||||
#include <dsp/taps/from_array.h>
|
||||
|
||||
#include "chrominance_filter.h"
|
||||
#include "amplitude.h"
|
||||
#include <dsp/demod/am.h>
|
||||
#include <dsp/loop/fast_agc.h>
|
||||
|
||||
#include "chroma_pll.h"
|
||||
#include "filters.h"
|
||||
#include <dsp/math/normalize_phase.h>
|
||||
#include <fstream>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
@@ -29,24 +33,25 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
|
||||
#define SAMPLE_RATE (625.0f * (float)LINE_SIZE * 25.0f)
|
||||
|
||||
class ATVDecoderModule : public ModuleManager::Instance {
|
||||
public:
|
||||
ATVDecoderModule(std::string name) : img(720, 625) {
|
||||
ATVDecoderModule(std::string name) : img(768, 576) {
|
||||
this->name = name;
|
||||
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 7000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
|
||||
|
||||
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
|
||||
agc.init(vfo->output, 1.0f, 1e6, 0.001f, 1.0f);
|
||||
demod.init(&agc.out, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
|
||||
// demod.init(vfo->output, dsp::demod::AM<float>::CARRIER, 8000000.0f, 50.0 / SAMPLE_RATE, 50.0 / SAMPLE_RATE, 0.0f, SAMPLE_RATE);
|
||||
// demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
|
||||
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05);
|
||||
sink.init(&sync.out, handler, this);
|
||||
|
||||
r2c.init(NULL);
|
||||
chromaTaps = dsp::taps::fromArray(CHROMA_FIR_SIZE, CHROMA_FIR);
|
||||
fir.init(NULL, chromaTaps);
|
||||
pll.init(NULL, 0.01, 0.0, dsp::math::hzToRads(4433618.75, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*0.90, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*1.1, SAMPLE_RATE));
|
||||
|
||||
agc.start();
|
||||
demod.start();
|
||||
sync.start();
|
||||
sink.start();
|
||||
@@ -58,7 +63,10 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
if (vfo) {
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
}
|
||||
agc.stop();
|
||||
demod.stop();
|
||||
sync.stop();
|
||||
sink.stop();
|
||||
gui::menu.removeEntry(name);
|
||||
}
|
||||
|
||||
@@ -82,142 +90,218 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
style::beginDisabled();
|
||||
}
|
||||
|
||||
// Ideal width for testing: 750pixels
|
||||
|
||||
ImGui::FillWidth();
|
||||
_this->img.draw();
|
||||
|
||||
ImGui::LeftLabel("Sync");
|
||||
ImGui::FillWidth();
|
||||
ImGui::SliderFloat("##syncLvl", &_this->sync_level, -2, 2);
|
||||
|
||||
ImGui::LeftLabel("Min");
|
||||
ImGui::FillWidth();
|
||||
ImGui::SliderFloat("##minLvl", &_this->minLvl, -1.0, 1.0);
|
||||
|
||||
ImGui::LeftLabel("Span");
|
||||
ImGui::FillWidth();
|
||||
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
|
||||
|
||||
ImGui::LeftLabel("Sync Bias");
|
||||
ImGui::FillWidth();
|
||||
ImGui::SliderFloat("##syncBias", &_this->sync.syncBias,-0.1, 0.1);
|
||||
|
||||
if (ImGui::Button("Test2")) {
|
||||
_this->sync.test2 = true;
|
||||
}
|
||||
|
||||
if (ImGui::Button("Switch frame")) {
|
||||
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
|
||||
_this->evenFrame = !_this->evenFrame;
|
||||
}
|
||||
|
||||
if (_this->sync.locked) {
|
||||
ImGui::TextUnformatted("Horizontal Sync:");
|
||||
ImGui::SameLine();
|
||||
if (_this->sync.locked > 750) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
|
||||
}
|
||||
else {
|
||||
ImGui::TextUnformatted("Not locked");
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Force Lock", &_this->sync.forceLock);
|
||||
ImGui::TextUnformatted("Vertical Sync:");
|
||||
ImGui::SameLine();
|
||||
if (_this->vlock > 15) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
|
||||
}
|
||||
else {
|
||||
ImGui::TextUnformatted("Not locked");
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Fast Lock", &_this->sync.fastLock);
|
||||
ImGui::Checkbox("Color Mode", &_this->colorMode);
|
||||
|
||||
if (!_this->enabled) {
|
||||
style::endDisabled();
|
||||
}
|
||||
|
||||
ImGui::Text("Gain: %f", _this->gain);
|
||||
ImGui::Text("Offset: %f", _this->offset);
|
||||
ImGui::Text("Subcarrier: %f", _this->subcarrierFreq);
|
||||
}
|
||||
|
||||
uint32_t pp = 0;
|
||||
|
||||
static void handler(float *data, int count, void *ctx) {
|
||||
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
|
||||
|
||||
// Convert line to complex
|
||||
_this->r2c.process(720, data, _this->r2c.out.writeBuf);
|
||||
|
||||
// Isolate the chroma subcarrier
|
||||
_this->fir.process(720, _this->r2c.out.writeBuf, _this->fir.out.writeBuf);
|
||||
|
||||
// Run chroma carrier through the PLL
|
||||
_this->pll.process(720, _this->fir.out.writeBuf, _this->pll.out.writeBuf, ((_this->ypos%2)==1) ^ _this->evenFrame);
|
||||
|
||||
// Render line to the image without color
|
||||
int lypos = _this->ypos - 1;
|
||||
if (lypos < 0) { lypos = 624; }
|
||||
uint32_t* lastLine = &((uint32_t *)_this->img.buffer)[(lypos < 313) ? (lypos*720*2) : ((((lypos - 313)*2)+1)*720) ];
|
||||
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos < 313) ? (_this->ypos*720*2) : ((((_this->ypos - 313)*2)+1)*720) ];
|
||||
|
||||
//uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[_this->ypos*720];
|
||||
|
||||
// Correct the offset
|
||||
#if VOLK_VERSION_MAJOR > 2 || (VOLK_VERSION_MAJOR == 2 && VOLK_VERSION_MINOR >= 3)
|
||||
volk_32f_s32f_add_32f(data, data, _this->offset, count);
|
||||
#else
|
||||
const float ofs = _this->offset;
|
||||
for (int i = 0; i < count; i++) {
|
||||
int imval = std::clamp<float>((data[i] - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
||||
// uint32_t re = std::clamp<float>((_this->pll.out.writeBuf[i].re - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
||||
// uint32_t im = std::clamp<float>((_this->pll.out.writeBuf[i].im - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
||||
// currentLine[i] = 0xFF000000 | (im << 8) | re;
|
||||
currentLine[i] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
|
||||
data[i] += ofs;
|
||||
}
|
||||
|
||||
// Vertical scan logic
|
||||
_this->ypos++;
|
||||
bool rollover = _this->ypos >= 625;
|
||||
if (rollover) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
|
||||
_this->evenFrame = !_this->evenFrame;
|
||||
#endif
|
||||
|
||||
// Correct the gain
|
||||
volk_32f_s32f_multiply_32f(data, data, _this->gain, count);
|
||||
|
||||
// Compute the sync levels
|
||||
float syncLLevel = 0.0f;
|
||||
float syncRLevel = 0.0f;
|
||||
volk_32f_accumulator_s32f(&syncLLevel, data, EQUAL_LEN);
|
||||
volk_32f_accumulator_s32f(&syncRLevel, &data[EQUAL_LEN], SYNC_LEN - EQUAL_LEN);
|
||||
syncLLevel *= 1.0f / EQUAL_LEN;
|
||||
syncRLevel *= 1.0f / (SYNC_LEN - EQUAL_LEN);
|
||||
float syncLevel = (syncLLevel + syncRLevel) * 0.5f; // TODO: It's technically correct but if the sizes were different it wouldn't be
|
||||
|
||||
// Compute the blanking level
|
||||
float blankLevel = 0.0f;
|
||||
volk_32f_accumulator_s32f(&blankLevel, &data[HBLANK_START], HBLANK_LEN);
|
||||
blankLevel /= (float)HBLANK_LEN;
|
||||
|
||||
// Run the offset control loop
|
||||
_this->offset -= (blankLevel / _this->gain)*0.001;
|
||||
_this->offset = std::clamp<float>(_this->offset, -1.0f, 1.0f);
|
||||
_this->gain -= (blankLevel - syncLevel + SYNC_LEVEL)*0.01f;
|
||||
_this->gain = std::clamp<float>(_this->gain, 0.1f, 10.0f);
|
||||
|
||||
// Detect the sync type
|
||||
uint16_t shortSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel > 0.5f*SYNC_LEVEL) && (blankLevel > 0.5f*SYNC_LEVEL);
|
||||
uint16_t longSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel < 0.5f*SYNC_LEVEL) && (blankLevel < 0.5f*SYNC_LEVEL);
|
||||
|
||||
// Save sync type to history
|
||||
_this->syncHistory = (_this->syncHistory << 2) | (longSync << 1) | shortSync;
|
||||
|
||||
// // If the line has a colorburst, decode it
|
||||
// dsp::complex_t* buf1 = _this->r2c.out.readBuf;
|
||||
// dsp::complex_t* buf2 = _this->r2c.out.writeBuf;
|
||||
// if (true) {
|
||||
// // Convert the line into complex
|
||||
// _this->r2c.process(count, data, buf1);
|
||||
|
||||
// // Extract the chroma subcarrier (TODO: Optimise by running only where needed)
|
||||
// for (int i = COLORBURST_START; i < count-(CHROMA_BANDPASS_DELAY+1); i++) {
|
||||
// volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&buf2[i], (lv_32fc_t*)&buf1[i - CHROMA_BANDPASS_DELAY], (lv_32fc_t*)CHROMA_BANDPASS, CHROMA_BANDPASS_SIZE);
|
||||
// }
|
||||
|
||||
// // Down convert the chroma subcarrier (TODO: Optimise by running only where needed)
|
||||
// lv_32fc_t startPhase = { 1.0f, 0.0f };
|
||||
// lv_32fc_t phaseDelta = { sinf(_this->subcarrierFreq), cosf(_this->subcarrierFreq) };
|
||||
// #if VOLK_VERSION >= 030100
|
||||
// volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], &phaseDelta, &startPhase, count - COLORBURST_START);
|
||||
// #else
|
||||
// volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], phaseDelta, &startPhase, count - COLORBURST_START);
|
||||
// #endif
|
||||
|
||||
// // Compute the phase of the burst
|
||||
// dsp::complex_t burstAvg = { 0.0f, 0.0f };
|
||||
// volk_32fc_accumulator_s32fc((lv_32fc_t*)&burstAvg, (lv_32fc_t*)&buf2[COLORBURST_START], COLORBURST_LEN);
|
||||
// float burstAmp = burstAvg.amplitude();
|
||||
// if (burstAmp*(1.0f/(float)COLORBURST_LEN) < 0.02f) {
|
||||
// printf("%d\n", _this->line);
|
||||
// }
|
||||
// burstAvg *= (1.0f / (burstAmp*burstAmp));
|
||||
// burstAvg = burstAvg.conj();
|
||||
|
||||
// // Normalize the chroma data (TODO: Optimise by running only where needed)
|
||||
// volk_32fc_s32fc_multiply_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], *((lv_32fc_t*)&burstAvg), count - COLORBURST_START);
|
||||
|
||||
// // Compute the frequency error of the burst
|
||||
// float phase = buf2[COLORBURST_START].phase();
|
||||
// float error = 0.0f;
|
||||
// for (int i = COLORBURST_START+1; i < COLORBURST_START+COLORBURST_LEN; i++) {
|
||||
// float cphase = buf2[i].phase();
|
||||
// error += dsp::math::normalizePhase(cphase - phase);
|
||||
// phase = cphase;
|
||||
// }
|
||||
// error *= (1.0f / (float)(COLORBURST_LEN-1));
|
||||
|
||||
// // Update the subcarrier freq
|
||||
// _this->subcarrierFreq += error*0.0001f;
|
||||
// }
|
||||
|
||||
// Render the line if it's visible
|
||||
if (_this->ypos >= 34 && _this->ypos <= 34+576-1) {
|
||||
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos - 34)*768];
|
||||
if (_this->colorMode) {
|
||||
// for (int i = 155; i < (155+768); i++) {
|
||||
// int imval1 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].re*5.0f) * 255.0f, 0, 255);
|
||||
// int imval2 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].im*5.0f) * 255.0f, 0, 255);
|
||||
// currentLine[i-155] = 0xFF000000 | (imval2 << 8) | imval1;
|
||||
// }
|
||||
}
|
||||
else {
|
||||
for (int i = 155; i < (155+768); i++) {
|
||||
int imval = std::clamp<float>(data[i] * 255.0f, 0, 255);
|
||||
currentLine[i-155] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute whether to rollover
|
||||
bool rollToOdd = (_this->ypos == 624);
|
||||
bool rollToEven = (_this->ypos == 623);
|
||||
|
||||
// Compute the field sync
|
||||
bool syncToOdd = (_this->syncHistory == 0b0101011010010101);
|
||||
bool syncToEven = (_this->syncHistory == 0b0001011010100101);
|
||||
|
||||
// Process the sync (NOTE: should start with 0b01, but for some reason I don't see a sync?)
|
||||
if (rollToOdd || syncToOdd) {
|
||||
// Update the vertical lock state
|
||||
bool disagree = (rollToOdd ^ syncToOdd);
|
||||
if (disagree && _this->vlock > 0) {
|
||||
_this->vlock--;
|
||||
}
|
||||
else if (!disagree && _this->vlock < 20) {
|
||||
_this->vlock++;
|
||||
}
|
||||
|
||||
// Start the odd field
|
||||
_this->ypos = 1;
|
||||
_this->line++;
|
||||
}
|
||||
else if (rollToEven || syncToEven) {
|
||||
// Update the vertical lock state
|
||||
bool disagree = (rollToEven ^ syncToEven);
|
||||
if (disagree && _this->vlock > 0) {
|
||||
_this->vlock--;
|
||||
}
|
||||
else if (!disagree && _this->vlock < 20) {
|
||||
_this->vlock++;
|
||||
}
|
||||
|
||||
// Start the even field
|
||||
_this->ypos = 0;
|
||||
_this->line = 0;
|
||||
|
||||
// Swap the video buffer
|
||||
_this->img.swap();
|
||||
}
|
||||
|
||||
// Measure vsync levels
|
||||
float sync0 = 0.0f, sync1 = 0.0f;
|
||||
for (int i = 0; i < 306; i++) {
|
||||
sync0 += data[i];
|
||||
}
|
||||
for (int i = (720/2); i < ((720/2)+306); i++) {
|
||||
sync1 += data[i];
|
||||
}
|
||||
sync0 *= (1.0f/305.0f);
|
||||
sync1 *= (1.0f/305.0f);
|
||||
|
||||
// Save sync detection to history
|
||||
_this->syncHistory >>= 2;
|
||||
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8);
|
||||
|
||||
// Trigger vsync in case one is detected
|
||||
// TODO: Also sync with odd field
|
||||
if (!rollover && _this->syncHistory == 0b0000011111) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
|
||||
_this->evenFrame = !_this->evenFrame;
|
||||
}
|
||||
_this->ypos = 0;
|
||||
_this->img.swap();
|
||||
else {
|
||||
_this->ypos += 2;
|
||||
_this->line++;
|
||||
}
|
||||
}
|
||||
|
||||
// NEW SYNC:
|
||||
float offset = 0.0f;
|
||||
float gain = 1.0f;
|
||||
uint16_t syncHistory = 0;
|
||||
int line = 0;
|
||||
int ypos = 0;
|
||||
int vlock = 0;
|
||||
float subcarrierFreq = 0.0f;
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
VFOManager::VFO *vfo = NULL;
|
||||
dsp::demod::Quadrature demod;
|
||||
// dsp::demod::Quadrature demod;
|
||||
dsp::loop::FastAGC<dsp::complex_t> agc;
|
||||
dsp::demod::Amplitude demod;
|
||||
//dsp::demod::AM<float> demod;
|
||||
LineSync sync;
|
||||
dsp::sink::Handler<float> sink;
|
||||
dsp::convert::RealToComplex r2c;
|
||||
dsp::tap<dsp::complex_t> chromaTaps;
|
||||
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
|
||||
dsp::loop::ChromaPLL pll;
|
||||
int ypos = 0;
|
||||
|
||||
bool evenFrame = false;
|
||||
std::mutex evenFrameMtx;
|
||||
|
||||
float sync_level = -0.06f;
|
||||
int sync_count = 0;
|
||||
int short_sync = 0;
|
||||
|
||||
float minLvl = 0.0f;
|
||||
float spanLvl = 1.0f;
|
||||
|
||||
bool lockedLines = 0;
|
||||
uint16_t syncHistory = 0;
|
||||
bool colorMode = false;
|
||||
|
||||
ImGui::ImageDisplay img;
|
||||
};
|
||||
|
||||
37
decoder_modules/dab_decoder/CMakeLists.txt
Normal file
37
decoder_modules/dab_decoder/CMakeLists.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(dab_decoder)
|
||||
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(dab_decoder PRIVATE "src/")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_include_directories(dab_decoder PRIVATE "C:/Program Files/codec2/include/")
|
||||
target_link_directories(dab_decoder PRIVATE "C:/Program Files/codec2/lib")
|
||||
|
||||
target_link_libraries(dab_decoder PRIVATE libcodec2)
|
||||
elseif (ANDROID)
|
||||
target_include_directories(dab_decoder PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/include/codec2
|
||||
)
|
||||
|
||||
target_link_libraries(dab_decoder PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libcodec2.so
|
||||
)
|
||||
else ()
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBCODEC2 REQUIRED codec2)
|
||||
|
||||
target_include_directories(dab_decoder PRIVATE ${LIBCODEC2_INCLUDE_DIRS})
|
||||
target_link_directories(dab_decoder PRIVATE ${LIBCODEC2_LIBRARY_DIRS})
|
||||
target_link_libraries(dab_decoder PRIVATE ${LIBCODEC2_LIBRARIES})
|
||||
|
||||
# Include it because for some reason pkgconfig doesn't look here?
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
target_include_directories(dab_decoder PRIVATE "/usr/local/include")
|
||||
endif()
|
||||
endif ()
|
||||
280
decoder_modules/dab_decoder/src/dab_dsp.h
Normal file
280
decoder_modules/dab_decoder/src/dab_dsp.h
Normal file
@@ -0,0 +1,280 @@
|
||||
#pragma once
|
||||
#include <dsp/processor.h>
|
||||
#include <utils/flog.h>
|
||||
#include <fftw3.h>
|
||||
#include "dab_phase_sym.h"
|
||||
|
||||
namespace dab {
|
||||
class CyclicSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
|
||||
public:
|
||||
CyclicSync() {}
|
||||
|
||||
// TODO: The default AGC rate is probably way too fast, plot out the avgCorr to see how much it moves
|
||||
CyclicSync(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { init(in, symbolLength, cyclicPrefixLength, samplerate, agcRate); }
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) {
|
||||
// Computer the number of samples for the symbol and its cyclic prefix
|
||||
symbolSamps = round(samplerate * symbolLength);
|
||||
prefixSamps = round(samplerate * cyclicPrefixLength);
|
||||
|
||||
// Allocate and clear the delay buffer
|
||||
delayBuf = dsp::buffer::alloc<dsp::complex_t>(STREAM_BUFFER_SIZE + 64000);
|
||||
dsp::buffer::clear(delayBuf, symbolSamps);
|
||||
|
||||
// Allocate and clear the history buffer
|
||||
histBuf = dsp::buffer::alloc<dsp::complex_t>(prefixSamps);
|
||||
dsp::buffer::clear(histBuf, prefixSamps);
|
||||
|
||||
// Compute the delay input addresses
|
||||
delayBufInput = &delayBuf[symbolSamps];
|
||||
|
||||
// Compute the correlation AGC configuration
|
||||
this->agcRate = agcRate;
|
||||
agcRateInv = 1.0f - agcRate;
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Copy the data into the normal delay buffer
|
||||
memcpy(delayBufInput, base_type::_in->readBuf, count * sizeof(dsp::complex_t));
|
||||
|
||||
// Flush the input stream
|
||||
base_type::_in->flush();
|
||||
|
||||
// Do cross-correlation
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Get the current history slot
|
||||
dsp::complex_t* slot = &histBuf[histId++];
|
||||
|
||||
// Wrap around the history slot index (TODO: Check that the history buffer's length is correct)
|
||||
histId %= prefixSamps;
|
||||
|
||||
// Kick out last value from the correlation
|
||||
corr -= *slot;
|
||||
|
||||
// Save input value and compute the new prodct
|
||||
dsp::complex_t val = delayBuf[i];
|
||||
dsp::complex_t prod = val.conj()*delayBuf[i+symbolSamps];
|
||||
|
||||
// Add the new value to the correlation
|
||||
*slot = prod;
|
||||
|
||||
// Add the new value to the history buffer
|
||||
corr += prod;
|
||||
|
||||
// Compute sample amplitude
|
||||
float rcorr = corr.amplitude();
|
||||
|
||||
// If a high enough peak is reached, reset the symbol counter
|
||||
if (rcorr > avgCorr && rcorr > peakCorr) { // Note keeping an average level might not be needed
|
||||
peakCorr = rcorr;
|
||||
peakLCorr = lastCorr;
|
||||
samplesSincePeak = 0;
|
||||
}
|
||||
|
||||
// If this is the sample right after the peak, save it
|
||||
if (samplesSincePeak == 1) {
|
||||
peakRCorr = rcorr;
|
||||
}
|
||||
|
||||
// Write the sample to the output
|
||||
out.writeBuf[samplesSincePeak++] = val;
|
||||
|
||||
// If the end of the symbol is reached, send it off
|
||||
if (samplesSincePeak >= symbolSamps) {
|
||||
if (!out.swap(symbolSamps)) {
|
||||
return -1;
|
||||
}
|
||||
samplesSincePeak = 0;
|
||||
peakCorr = 0;
|
||||
}
|
||||
|
||||
// Update the average correlation
|
||||
lastCorr = rcorr;
|
||||
|
||||
// Update the average correlation value
|
||||
avgCorr = agcRate*rcorr + agcRateInv*avgCorr;
|
||||
}
|
||||
|
||||
// Move unused data
|
||||
memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
int symbolSamps;
|
||||
int prefixSamps;
|
||||
|
||||
int histId = 0;
|
||||
dsp::complex_t* histBuf;
|
||||
|
||||
dsp::complex_t* delayBuf;
|
||||
dsp::complex_t* delayBufInput;
|
||||
|
||||
dsp::complex_t corr = { 0.0f, 0.0f };
|
||||
|
||||
int samplesSincePeak = 0;
|
||||
float lastCorr = 0.0f;
|
||||
float peakCorr = 0.0f;
|
||||
float peakLCorr = 0.0f;
|
||||
float peakRCorr = 0.0f;
|
||||
|
||||
// Note only required for DAB
|
||||
float avgCorr = 0.0f;
|
||||
float agcRate;
|
||||
float agcRateInv;
|
||||
};
|
||||
|
||||
class FrameFreqSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
|
||||
public:
|
||||
FrameFreqSync() {}
|
||||
|
||||
FrameFreqSync(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { init(in, agcRate); }
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) {
|
||||
// Allocate buffers
|
||||
amps = dsp::buffer::alloc<float>(2048);
|
||||
conjRef = dsp::buffer::alloc<dsp::complex_t>(2048);
|
||||
corrIn = (dsp::complex_t*)fftwf_alloc_complex(2048);
|
||||
corrOut = (dsp::complex_t*)fftwf_alloc_complex(2048);
|
||||
|
||||
// Copy the phase reference
|
||||
memcpy(conjRef, DAB_PHASE_SYM_CONJ, 2048 * sizeof(dsp::complex_t));
|
||||
|
||||
// Plan the FFT computation
|
||||
plan = fftwf_plan_dft_1d(2048, (fftwf_complex*)corrIn, (fftwf_complex*)corrOut, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
// Compute the correlation AGC configuration
|
||||
this->agcRate = agcRate;
|
||||
agcRateInv = 1.0f - agcRate;
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Apply frequency shift
|
||||
lv_32fc_t phase = lv_cmake(1.0f, 0.0f);
|
||||
lv_32fc_t phaseDelta = lv_cmake(cos(offset), sin(offset));
|
||||
#if VOLK_VERSION >= 030100
|
||||
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
#else
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
#endif
|
||||
|
||||
// Compute the amplitude amplitude of all samples
|
||||
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)_in->readBuf, 2048);
|
||||
|
||||
// Compute the average signal level by adding up all values
|
||||
float level = 0.0f;
|
||||
volk_32f_accumulator_s32f(&level, amps, 2048);
|
||||
|
||||
// Detect a frame sync condition
|
||||
if (level < avgLvl * 0.5f) {
|
||||
// Reset symbol counter
|
||||
sym = 1;
|
||||
|
||||
// Update the average level
|
||||
avgLvl = agcRate*level + agcRateInv*avgLvl;
|
||||
|
||||
// Flush the input stream and return
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
// Update the average level
|
||||
avgLvl = agcRate*level + agcRateInv*avgLvl;
|
||||
|
||||
// Handle phase reference
|
||||
if (sym == 1) {
|
||||
// Output the symbols (DEBUG ONLY)
|
||||
memcpy(corrIn, _in->readBuf, 2048 * sizeof(dsp::complex_t));
|
||||
fftwf_execute(plan);
|
||||
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
|
||||
int outCount = 0;
|
||||
dsp::complex_t pi4 = { cos(3.1415926535*0.25), sin(3.1415926535*0.25) };
|
||||
for (int i = -767; i < 768; i++) {
|
||||
if (!i) { continue; }
|
||||
int cid0 = ((i-1) >= 0) ? (i-1) : 2048+(i-1);
|
||||
int cid1 = (i >= 0) ? i : 2048+i;;
|
||||
out.writeBuf[outCount++] = pi4 * (corrOut[cid1] * corrOut[cid0].conj()) * (1.0f/(amps[cid0]*amps[cid0]));
|
||||
}
|
||||
out.swap(outCount);
|
||||
|
||||
// Multiply the samples with the conjugated phase reference signal
|
||||
volk_32fc_x2_multiply_32fc((lv_32fc_t*)corrIn, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)conjRef, 2048);
|
||||
|
||||
// Compute the FFT of the product
|
||||
fftwf_execute(plan);
|
||||
|
||||
// Compute the amplitude of the bins
|
||||
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
|
||||
|
||||
// Locate highest power bin
|
||||
uint32_t peakId;
|
||||
volk_32f_index_max_32u(&peakId, amps, 2048);
|
||||
|
||||
// Obtain the value of the bins next to the peak
|
||||
float peakL = amps[(peakId + 2047) % 2048];
|
||||
float peakR = amps[(peakId + 1) % 2048];
|
||||
|
||||
// Compute the integer frequency offset
|
||||
float offInt = (peakId < 1024) ? (float)peakId : ((float)peakId - 2048.0f);
|
||||
|
||||
// Compute the frequency offset in rad/samp
|
||||
float off = 3.1415926535f * (offInt + ((peakR - peakL) / (peakR + peakL))) * (1.0f / 1024.0f);
|
||||
|
||||
// Run control loop
|
||||
offset -= 0.1f*off;
|
||||
flog::debug("Offset: {} Hz, Error: {} Hz, Avg Level: {}", offset * (0.5f/3.1415926535f)*2.048e6, off * (0.5f/3.1415926535f)*2.048e6, avgLvl);
|
||||
}
|
||||
|
||||
// Increment the symbol counter
|
||||
sym++;
|
||||
|
||||
// Flush the input stream and return
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
fftwf_plan plan;
|
||||
|
||||
float* amps;
|
||||
dsp::complex_t* conjRef;
|
||||
dsp::complex_t* corrIn;
|
||||
dsp::complex_t* corrOut;
|
||||
|
||||
int sym;
|
||||
float offset = 0.0f;
|
||||
|
||||
float avgLvl = 0.0f;
|
||||
float agcRate;
|
||||
float agcRateInv;
|
||||
};
|
||||
}
|
||||
2053
decoder_modules/dab_decoder/src/dab_phase_sym.h
Normal file
2053
decoder_modules/dab_decoder/src/dab_phase_sym.h
Normal file
File diff suppressed because it is too large
Load Diff
163
decoder_modules/dab_decoder/src/main.cpp
Normal file
163
decoder_modules/dab_decoder/src/main.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <imgui.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <module.h>
|
||||
#include <filesystem>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include "dab_dsp.h"
|
||||
#include <gui/widgets/constellation_diagram.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "dab_decoder",
|
||||
/* Description: */ "DAB/DAB+ Decoder for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
#define INPUT_SAMPLE_RATE 2.048e6
|
||||
#define VFO_BANDWIDTH 1.6e6
|
||||
|
||||
class M17DecoderModule : public ModuleManager::Instance {
|
||||
public:
|
||||
M17DecoderModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
file = std::ofstream("sync4.f32", std::ios::out | std::ios::binary);
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
|
||||
config.release(true);
|
||||
|
||||
// Initialize VFO
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
|
||||
vfo->setSnapInterval(250);
|
||||
|
||||
// Initialize DSP here
|
||||
csync.init(vfo->output, 1e-3, 246e-6, INPUT_SAMPLE_RATE);
|
||||
ffsync.init(&csync.out);
|
||||
ns.init(&ffsync.out, handler, this);
|
||||
|
||||
// Start DSO Here
|
||||
csync.start();
|
||||
ffsync.start();
|
||||
ns.start();
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
}
|
||||
|
||||
~M17DecoderModule() {
|
||||
gui::menu.removeEntry(name);
|
||||
// Stop DSP Here
|
||||
if (enabled) {
|
||||
csync.stop();
|
||||
ffsync.stop();
|
||||
ns.stop();
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
}
|
||||
|
||||
sigpath::sinkManager.unregisterStream(name);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
double bw = gui::waterfall.getBandwidth();
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
|
||||
vfo->setSnapInterval(250);
|
||||
|
||||
// Set Input of demod here
|
||||
csync.setInput(vfo->output);
|
||||
|
||||
// Start DSP here
|
||||
csync.start();
|
||||
ffsync.start();
|
||||
ns.start();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
// Stop DSP here
|
||||
csync.stop();
|
||||
ffsync.stop();
|
||||
ns.stop();
|
||||
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static void menuHandler(void* ctx) {
|
||||
M17DecoderModule* _this = (M17DecoderModule*)ctx;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (!_this->enabled) { style::beginDisabled(); }
|
||||
|
||||
_this->constDiagram.draw();
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
std::ofstream file;
|
||||
|
||||
static void handler(dsp::complex_t* data, int count, void* ctx) {
|
||||
M17DecoderModule* _this = (M17DecoderModule*)ctx;
|
||||
//_this->file.write((char*)data, count * sizeof(dsp::complex_t));
|
||||
|
||||
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
|
||||
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
|
||||
_this->constDiagram.releaseBuffer();
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
dab::CyclicSync csync;
|
||||
dab::FrameFreqSync ffsync;
|
||||
dsp::sink::Handler<dsp::complex_t> ns;
|
||||
|
||||
ImGui::ConstellationDiagram constDiagram;
|
||||
|
||||
// DSP Chain
|
||||
VFOManager::VFO* vfo;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Create default recording directory
|
||||
json def = json({});
|
||||
config.setPath(core::args["root"].s() + "/dab_decoder_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new M17DecoderModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (M17DecoderModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
34
decoder_modules/dab_decoder/src/optimized_algo.txt
Normal file
34
decoder_modules/dab_decoder/src/optimized_algo.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
0123456789
|
||||
--- ---
|
||||
|
||||
0*4
|
||||
1*5
|
||||
2*6
|
||||
|
||||
1*5
|
||||
2*6 = L + 3*7 - 0*4
|
||||
3*7
|
||||
|
||||
2*6
|
||||
3*7 = L + 4*8 - 1*5
|
||||
4*8
|
||||
|
||||
3*7
|
||||
4*8 = L + 5*9 - 2*6
|
||||
5*9
|
||||
|
||||
|
||||
|
||||
0*5
|
||||
1*6
|
||||
2*7
|
||||
|
||||
1*6
|
||||
2*7
|
||||
3*8
|
||||
|
||||
2*7
|
||||
3*8
|
||||
4*9
|
||||
|
||||
=> Use same technique to cache the interpolation results
|
||||
8
decoder_modules/ryfi_decoder/CMakeLists.txt
Normal file
8
decoder_modules/ryfi_decoder/CMakeLists.txt
Normal 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/")
|
||||
139
decoder_modules/ryfi_decoder/src/main.cpp
Normal file
139
decoder_modules/ryfi_decoder/src/main.cpp
Normal 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_() {
|
||||
|
||||
}
|
||||
74
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.cpp
Normal file
74
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
71
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.h
Normal file
71
decoder_modules/ryfi_decoder/src/ryfi/conv_codec.h
Normal 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;
|
||||
};
|
||||
}
|
||||
37
decoder_modules/ryfi_decoder/src/ryfi/frame.cpp
Normal file
37
decoder_modules/ryfi_decoder/src/ryfi/frame.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
42
decoder_modules/ryfi_decoder/src/ryfi/frame.h
Normal file
42
decoder_modules/ryfi_decoder/src/ryfi/frame.h
Normal 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];
|
||||
};
|
||||
}
|
||||
137
decoder_modules/ryfi_decoder/src/ryfi/framing.cpp
Normal file
137
decoder_modules/ryfi_decoder/src/ryfi/framing.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
87
decoder_modules/ryfi_decoder/src/ryfi/framing.h
Normal file
87
decoder_modules/ryfi_decoder/src/ryfi/framing.h
Normal 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;
|
||||
};
|
||||
}
|
||||
126
decoder_modules/ryfi_decoder/src/ryfi/packet.cpp
Normal file
126
decoder_modules/ryfi_decoder/src/ryfi/packet.cpp
Normal 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];
|
||||
}
|
||||
}
|
||||
89
decoder_modules/ryfi_decoder/src/ryfi/packet.h
Normal file
89
decoder_modules/ryfi_decoder/src/ryfi/packet.h
Normal 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;
|
||||
};
|
||||
}
|
||||
194
decoder_modules/ryfi_decoder/src/ryfi/receiver.cpp
Normal file
194
decoder_modules/ryfi_decoder/src/ryfi/receiver.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
69
decoder_modules/ryfi_decoder/src/ryfi/receiver.h
Normal file
69
decoder_modules/ryfi_decoder/src/ryfi/receiver.h
Normal 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;
|
||||
};
|
||||
}
|
||||
169
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.cpp
Normal file
169
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.cpp
Normal 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
|
||||
};
|
||||
}
|
||||
82
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.h
Normal file
82
decoder_modules/ryfi_decoder/src/ryfi/rs_codec.h
Normal 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;
|
||||
};
|
||||
}
|
||||
177
decoder_modules/ryfi_decoder/src/ryfi/transmitter.cpp
Normal file
177
decoder_modules/ryfi_decoder/src/ryfi/transmitter.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
69
decoder_modules/ryfi_decoder/src/ryfi/transmitter.h
Normal file
69
decoder_modules/ryfi_decoder/src/ryfi/transmitter.h
Normal 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;
|
||||
};
|
||||
}
|
||||
8
decoder_modules/vor_receiver/CMakeLists.txt
Normal file
8
decoder_modules/vor_receiver/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(vor_receiver)
|
||||
|
||||
file(GLOB_RECURSE SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(vor_receiver PRIVATE "src/")
|
||||
128
decoder_modules/vor_receiver/src/main.cpp
Normal file
128
decoder_modules/vor_receiver/src/main.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#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/buffer/reshaper.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <gui/widgets/constellation_diagram.h>
|
||||
#include "vor_decoder.h"
|
||||
#include <fstream>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "vor_receiver",
|
||||
/* Description: */ "VOR Receiver for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
#define INPUT_SAMPLE_RATE VOR_IN_SR
|
||||
|
||||
class VORReceiverModule : public ModuleManager::Instance {
|
||||
public:
|
||||
VORReceiverModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
// TODO: Load config
|
||||
config.release();
|
||||
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
|
||||
decoder = new vor::Decoder(vfo->output, 1);
|
||||
decoder->onBearing.bind(&VORReceiverModule::onBearing, this);
|
||||
|
||||
decoder->start();
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
}
|
||||
|
||||
~VORReceiverModule() {
|
||||
decoder->stop();
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
gui::menu.removeEntry(name);
|
||||
delete decoder;
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
double bw = gui::waterfall.getBandwidth();
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
|
||||
|
||||
decoder->setInput(vfo->output);
|
||||
|
||||
decoder->start();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
decoder->stop();
|
||||
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static void menuHandler(void* ctx) {
|
||||
VORReceiverModule* _this = (VORReceiverModule*)ctx;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (!_this->enabled) { style::beginDisabled(); }
|
||||
|
||||
ImGui::Text("Bearing: %f°", _this->bearing);
|
||||
ImGui::Text("Quality: %0.1f%%", _this->quality);
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
void onBearing(float nbearing, float nquality) {
|
||||
bearing = (180.0f * nbearing / FL_M_PI);
|
||||
quality = nquality * 100.0f;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
// DSP Chain
|
||||
VFOManager::VFO* vfo;
|
||||
vor::Decoder* decoder;
|
||||
|
||||
float bearing = 0.0f, quality = 0.0f;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Create default recording directory
|
||||
std::string root = (std::string)core::args["root"];
|
||||
json def = json({});
|
||||
config.setPath(root + "/vor_receiver_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new VORReceiverModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (VORReceiverModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
50
decoder_modules/vor_receiver/src/vor_decoder.cpp
Normal file
50
decoder_modules/vor_receiver/src/vor_decoder.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "vor_decoder.h"
|
||||
|
||||
#define STDDEV_NORM_FACTOR 1.813799364234218f // 2.0f * FL_M_PI / sqrt(12)
|
||||
|
||||
namespace vor {
|
||||
Decoder::Decoder(dsp::stream<dsp::complex_t>* in, double integrationTime) {
|
||||
rx.init(in);
|
||||
reshape.init(&rx.out, round(1000.0 * integrationTime), 0);
|
||||
symSink.init(&reshape.out, dataHandler, this);
|
||||
}
|
||||
|
||||
Decoder::~Decoder() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void Decoder::setInput(dsp::stream<dsp::complex_t>* in) {
|
||||
rx.setInput(in);
|
||||
}
|
||||
|
||||
void Decoder::start() {
|
||||
rx.start();
|
||||
reshape.start();
|
||||
symSink.start();
|
||||
}
|
||||
|
||||
void Decoder::stop() {
|
||||
rx.stop();
|
||||
reshape.stop();
|
||||
symSink.stop();
|
||||
}
|
||||
|
||||
void Decoder::dataHandler(float* data, int count, void* ctx) {
|
||||
// Get the instance from context
|
||||
Decoder* _this = (Decoder*)ctx;
|
||||
|
||||
// Compute the mean and standard deviation of the
|
||||
float mean, stddev;
|
||||
volk_32f_stddev_and_mean_32f_x2(&stddev, &mean, data, count);
|
||||
|
||||
// Compute the signal quality
|
||||
float quality = std::max<float>(1.0f - (stddev / STDDEV_NORM_FACTOR), 0.0f);
|
||||
|
||||
// Convert the phase difference to a compass heading
|
||||
mean = -mean;
|
||||
if (mean < 0) { mean = 2.0f*FL_M_PI + mean; }
|
||||
|
||||
// Call the handler
|
||||
_this->onBearing(mean, quality);
|
||||
}
|
||||
}
|
||||
49
decoder_modules/vor_receiver/src/vor_decoder.h
Normal file
49
decoder_modules/vor_receiver/src/vor_decoder.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "vor_receiver.h"
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <utils/new_event.h>
|
||||
|
||||
namespace vor {
|
||||
// Note: hard coded to 22KHz samplerate
|
||||
class Decoder {
|
||||
public:
|
||||
/**
|
||||
* Create an instance of a VOR decoder.
|
||||
* @param in Input IQ stream at 22 KHz sampling rate.
|
||||
* @param integrationTime Integration time of the bearing data in seconds.
|
||||
*/
|
||||
Decoder(dsp::stream<dsp::complex_t>* in, double integrationTime);
|
||||
|
||||
// Destructor
|
||||
~Decoder();
|
||||
|
||||
/**
|
||||
* Set the input stream.
|
||||
* @param in Input IQ stream at 22 KHz sampling rate.
|
||||
*/
|
||||
void setInput(dsp::stream<dsp::complex_t>* in);
|
||||
|
||||
/**
|
||||
* Start the decoder.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the decoder.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* handler(bearing, signalQuality);
|
||||
*/
|
||||
NewEvent<float, float> onBearing;
|
||||
|
||||
private:
|
||||
static void dataHandler(float* data, int count, void* ctx);
|
||||
|
||||
// DSP
|
||||
Receiver rx;
|
||||
dsp::buffer::Reshaper<float> reshape;
|
||||
dsp::sink::Handler<float> symSink;
|
||||
};
|
||||
}
|
||||
2019
decoder_modules/vor_receiver/src/vor_fm_filter.h
Normal file
2019
decoder_modules/vor_receiver/src/vor_fm_filter.h
Normal file
File diff suppressed because it is too large
Load Diff
106
decoder_modules/vor_receiver/src/vor_receiver.h
Normal file
106
decoder_modules/vor_receiver/src/vor_receiver.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#include <dsp/sink.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/demod/am.h>
|
||||
#include <dsp/demod/quadrature.h>
|
||||
#include <dsp/convert/real_to_complex.h>
|
||||
#include <dsp/channel/frequency_xlator.h>
|
||||
#include <dsp/filter/fir.h>
|
||||
#include <dsp/math/delay.h>
|
||||
#include <dsp/math/conjugate.h>
|
||||
#include <dsp/channel/rx_vfo.h>
|
||||
#include "vor_fm_filter.h"
|
||||
#include <utils/wav.h>
|
||||
|
||||
#define VOR_IN_SR 25e3
|
||||
|
||||
namespace vor {
|
||||
class Receiver : public dsp::Processor<dsp::complex_t, float> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, float>;
|
||||
public:
|
||||
Receiver() {}
|
||||
|
||||
Receiver(dsp::stream<dsp::complex_t>* in) { init(in); }
|
||||
|
||||
~Receiver() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::taps::free(fmfTaps);
|
||||
}
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in) {
|
||||
amd.init(NULL, dsp::demod::AM<float>::CARRIER, VOR_IN_SR, 50.0f / VOR_IN_SR, 5.0f / VOR_IN_SR, 100.0f / VOR_IN_SR, VOR_IN_SR);
|
||||
amr2c.init(NULL);
|
||||
fmr2c.init(NULL);
|
||||
fmx.init(NULL, -9960, VOR_IN_SR);
|
||||
fmfTaps = dsp::taps::fromArray(FM_TAPS_COUNT, fm_taps);
|
||||
fmf.init(NULL, fmfTaps);
|
||||
fmd.init(NULL, 600, VOR_IN_SR);
|
||||
amde.init(NULL, FM_TAPS_COUNT / 2);
|
||||
amv.init(NULL, VOR_IN_SR, 1000, 30, 30);
|
||||
fmv.init(NULL, VOR_IN_SR, 1000, 30, 30);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
int process(dsp::complex_t* in, float* out, int count) {
|
||||
// Demodulate the AM outer modulation
|
||||
volk_32fc_magnitude_32f(amd.out.writeBuf, (lv_32fc_t*)in, count);
|
||||
amr2c.process(count, amd.out.writeBuf, amr2c.out.writeBuf);
|
||||
|
||||
// Isolate the FM subcarrier
|
||||
fmx.process(count, amr2c.out.writeBuf, fmx.out.writeBuf);
|
||||
fmf.process(count, fmx.out.writeBuf, fmx.out.writeBuf);
|
||||
|
||||
// Demodulate the FM subcarrier
|
||||
fmd.process(count, fmx.out.writeBuf, fmd.out.writeBuf);
|
||||
fmr2c.process(count, fmd.out.writeBuf, fmr2c.out.writeBuf);
|
||||
|
||||
// Delay the AM signal by the same amount as the FM one
|
||||
amde.process(count, amr2c.out.writeBuf, amr2c.out.writeBuf);
|
||||
|
||||
// Isolate the 30Hz component on both the AM and FM channels
|
||||
int rcount = amv.process(count, amr2c.out.writeBuf, amv.out.writeBuf);
|
||||
fmv.process(count, fmr2c.out.writeBuf, fmv.out.writeBuf);
|
||||
|
||||
// If no data was returned, we're done for this round
|
||||
if (!rcount) { return 0; }
|
||||
|
||||
// Conjugate FM reference
|
||||
volk_32fc_conjugate_32fc((lv_32fc_t*)fmv.out.writeBuf, (lv_32fc_t*)fmv.out.writeBuf, rcount);
|
||||
|
||||
// Multiply both together
|
||||
volk_32fc_x2_multiply_32fc((lv_32fc_t*)amv.out.writeBuf, (lv_32fc_t*)amv.out.writeBuf, (lv_32fc_t*)fmv.out.writeBuf, rcount);
|
||||
|
||||
// Compute angle
|
||||
volk_32fc_s32f_atan2_32f(out, (lv_32fc_t*)amv.out.writeBuf, 1.0f, rcount);
|
||||
|
||||
return rcount;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(base_type::_in->readBuf, base_type::out.writeBuf, count);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
private:
|
||||
dsp::demod::AM<float> amd;
|
||||
dsp::convert::RealToComplex amr2c;
|
||||
dsp::convert::RealToComplex fmr2c;
|
||||
dsp::channel::FrequencyXlator fmx;
|
||||
dsp::tap<float> fmfTaps;
|
||||
dsp::filter::FIR<dsp::complex_t, float> fmf;
|
||||
dsp::demod::Quadrature fmd;
|
||||
dsp::math::Delay<dsp::complex_t> amde;
|
||||
dsp::channel::RxVFO amv;
|
||||
dsp::channel::RxVFO fmv;
|
||||
};
|
||||
}
|
||||
@@ -6,13 +6,14 @@ 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.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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@@ -25,10 +26,40 @@ 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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -6,13 +6,14 @@ 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.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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@@ -25,10 +26,40 @@ 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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd /root
|
||||
|
||||
# Install dependencies and tools
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev 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
|
||||
|
||||
# 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 ..
|
||||
|
||||
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
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk1-dev, librtaudio-dev, libzstd-dev'
|
||||
@@ -6,13 +6,14 @@ 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.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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@@ -25,10 +26,40 @@ 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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:mantic
|
||||
FROM debian:trixie
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
COPY do_build.sh /root
|
||||
RUN chmod +x /root/do_build.sh
|
||||
66
docker_builds/debian_trixie/do_build.sh
Normal file
66
docker_builds/debian_trixie/do_build.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'
|
||||
@@ -12,13 +12,14 @@ 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.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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install a more recent libusb version
|
||||
@@ -51,6 +52,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
|
||||
@@ -62,11 +83,21 @@ echo 'Version: 0.7' >> /usr/share/pkgconfig/codec2.pc
|
||||
echo 'Libs: -L/usr/include/x86_64-linux-gnu/ -lcodec2' >> /usr/share/pkgconfig/codec2.pc
|
||||
echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
# Build SDR++ Itself
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
# Generate package
|
||||
|
||||
@@ -6,13 +6,14 @@ 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.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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@@ -25,10 +26,40 @@ 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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -6,13 +6,14 @@ 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.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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@@ -25,10 +26,40 @@ 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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd /root
|
||||
|
||||
# Install dependencies and tools
|
||||
apt update
|
||||
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
|
||||
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
|
||||
libcodec2-dev autoconf libtool xxd
|
||||
|
||||
# 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 ..
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'
|
||||
@@ -6,13 +6,14 @@ 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.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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/libsdrplay_api.so.3.15 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
@@ -25,10 +26,40 @@ 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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:buster
|
||||
FROM ubuntu:oracular
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
COPY do_build.sh /root
|
||||
RUN chmod +x /root/do_build.sh
|
||||
66
docker_builds/ubuntu_oracular/do_build.sh
Normal file
66
docker_builds/ubuntu_oracular/do_build.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/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
|
||||
SDRPLAY_ARCH=$(dpkg --print-architecture)
|
||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.15.2
|
||||
cp $SDRPLAY_ARCH/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 ../../
|
||||
|
||||
# Install libhydrasdr
|
||||
git clone https://github.com/hydrasdr/rfone_host
|
||||
cd rfone_host
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'
|
||||
@@ -28,6 +28,8 @@ bundle_is_not_to_be_installed() {
|
||||
if [ "$1" = "Security" ]; then echo 1; fi
|
||||
if [ "$1" = "AppleFSCompression" ]; then echo 1; fi
|
||||
if [ "$1" = "libsdrplay_api.so.3.14" ]; then echo 1; fi
|
||||
if [ "$1" = "libsdrplay_api.so.3.15" ]; then echo 1; fi
|
||||
if [ "$1" = "libxml2.2.dylib" ]; then echo 1; fi
|
||||
}
|
||||
|
||||
# ========================= FOR INTERNAL USE ONLY =========================
|
||||
@@ -226,4 +228,4 @@ bundle_sign() {
|
||||
fi
|
||||
|
||||
codesign --force --deep -s - $1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ mkdir sdrpp_debian_amd64/DEBIAN
|
||||
# Create package info
|
||||
echo Create package info
|
||||
echo Package: sdrpp >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Version: 1.2.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Version: 1.2.1$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Maintainer: Ryzerth >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Architecture: all >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Description: Bloat-free SDR receiver software >> sdrpp_debian_amd64/DEBIAN/control
|
||||
|
||||
@@ -22,7 +22,7 @@ cp -R root/res/* $BUNDLE/Contents/Resources/
|
||||
bundle_create_icns root/res/icons/sdrpp.macos.png $BUNDLE/Contents/Resources/sdrpp
|
||||
|
||||
# Create the property list
|
||||
bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.0 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
|
||||
bundle_create_plist sdrpp SDR++ org.sdrpp.sdrpp 1.2.1 sdrp sdrpp sdrpp $BUNDLE/Contents/Info.plist
|
||||
|
||||
# ========================= Install binaries =========================
|
||||
|
||||
@@ -35,11 +35,15 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/airspyhf_source/airspyhf_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/bladerf_source/bladerf_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/file_source/file_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/fobossdr_source/fobossdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hydrasdr_source/hydrasdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/network_source/network_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfnm_source/rfnm_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfspace_source/rfspace_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_sdr_source/rtl_sdr_source.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_tcp_source/rtl_tcp_source.dylib
|
||||
@@ -54,6 +58,7 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/sink_modules/n
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/sink_modules/new_portaudio_sink/new_portaudio_sink.dylib
|
||||
|
||||
# Decoder modules
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_modules/atv_decoder/atv_decoder.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_modules/m17_decoder/m17_decoder.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_modules/meteor_demodulator/meteor_demodulator.dylib
|
||||
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_modules/radio/radio.dylib
|
||||
|
||||
@@ -24,14 +24,22 @@ 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/
|
||||
|
||||
cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windows_x64/modules/
|
||||
|
||||
cp $build_dir/source_modules/hydrasdr_source/Release/hydrasdr_source.dll sdrpp_windows_x64/modules/
|
||||
cp 'C:/Program Files (x86)/hydrasdr_all/bin/hydrasdr.dll' sdrpp_windows_x64/
|
||||
|
||||
cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/
|
||||
cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/
|
||||
|
||||
cp $build_dir/source_modules/network_source/Release/network_source.dll sdrpp_windows_x64/modules/
|
||||
|
||||
cp $build_dir/source_modules/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/
|
||||
cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/
|
||||
|
||||
@@ -39,6 +47,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/
|
||||
@@ -64,6 +77,8 @@ cp $build_dir/sink_modules/network_sink/Release/network_sink.dll sdrpp_windows_x
|
||||
|
||||
|
||||
# Copy decoder modules
|
||||
cp $build_dir/decoder_modules/atv_decoder/Release/atv_decoder.dll sdrpp_windows_x64/modules/
|
||||
|
||||
cp $build_dir/decoder_modules/m17_decoder/Release/m17_decoder.dll sdrpp_windows_x64/modules/
|
||||
cp "C:/Program Files/codec2/lib/libcodec2.dll" sdrpp_windows_x64/
|
||||
|
||||
|
||||
@@ -168,10 +168,9 @@ public:
|
||||
writer.setSamplerate(samplerate);
|
||||
|
||||
// Open file
|
||||
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
|
||||
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : "";
|
||||
std::string extension = ".wav";
|
||||
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension);
|
||||
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, recMode, vfoName) + extension);
|
||||
if (!writer.open(expandedPath)) {
|
||||
flog::error("Failed to open file for recording: {0}", expandedPath);
|
||||
return;
|
||||
@@ -249,7 +248,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 +282,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 +295,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();
|
||||
@@ -449,7 +451,7 @@ private:
|
||||
{ RADIO_IFACE_MODE_RAW, "RAW" }
|
||||
};
|
||||
|
||||
std::string genFileName(std::string templ, std::string type, std::string name) {
|
||||
std::string genFileName(std::string templ, int mode, std::string name) {
|
||||
// Get data
|
||||
time_t now = time(0);
|
||||
tm* ltm = localtime(&now);
|
||||
@@ -459,6 +461,9 @@ private:
|
||||
freq += gui::waterfall.vfos[name]->generalOffset;
|
||||
}
|
||||
|
||||
// Select the recording type string
|
||||
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
|
||||
|
||||
// Format to string
|
||||
char freqStr[128];
|
||||
char hourStr[128];
|
||||
@@ -467,7 +472,7 @@ private:
|
||||
char dayStr[128];
|
||||
char monStr[128];
|
||||
char yearStr[128];
|
||||
const char* modeStr = "Unknown";
|
||||
const char* modeStr = (recMode == RECORDER_MODE_AUDIO) ? "Unknown" : "IQ";
|
||||
sprintf(freqStr, "%.0lfHz", freq);
|
||||
sprintf(hourStr, "%02d", ltm->tm_hour);
|
||||
sprintf(minStr, "%02d", ltm->tm_min);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <gui/gui.h>
|
||||
#include <gui/style.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <chrono>
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "scanner",
|
||||
|
||||
33
readme.md
33
readme.md
@@ -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,13 +324,16 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| audio_source | Working | rtaudio | OPT_BUILD_AUDIO_SOURCE | ✅ | ✅ | ✅ |
|
||||
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ✅ (not Debian Buster) | ✅ |
|
||||
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
|
||||
| fobossdr_source | Working | libfobos | OPT_BUILD_FOBOSSDR_SOURCE | ✅ | ✅ | ✅ |
|
||||
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
|
||||
| harogic_source | Beta | htra_api | OPT_BUILD_HAROGIC_SOURCE | ⛔ | ⛔ | ✅ |
|
||||
| hermes_source | Beta | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
|
||||
| kcsdr_source | Unfinished | libkcsdr | OPT_BUILD_KCSDR_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
|
||||
| network_source | Unfinished | - | OPT_BUILD_NETWORK_SOURCE | ✅ | ✅ | ⛔ |
|
||||
| network_source | Beta | - | OPT_BUILD_NETWORK_SOURCE | ✅ | ✅ | ✅ |
|
||||
| perseus_source | Beta | libperseus-sdr | OPT_BUILD_PERSEUS_SOURCE | ⛔ | ✅ | ✅ |
|
||||
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
|
||||
| rfnm_source | Beta | librfnm | OPT_BUILD_RFNM_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| 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 | ✅ | ✅ | ✅ |
|
||||
@@ -339,9 +341,9 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| sdrpp_server_source | Working | - | OPT_BUILD_SDRPP_SERVER_SOURCE | ✅ | ✅ | ✅ |
|
||||
| soapy_source | Deprecated | soapysdr | OPT_BUILD_SOAPY_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| spectran_source | Unfinished | RTSA Suite | OPT_BUILD_SPECTRAN_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| spectran_http_source | Beta | - | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅ | ✅ | ⛔ |
|
||||
| spectran_http_source | Beta | - | OPT_BUILD_SPECTRAN_HTTP_SOURCE | ✅ | ✅ | ✅ |
|
||||
| spyserver_source | Working | - | OPT_BUILD_SPYSERVER_SOURCE | ✅ | ✅ | ✅ |
|
||||
| usrp_source | Beta | libuhd | OPT_BUILD_USRP_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| usrp_source | Beta | libuhd | OPT_BUILD_USRP_SOURCE | ⛔ | ⛔ | ✅ |
|
||||
|
||||
## Sinks
|
||||
|
||||
@@ -350,20 +352,22 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| android_audio_sink | Working | - | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔ | ✅ | ✅ (Android only) |
|
||||
| audio_sink | Working | rtaudio | OPT_BUILD_AUDIO_SINK | ✅ | ✅ | ✅ |
|
||||
| network_sink | Working | - | OPT_BUILD_NETWORK_SINK | ✅ | ✅ | ✅ |
|
||||
| new_portaudio_sink | Beta | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
|
||||
| portaudio_sink | Beta | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
|
||||
| new_portaudio_sink | Working | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
|
||||
| portaudio_sink | Working | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
|
||||
|
||||
## Decoders
|
||||
|
||||
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|
||||
|---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:|
|
||||
| atv_decoder | Unfinished | - | OPT_BUILD_ATV_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| dab_decoder | Unfinished | - | OPT_BUILD_DAB_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| falcon9_decoder | Unfinished | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| m17_decoder | Working | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
|
||||
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
||||
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
||||
| radio | Unfinished | - | OPT_BUILD_VOR_RECEIVER | ⛔ | ⛔ | ⛔ |
|
||||
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
|
||||
## Misc
|
||||
@@ -416,8 +420,8 @@ If you still have an issue, please open an issue about it or ask on the discord.
|
||||
|
||||
# Contributing
|
||||
|
||||
Feel free to submit pull requests and report bugs via the GitHub issue tracker.
|
||||
I will soon publish a contributing.md listing the code style to use.
|
||||
Feel free to submit band plans via the GitHub issue tracker.
|
||||
For code changes, please create a feature request instead.
|
||||
|
||||
# Credits
|
||||
|
||||
@@ -437,15 +441,18 @@ I will soon publish a contributing.md listing the code style to use.
|
||||
* Flinger Films
|
||||
* [Frank Werner (HB9FXQ)](https://twitter.com/HB9FXQ)
|
||||
* gringogrigio
|
||||
* Jandro
|
||||
* Jeff Moe
|
||||
* Joe Cupano
|
||||
* KD1SQ
|
||||
* Kezza
|
||||
* Krys Kamieniecki
|
||||
* Lee Donaghy
|
||||
* Lee KD1SQ
|
||||
* Lee (KD1SQ)
|
||||
* .lozenge. (Hank Hill)
|
||||
* Martin Herren (HB9FXX)
|
||||
* NeoVilsonWong
|
||||
* Nitin (VU2JEK)
|
||||
* ON4MU
|
||||
* [Passion-Radio.com](https://passion-radio.com/)
|
||||
* Paul Maine
|
||||
|
||||
@@ -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",
|
||||
|
||||
231
root/res/bandplans/ireland.json
Normal file
231
root/res/bandplans/ireland.json
Normal file
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"name": "Ireland",
|
||||
"country_name": "Republic Of Ireland",
|
||||
"country_code": "IE",
|
||||
"author_name": "Oskar Dudek",
|
||||
"author_url": "",
|
||||
"bands": [
|
||||
{
|
||||
"name": "2200m Ham Band",
|
||||
"type": "amateur",
|
||||
"start": 135700,
|
||||
"end": 137800
|
||||
},
|
||||
{
|
||||
"name": "Long wave",
|
||||
"type": "broadcast",
|
||||
"start": 148500,
|
||||
"end": 282500
|
||||
},
|
||||
{
|
||||
"name": "AM broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 531000,
|
||||
"end": 1602000
|
||||
},
|
||||
{
|
||||
"name": "160m ham band",
|
||||
"type": "amateur",
|
||||
"start": 1810000,
|
||||
"end": 2000000
|
||||
},
|
||||
{
|
||||
"name": "120m SW broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 2300000,
|
||||
"end": 2495000
|
||||
},
|
||||
{
|
||||
"name": "90m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 3200000,
|
||||
"end": 3400000
|
||||
},
|
||||
{
|
||||
"name": "80m ham band",
|
||||
"type": "amateur",
|
||||
"start": 3500000,
|
||||
"end": 3800000
|
||||
},
|
||||
{
|
||||
"name": "75m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 3900000,
|
||||
"end": 4000000
|
||||
},
|
||||
{
|
||||
"name": "60m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 4750000,
|
||||
"end": 5060000
|
||||
},
|
||||
{
|
||||
"name": "60m ham band",
|
||||
"type": "amateur",
|
||||
"start": 5351500,
|
||||
"end": 5366500
|
||||
},
|
||||
{
|
||||
"name": "49m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 5900000,
|
||||
"end": 6200000
|
||||
},
|
||||
{
|
||||
"name": "40m ham band",
|
||||
"type": "amateur",
|
||||
"start": 7000000,
|
||||
"end": 7200000
|
||||
},
|
||||
{
|
||||
"name": "40m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 7200000,
|
||||
"end": 7450000
|
||||
},
|
||||
{
|
||||
"name": "31m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 9400000,
|
||||
"end": 9900000
|
||||
},
|
||||
{
|
||||
"name": "30m ham band",
|
||||
"type": "amateur",
|
||||
"start": 10100000,
|
||||
"end": 10150000
|
||||
},
|
||||
{
|
||||
"name": "25m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 11600000,
|
||||
"end": 12100000
|
||||
},
|
||||
{
|
||||
"name": "22m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 13570000,
|
||||
"end": 13870000
|
||||
},
|
||||
{
|
||||
"name": "20m ham band",
|
||||
"type": "amateur",
|
||||
"start": 14000000,
|
||||
"end": 14350000
|
||||
},
|
||||
{
|
||||
"name": "19m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 15100000,
|
||||
"end": 15800000
|
||||
},
|
||||
{
|
||||
"name": "17m ham band",
|
||||
"type": "amateur",
|
||||
"start": 18068000,
|
||||
"end": 18168000
|
||||
},
|
||||
{
|
||||
"name": "16m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 17480000,
|
||||
"end": 17900000
|
||||
},
|
||||
{
|
||||
"name": "15m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 18900000,
|
||||
"end": 19020000
|
||||
},
|
||||
{
|
||||
"name": "15m ham band",
|
||||
"type": "amateur",
|
||||
"start": 21000000,
|
||||
"end": 21450000
|
||||
},
|
||||
{
|
||||
"name": "13m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 21450000,
|
||||
"end": 21850000
|
||||
},
|
||||
{
|
||||
"name": "12m ham band",
|
||||
"type": "amateur",
|
||||
"start": 24890000,
|
||||
"end": 24990000
|
||||
},
|
||||
{
|
||||
"name": "11m SW Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 25670000,
|
||||
"end": 26100000
|
||||
},
|
||||
{
|
||||
"name": "CB",
|
||||
"type": "amateur",
|
||||
"start": 26965000,
|
||||
"end": 27405000
|
||||
},
|
||||
{
|
||||
"name": "10m ham band",
|
||||
"type": "amateur",
|
||||
"start": 28000000,
|
||||
"end": 29700000
|
||||
},
|
||||
{
|
||||
"name": "FM Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 87500000,
|
||||
"end": 108000000
|
||||
},
|
||||
{
|
||||
"name": "Airband VOR/ILS",
|
||||
"type": "aviation",
|
||||
"start": 108000000,
|
||||
"end": 117900000
|
||||
},
|
||||
{
|
||||
"name": "Airband Voice",
|
||||
"type": "aviation",
|
||||
"start": 118000000,
|
||||
"end": 137000000
|
||||
},
|
||||
{
|
||||
"name": "Polar orbiting satellites",
|
||||
"type": "satellite",
|
||||
"start": 137000000,
|
||||
"end": 138000000
|
||||
},
|
||||
{
|
||||
"name": "6m ham band",
|
||||
"type": "amateur",
|
||||
"start": 50000000,
|
||||
"end": 52000000
|
||||
},
|
||||
{
|
||||
"name": "4m ham band",
|
||||
"type": "amateur",
|
||||
"start": 70000000,
|
||||
"end": 70500000
|
||||
},
|
||||
{
|
||||
"name": "2m ham band",
|
||||
"type": "amateur",
|
||||
"start": 144000000,
|
||||
"end": 146000000
|
||||
},
|
||||
{
|
||||
"name": "70cm ham band",
|
||||
"type": "amateur",
|
||||
"start": 430000000,
|
||||
"end": 440000000
|
||||
},
|
||||
{
|
||||
"name": "ADS-B",
|
||||
"type": "aviation",
|
||||
"start": 1089000000,
|
||||
"end": 1091000000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
"name": "Radionavigazione",
|
||||
"type": "marine",
|
||||
"start": 11300,
|
||||
"end": 135500
|
||||
"end": 148500
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 137kHz",
|
||||
@@ -47,7 +47,7 @@
|
||||
"end": 520000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 472 kHz",
|
||||
"name": "Radioamatori 472kHz",
|
||||
"type": "amateur",
|
||||
"start": 472000,
|
||||
"end": 479000
|
||||
@@ -65,7 +65,7 @@
|
||||
"end": 1830000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 160 m",
|
||||
"name": "Radioamatori 160m",
|
||||
"type": "amateur",
|
||||
"start": 1830000,
|
||||
"end": 1850000
|
||||
@@ -77,7 +77,7 @@
|
||||
"end": 2300000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 120 m",
|
||||
"name": "Radiodiffusione OC 120m",
|
||||
"type": "broadcast",
|
||||
"start": 2300000,
|
||||
"end": 2500000
|
||||
@@ -107,7 +107,7 @@
|
||||
"end": 3200000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 90 m",
|
||||
"name": "Radiodiffusione OC 90m",
|
||||
"type": "broadcast",
|
||||
"start": 3200000,
|
||||
"end": 3400000
|
||||
@@ -125,13 +125,13 @@
|
||||
"end": 3600000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 80 m",
|
||||
"name": "Radioamatori 80m",
|
||||
"type": "amateur",
|
||||
"start": 3600000,
|
||||
"start": 3500000,
|
||||
"end": 3800000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 75 m",
|
||||
"name": "Radiodiffusione OC 75m",
|
||||
"type": "broadcast",
|
||||
"start": 3900000,
|
||||
"end": 4000000
|
||||
@@ -150,19 +150,19 @@
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Radiodiffusione OC 60 m",
|
||||
"name": "Radiodiffusione OC 60m",
|
||||
"type": "broadcast",
|
||||
"start": 4750000,
|
||||
"end": 4995000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 60 m",
|
||||
"name": "Radiodiffusione OC 60m",
|
||||
"type": "broadcast",
|
||||
"start": 5005000,
|
||||
"end": 5060000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 60 m",
|
||||
"name": "Radioamatori 60m",
|
||||
"type": "amateur",
|
||||
"start": 5351500,
|
||||
"end": 5366500
|
||||
@@ -174,7 +174,7 @@
|
||||
"end": 5730000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 49 m",
|
||||
"name": "Radiodiffusione OC 49m",
|
||||
"type": "broadcast",
|
||||
"start": 5900000,
|
||||
"end": 6200000
|
||||
@@ -192,13 +192,13 @@
|
||||
"end": 6765000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 40 m",
|
||||
"name": "Radioamatori 40m",
|
||||
"type": "amateur",
|
||||
"start": 7000000,
|
||||
"end": 7200000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 41 m",
|
||||
"name": "Radiodiffusione OC 41m",
|
||||
"type": "broadcast",
|
||||
"start": 7200000,
|
||||
"end": 7450000
|
||||
@@ -216,7 +216,7 @@
|
||||
"end": 9040000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 31 m",
|
||||
"name": "Radiodiffusione OC 31m",
|
||||
"type": "broadcast",
|
||||
"start": 9400000,
|
||||
"end": 9900000
|
||||
@@ -228,7 +228,7 @@
|
||||
"end": 10100000
|
||||
},
|
||||
{
|
||||
"name": "30m - Radioamateur",
|
||||
"name": "Radioamatori 30m",
|
||||
"type": "amateur",
|
||||
"start": 10100000,
|
||||
"end": 10150000
|
||||
@@ -282,7 +282,7 @@
|
||||
"end": 15100000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 19 m",
|
||||
"name": "Radiodiffusione OC 19m",
|
||||
"type": "broadcast",
|
||||
"start": 15100000,
|
||||
"end": 15800000
|
||||
@@ -294,7 +294,7 @@
|
||||
"end": 17410000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 16 m",
|
||||
"name": "Radiodiffusione OC 16m",
|
||||
"type": "broadcast",
|
||||
"start": 17480000,
|
||||
"end": 17900000
|
||||
@@ -306,9 +306,9 @@
|
||||
"end": 18030000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 17 m",
|
||||
"name": "Radioamatori 17m",
|
||||
"type": "amateur",
|
||||
"start": 18068000,
|
||||
"start": 18069000,
|
||||
"end": 18168000
|
||||
},
|
||||
{
|
||||
@@ -318,7 +318,7 @@
|
||||
"end": 18900000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 15 m",
|
||||
"name": "Radiodiffusione OC 15m",
|
||||
"type": "broadcast",
|
||||
"start": 18900000,
|
||||
"end": 19020000
|
||||
@@ -336,13 +336,13 @@
|
||||
"end": 20010000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 15 m",
|
||||
"name": "Radioamatori 15m",
|
||||
"type": "amateur",
|
||||
"start": 21000000,
|
||||
"end": 21450000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 13 m",
|
||||
"name": "Radiodiffusione OC 13m",
|
||||
"type": "broadcast",
|
||||
"start": 21450000,
|
||||
"end": 21850000
|
||||
@@ -366,7 +366,7 @@
|
||||
"end": 23350000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 12 m",
|
||||
"name": "Radioamatori 12m",
|
||||
"type": "amateur",
|
||||
"start": 24890000,
|
||||
"end": 24990000
|
||||
@@ -390,7 +390,7 @@
|
||||
"end": 25670000
|
||||
},
|
||||
{
|
||||
"name": "Radiodiffusione OC 11 m",
|
||||
"name": "Radiodiffusione OC 11m",
|
||||
"type": "broadcast",
|
||||
"start": 25670000,
|
||||
"end": 26100000
|
||||
@@ -408,7 +408,7 @@
|
||||
"end": 27230000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 10 m",
|
||||
"name": "Radioamatori 10m",
|
||||
"type": "amateur",
|
||||
"start": 28000000,
|
||||
"end": 29700000
|
||||
@@ -420,10 +420,10 @@
|
||||
"end": 47000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 9 m",
|
||||
"name": "Radioamatori 6m",
|
||||
"type": "amateur",
|
||||
"start": 47000000,
|
||||
"end": 52500000
|
||||
"start": 50000000,
|
||||
"end": 51000000
|
||||
},
|
||||
{
|
||||
"name": "Wind profiler",
|
||||
@@ -438,7 +438,7 @@
|
||||
"end": 74800000
|
||||
},
|
||||
{
|
||||
"name": "Radiofari 75 MHz",
|
||||
"name": "Radiofari 75MHz",
|
||||
"type": "aviation",
|
||||
"start": 74800000,
|
||||
"end": 75200000
|
||||
@@ -446,19 +446,19 @@
|
||||
{
|
||||
"name": "Radiodiffusione FM",
|
||||
"type": "broadcast",
|
||||
"start": 80000000,
|
||||
"start": 87500000,
|
||||
"end": 108000000
|
||||
},
|
||||
{
|
||||
"name": "VOR/ILS",
|
||||
"type": "aviation",
|
||||
"start": 108000000,
|
||||
"end": 118000000
|
||||
"end": 117975000
|
||||
},
|
||||
{
|
||||
"name": "Mobile aeronautico",
|
||||
"type": "aviation",
|
||||
"start": 118000000,
|
||||
"start": 117975000,
|
||||
"end": 137000000
|
||||
},
|
||||
{
|
||||
@@ -468,10 +468,10 @@
|
||||
"end": 138000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 2 m",
|
||||
"name": "Radioamatori 2m",
|
||||
"type": "amateur",
|
||||
"start": 144000000,
|
||||
"end": 148000000
|
||||
"end": 146000000
|
||||
},
|
||||
{
|
||||
"name": "Telefonia satellitare",
|
||||
@@ -552,7 +552,7 @@
|
||||
"end": 430000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 70 cm",
|
||||
"name": "Radioamatori 70cm",
|
||||
"type": "amateur",
|
||||
"start": 430000000,
|
||||
"end": 434000000
|
||||
@@ -564,10 +564,10 @@
|
||||
"end": 435000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 70 cm",
|
||||
"name": "Radioamatori 70cm",
|
||||
"type": "amateur",
|
||||
"start": 435000000,
|
||||
"end": 436000000
|
||||
"end": 438000000
|
||||
},
|
||||
{
|
||||
"name": "Mobile o fisso privato",
|
||||
@@ -642,10 +642,16 @@
|
||||
"end": 124000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 23 cm",
|
||||
"name": "Radioamatori 23cm",
|
||||
"type": "amateur",
|
||||
"start": 1240000000,
|
||||
"end": 1270000000
|
||||
"end": 1245000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 23cm",
|
||||
"type": "amateur",
|
||||
"start": 1267000000,
|
||||
"end": 1298000000
|
||||
},
|
||||
{
|
||||
"name": "Wind profiler",
|
||||
@@ -792,10 +798,10 @@
|
||||
"end": 2300000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 13 cm",
|
||||
"name": "Radioamatori 13cm",
|
||||
"type": "amateur",
|
||||
"start": 2300000000,
|
||||
"end": 2400000000
|
||||
"end": 2450000000
|
||||
},
|
||||
{
|
||||
"name": "ISM, SAP/SAB, 802.11",
|
||||
@@ -827,12 +833,6 @@
|
||||
"start": 2900000000,
|
||||
"end": 3400000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 9 cm",
|
||||
"type": "amateur",
|
||||
"start": 3400000000,
|
||||
"end": 3475000000
|
||||
},
|
||||
{
|
||||
"name": "Reti numeriche",
|
||||
"type": "comms",
|
||||
@@ -858,9 +858,21 @@
|
||||
"end": 5650000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 6 cm",
|
||||
"name": "Radioamatori 5cm",
|
||||
"type": "amateur",
|
||||
"start": 5650000000,
|
||||
"end": 5670000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 5cm",
|
||||
"type": "amateur",
|
||||
"start": 5760000000,
|
||||
"end": 5770000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 5cm",
|
||||
"type": "amateur",
|
||||
"start": 5830000000,
|
||||
"end": 5850000000
|
||||
},
|
||||
{
|
||||
@@ -912,9 +924,9 @@
|
||||
"end": 10000000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 3 cm",
|
||||
"name": "Radioamatori 3cm",
|
||||
"type": "amateur",
|
||||
"start": 10000000000,
|
||||
"start": 10300000000,
|
||||
"end": 10500000000
|
||||
},
|
||||
{
|
||||
@@ -1001,10 +1013,16 @@
|
||||
"start": 23150000000,
|
||||
"end": 23338000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 1,5cm",
|
||||
"type": "amateur",
|
||||
"start": 24000000000,
|
||||
"end": 24050000000
|
||||
},
|
||||
{
|
||||
"name": "ISM, SRD e LPR",
|
||||
"type": "ism",
|
||||
"start": 24000000000,
|
||||
"start": 24050000000,
|
||||
"end": 24450000000
|
||||
},
|
||||
{
|
||||
@@ -1086,7 +1104,7 @@
|
||||
"end": 43500000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 6 mm",
|
||||
"name": "Radioamatori 7mm",
|
||||
"type": "amateur",
|
||||
"start": 47000000000,
|
||||
"end": 47200000000
|
||||
@@ -1128,9 +1146,9 @@
|
||||
"end": 76500000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 4 mm",
|
||||
"name": "Radioamatori 4mm",
|
||||
"type": "amateur",
|
||||
"start": 76500000000,
|
||||
"start": 75500000000,
|
||||
"end": 81500000000
|
||||
},
|
||||
{
|
||||
@@ -1146,19 +1164,25 @@
|
||||
"end": 122250000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 2,5 mm",
|
||||
"name": "Radioamatori 2,4mm",
|
||||
"type": "amateur",
|
||||
"start": 122250000000,
|
||||
"start": 122500000000,
|
||||
"end": 123000000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 2 mm",
|
||||
"name": "Radioamatori 2,23mm",
|
||||
"type": "amateur",
|
||||
"start": 134000000000,
|
||||
"end": 141000000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 1 mm",
|
||||
"name": "Radioamatori 2,1mm",
|
||||
"type": "amateur",
|
||||
"start": 142000000000,
|
||||
"end": 144000000000
|
||||
},
|
||||
{
|
||||
"name": "Radioamatori 1mm",
|
||||
"type": "amateur",
|
||||
"start": 241000000000,
|
||||
"end": 250000000000
|
||||
|
||||
549
root/res/bandplans/republic-of-korea.json
Normal file
549
root/res/bandplans/republic-of-korea.json
Normal file
@@ -0,0 +1,549 @@
|
||||
{
|
||||
"name": "Republic of Korea",
|
||||
"country_name": "Republic of Korea",
|
||||
"country_code": "KR",
|
||||
"author_name": "SeoyeonBae",
|
||||
"author_url": "https://github.com/bsy0317",
|
||||
"bands": [
|
||||
{
|
||||
"name": "Radio Navigation",
|
||||
"type": "aviation",
|
||||
"start": 8300,
|
||||
"end": 14000
|
||||
},
|
||||
{
|
||||
"name": "Coastal Telegraph",
|
||||
"type": "marine",
|
||||
"start": 14000,
|
||||
"end": 19950
|
||||
},
|
||||
{
|
||||
"name": "Standard Frequency Time Signal",
|
||||
"type": "utility",
|
||||
"start": 19950,
|
||||
"end": 20250
|
||||
},
|
||||
{
|
||||
"name": "Coastal Telegraph",
|
||||
"type": "marine",
|
||||
"start": 20250,
|
||||
"end": 70000
|
||||
},
|
||||
{
|
||||
"name": "Radio Navigation",
|
||||
"type": "navigation",
|
||||
"start": 70000,
|
||||
"end": 160000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Radio Navigation",
|
||||
"type": "aviation",
|
||||
"start": 160000,
|
||||
"end": 285000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Maritime Radiobeacon",
|
||||
"type": "aviation",
|
||||
"start": 285000,
|
||||
"end": 325000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Radio Navigation",
|
||||
"type": "aviation",
|
||||
"start": 325000,
|
||||
"end": 472000
|
||||
},
|
||||
{
|
||||
"name": "Amateur",
|
||||
"type": "amateur",
|
||||
"start": 472000,
|
||||
"end": 479000
|
||||
},
|
||||
{
|
||||
"name": "International Distress Safety Call",
|
||||
"type": "marine",
|
||||
"start": 479000,
|
||||
"end": 505000
|
||||
},
|
||||
{
|
||||
"name": "Maritime Telegraph",
|
||||
"type": "marine",
|
||||
"start": 505000,
|
||||
"end": 526500
|
||||
},
|
||||
{
|
||||
"name": "Standard Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 526500,
|
||||
"end": 1606500
|
||||
},
|
||||
{
|
||||
"name": "Radiobuoy",
|
||||
"type": "navigation",
|
||||
"start": 1606500,
|
||||
"end": 1800000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 1800000,
|
||||
"end": 1825000
|
||||
},
|
||||
{
|
||||
"name": "Radiobuoy Control LORAN",
|
||||
"type": "radiolocation",
|
||||
"start": 1825000,
|
||||
"end": 2000000
|
||||
},
|
||||
{
|
||||
"name": "Radiobuoy",
|
||||
"type": "fixed",
|
||||
"start": 2000000,
|
||||
"end": 2065000
|
||||
},
|
||||
{
|
||||
"name": "Distress Call",
|
||||
"type": "marine",
|
||||
"start": 2065000,
|
||||
"end": 2107000
|
||||
},
|
||||
{
|
||||
"name": "International Distress Search and Rescue",
|
||||
"type": "mobile",
|
||||
"start": 2173500,
|
||||
"end": 2190500
|
||||
},
|
||||
{
|
||||
"name": "Road Management",
|
||||
"type": "fixed",
|
||||
"start": 2194000,
|
||||
"end": 2495000
|
||||
},
|
||||
{
|
||||
"name": "Standard Frequency Time Signal",
|
||||
"type": "utility",
|
||||
"start": 2495000,
|
||||
"end": 2505000
|
||||
},
|
||||
{
|
||||
"name": "Ship Station Telephone",
|
||||
"type": "fixed",
|
||||
"start": 2505000,
|
||||
"end": 2850000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Mobile R",
|
||||
"type": "aviation",
|
||||
"start": 2850000,
|
||||
"end": 3025000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Mobile OR",
|
||||
"type": "aviation",
|
||||
"start": 3025000,
|
||||
"end": 3155000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Mobile R",
|
||||
"type": "aviation",
|
||||
"start": 3400000,
|
||||
"end": 3500000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 3500000,
|
||||
"end": 3550000
|
||||
},
|
||||
{
|
||||
"name": "Experimental Station",
|
||||
"type": "fixed",
|
||||
"start": 3550000,
|
||||
"end": 3790000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 3790000,
|
||||
"end": 3800000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 3900000,
|
||||
"end": 3950000
|
||||
},
|
||||
{
|
||||
"name": "Standard Frequency Time Signal",
|
||||
"type": "utility",
|
||||
"start": 3995000,
|
||||
"end": 4005000
|
||||
},
|
||||
{
|
||||
"name": "Ship Station Telephone",
|
||||
"type": "marine",
|
||||
"start": 4005000,
|
||||
"end": 4063000
|
||||
},
|
||||
{
|
||||
"name": "Oceanographic Data",
|
||||
"type": "marine",
|
||||
"start": 4063000,
|
||||
"end": 4065000
|
||||
},
|
||||
{
|
||||
"name": "Ship Station Duplex Telephone",
|
||||
"type": "marine",
|
||||
"start": 4065000,
|
||||
"end": 4146000
|
||||
},
|
||||
{
|
||||
"name": "Ship Station Simplex Telephone",
|
||||
"type": "marine",
|
||||
"start": 4146000,
|
||||
"end": 4152000
|
||||
},
|
||||
{
|
||||
"name": "Ship Station Wideband Telegraph Fax",
|
||||
"type": "marine",
|
||||
"start": 4152000,
|
||||
"end": 4172000
|
||||
},
|
||||
{
|
||||
"name": "Ship Station Narrowband",
|
||||
"type": "marine",
|
||||
"start": 4172000,
|
||||
"end": 4181750
|
||||
},
|
||||
{
|
||||
"name": "Ship Station A1A Morse Code Communication",
|
||||
"type": "marine",
|
||||
"start": 4186750,
|
||||
"end": 4202250
|
||||
},
|
||||
{
|
||||
"name": "Radiolocation",
|
||||
"type": "radiolocation",
|
||||
"start": 4438000,
|
||||
"end": 4488000
|
||||
},
|
||||
{
|
||||
"name": "Calling Response",
|
||||
"type": "fixed",
|
||||
"start": 4488000,
|
||||
"end": 4650000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Mobile R",
|
||||
"type": "aviation",
|
||||
"start": 4650000,
|
||||
"end": 4850000
|
||||
},
|
||||
{
|
||||
"name": "Standard Frequency Time Signal",
|
||||
"type": "utility",
|
||||
"start": 4995000,
|
||||
"end": 5005000
|
||||
},
|
||||
{
|
||||
"name": "Search Rescue",
|
||||
"type": "aviation",
|
||||
"start": 5480000,
|
||||
"end": 5730000
|
||||
},
|
||||
{
|
||||
"name": "Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 5900000,
|
||||
"end": 5950000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 5950000,
|
||||
"end": 6200000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 7000000,
|
||||
"end": 7100000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 7100000,
|
||||
"end": 7200000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 7200000,
|
||||
"end": 7450000
|
||||
},
|
||||
{
|
||||
"name": "Standard Frequency Time Signal",
|
||||
"type": "utility",
|
||||
"start": 7995000,
|
||||
"end": 8005000
|
||||
},
|
||||
{
|
||||
"name": "Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 9400000,
|
||||
"end": 9500000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 9500000,
|
||||
"end": 9900000
|
||||
},
|
||||
{
|
||||
"name": "Standard Frequency Time Signal",
|
||||
"type": "utility",
|
||||
"start": 9995000,
|
||||
"end": 10005000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 10100000,
|
||||
"end": 10150000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Mobile",
|
||||
"type": "aviation",
|
||||
"start": 10150000,
|
||||
"end": 11600000
|
||||
},
|
||||
{
|
||||
"name": "Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 11600000,
|
||||
"end": 11650000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 11650000,
|
||||
"end": 12050000
|
||||
},
|
||||
{
|
||||
"name": "Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 12050000,
|
||||
"end": 12100000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Mobile",
|
||||
"type": "aviation",
|
||||
"start": 13260000,
|
||||
"end": 13360000
|
||||
},
|
||||
{
|
||||
"name": "Radio Astronomy",
|
||||
"type": "astronomy",
|
||||
"start": 13360000,
|
||||
"end": 13410000
|
||||
},
|
||||
{
|
||||
"name": "Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 13570000,
|
||||
"end": 13600000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 13600000,
|
||||
"end": 13800000
|
||||
},
|
||||
{
|
||||
"name": "Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 13800000,
|
||||
"end": 13870000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 14000000,
|
||||
"end": 14350000
|
||||
},
|
||||
{
|
||||
"name": "Aviation Mobile",
|
||||
"type": "aviation",
|
||||
"start": 15010000,
|
||||
"end": 15100000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 15100000,
|
||||
"end": 15600000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 15600000,
|
||||
"end": 15800000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 15800000,
|
||||
"end": 15995000
|
||||
},
|
||||
{
|
||||
"name": "Standard Frequency Time Signal",
|
||||
"type": "utility",
|
||||
"start": 15995000,
|
||||
"end": 16005000
|
||||
},
|
||||
{
|
||||
"name": "Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 18900000,
|
||||
"end": 19020000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 21000000,
|
||||
"end": 21450000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 21450000,
|
||||
"end": 21850000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 24890000,
|
||||
"end": 24990000
|
||||
},
|
||||
{
|
||||
"name": "Shortwave Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 25670000,
|
||||
"end": 26100000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 28000000,
|
||||
"end": 29700000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 50000000,
|
||||
"end": 54000000
|
||||
},
|
||||
{
|
||||
"name": "TV Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 54000000,
|
||||
"end": 72000000
|
||||
},
|
||||
{
|
||||
"name": "Flood Warning",
|
||||
"type": "broadcast",
|
||||
"start": 72000000,
|
||||
"end": 74800000
|
||||
},
|
||||
{
|
||||
"name": "TV Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 76000000,
|
||||
"end": 88000000
|
||||
},
|
||||
{
|
||||
"name": "FM Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 88000000,
|
||||
"end": 100000000
|
||||
},
|
||||
{
|
||||
"name": "FM Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 100000000,
|
||||
"end": 108000000
|
||||
},
|
||||
{
|
||||
"name": "ILS Localizer VOR",
|
||||
"type": "fixed",
|
||||
"start": 108000000,
|
||||
"end": 117975000
|
||||
},
|
||||
{
|
||||
"name": "Amateur Station",
|
||||
"type": "amateur",
|
||||
"start": 144000000,
|
||||
"end": 146000000
|
||||
},
|
||||
{
|
||||
"name": "General Communication",
|
||||
"type": "fixed",
|
||||
"start": 146000000,
|
||||
"end": 148000000
|
||||
},
|
||||
{
|
||||
"name": "Low Power Device",
|
||||
"type": "fixed",
|
||||
"start": 162037500,
|
||||
"end": 174000000
|
||||
},
|
||||
{
|
||||
"name": "TV Broadcast",
|
||||
"type": "broadcast",
|
||||
"start": 174000000,
|
||||
"end": 216000000
|
||||
},
|
||||
{
|
||||
"name": "Low Power Device",
|
||||
"type": "fixed",
|
||||
"start": 216000000,
|
||||
"end": 230000000
|
||||
},
|
||||
{
|
||||
"name": "Low Power Device",
|
||||
"type": "fixed",
|
||||
"start": 273000000,
|
||||
"end": 322000000
|
||||
},
|
||||
{
|
||||
"name": "Personal Radio",
|
||||
"type": "fixed",
|
||||
"start": 420000000,
|
||||
"end": 470000000
|
||||
},
|
||||
{
|
||||
"name": "Public Network",
|
||||
"type": "broadcast",
|
||||
"start": 698000000,
|
||||
"end": 806000000
|
||||
},
|
||||
{
|
||||
"name": "Low Power Device",
|
||||
"type": "fixed",
|
||||
"start": 942000000,
|
||||
"end": 960000000
|
||||
},
|
||||
{
|
||||
"name": "Satellite Mobile Communication",
|
||||
"type": "fixed",
|
||||
"start": 15250000000,
|
||||
"end": 16605000000
|
||||
},
|
||||
{
|
||||
"name": "Mobile Communication",
|
||||
"type": "mobile",
|
||||
"start": 25000000000,
|
||||
"end": 37000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
285
root/res/bandplans/turkey.json
Normal file
285
root/res/bandplans/turkey.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -217,14 +217,19 @@ private:
|
||||
}
|
||||
|
||||
void startServer() {
|
||||
if (modeId == SINK_MODE_TCP) {
|
||||
listener = net::listen(hostname, port);
|
||||
if (listener) {
|
||||
listener->acceptAsync(clientHandler, this);
|
||||
try {
|
||||
if (modeId == SINK_MODE_TCP) {
|
||||
listener = net::listen(hostname, port);
|
||||
if (listener) {
|
||||
listener->acceptAsync(clientHandler, this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
conn = net::openUDP("0.0.0.0", port, hostname, port, false);
|
||||
catch (const std::exception& e) {
|
||||
flog::error("Failed to open socket: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,6 +183,9 @@ private:
|
||||
static void start(void* ctx) {
|
||||
AudioSourceModule* _this = (AudioSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
|
||||
// If no device is selected, give up
|
||||
if (_this->selectedDevice.empty()) { return; }
|
||||
|
||||
// Stream options
|
||||
RtAudio::StreamParameters parameters;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <libbladeRF.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <algorithm>
|
||||
#include <utils/optionlist.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
@@ -37,6 +38,10 @@ public:
|
||||
BladeRFSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
// Define clocks
|
||||
clocks.define("onboard", "On-Board", CLOCK_SELECT_ONBOARD);
|
||||
clocks.define("external", "External", CLOCK_SELECT_EXTERNAL);
|
||||
|
||||
sampleRate = 1000000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
@@ -267,6 +272,15 @@ public:
|
||||
}
|
||||
config.release(true);
|
||||
|
||||
// Load clock source
|
||||
clkId = clocks.keyId("onboard");
|
||||
if (config.conf["devices"][selectedSerial].contains("clock")) {
|
||||
std::string clkStr = config.conf["devices"][selectedSerial]["clock"];
|
||||
if (clocks.keyExists(clkStr)) {
|
||||
clkId = clocks.keyId(clkStr);
|
||||
}
|
||||
}
|
||||
|
||||
// Load gain mode
|
||||
if (config.conf["devices"][selectedSerial].contains("gainMode")) {
|
||||
std::string gm = config.conf["devices"][selectedSerial]["gainMode"];
|
||||
@@ -364,6 +378,7 @@ private:
|
||||
if (_this->bufferSize < 1024) { _this->bufferSize = 1024; }
|
||||
|
||||
// Setup device parameters
|
||||
_this->setClockSource(_this->clocks[_this->clkId]);
|
||||
bladerf_set_sample_rate(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->sampleRate, NULL);
|
||||
bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq);
|
||||
bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ? std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL);
|
||||
@@ -486,6 +501,19 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Clock Source");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_balderf_clk_sel_", _this->name), &_this->clkId, _this->clocks.txt)) {
|
||||
if (_this->running) {
|
||||
_this->setClockSource(_this->clocks[_this->clkId]);
|
||||
}
|
||||
if (_this->selectedSerial != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["clock"] = _this->clocks.key(_this->clkId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
// General config BS
|
||||
SmGui::LeftLabel("Gain control mode");
|
||||
SmGui::FillWidth();
|
||||
@@ -537,6 +565,15 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void setClockSource(bladerf_clock_select clk) {
|
||||
if (selectedBladeType == BLADERF_TYPE_V1) {
|
||||
bladerf_set_smb_mode(openDev, (clk == CLOCK_SELECT_EXTERNAL) ? BLADERF_SMB_MODE_INPUT : BLADERF_SMB_MODE_DISABLED);
|
||||
}
|
||||
else {
|
||||
bladerf_set_clock_select(openDev, clk);
|
||||
}
|
||||
}
|
||||
|
||||
void worker() {
|
||||
int16_t* buffer = new int16_t[bufferSize * 2];
|
||||
bladerf_metadata meta;
|
||||
@@ -565,6 +602,7 @@ private:
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int bwId = 0;
|
||||
int clkId = 0;
|
||||
int chanId = 0;
|
||||
int gainMode = 0;
|
||||
bool streamingEnabled = false;
|
||||
@@ -580,8 +618,8 @@ private:
|
||||
std::string sampleRatesTxt;
|
||||
std::vector<uint64_t> bandwidths;
|
||||
std::string bandwidthsTxt;
|
||||
|
||||
std::string channelNamesTxt;
|
||||
OptionList<std::string, bladerf_clock_select> clocks;
|
||||
|
||||
int bufferSize;
|
||||
struct bladerf_stream* rxStream;
|
||||
|
||||
21
source_modules/fobossdr_source/CMakeLists.txt
Normal file
21
source_modules/fobossdr_source/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(fobossdr_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/lib/")
|
||||
target_include_directories(fobossdr_source PRIVATE "C:/Program Files/RigExpert/Fobos/include/")
|
||||
target_link_libraries(fobossdr_source PRIVATE fobos)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBFOBOS REQUIRED libfobos)
|
||||
|
||||
target_include_directories(fobossdr_source PRIVATE ${LIBFOBOS_INCLUDE_DIRS})
|
||||
target_link_directories(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARY_DIRS})
|
||||
target_link_libraries(fobossdr_source PRIVATE ${LIBFOBOS_LIBRARIES})
|
||||
endif ()
|
||||
560
source_modules/fobossdr_source/src/main.cpp
Normal file
560
source_modules/fobossdr_source/src/main.cpp
Normal file
@@ -0,0 +1,560 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <atomic>
|
||||
#include <fobos.h>
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "fobossdr_source",
|
||||
/* Description: */ "FobosSDR Source Module",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
// Work around for the fobos API not including
|
||||
#define FOBOS_LNA_GAIN_MIN 1
|
||||
#define FOBOS_LNA_GAIN_MAX 3
|
||||
#define FOBOS_VGA_GAIN_MIN 0
|
||||
#define FOBOS_VGA_GAIN_MAX 31
|
||||
|
||||
class FobosSDRSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
FobosSDRSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 50000000.0;
|
||||
|
||||
// Initialize the DDC
|
||||
ddc.init(&ddcIn, 50e6, 50e6, 50e6, 0.0);
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &ddc.out;
|
||||
|
||||
// Refresh devices
|
||||
refresh();
|
||||
|
||||
// Select device from config
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
select(devSerial);
|
||||
|
||||
sigpath::sourceManager.registerSource("FobosSDR", &handler);
|
||||
}
|
||||
|
||||
~FobosSDRSourceModule() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
enum Port {
|
||||
PORT_RF,
|
||||
PORT_HF1,
|
||||
PORT_HF2
|
||||
};
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
devices.clear();
|
||||
|
||||
// Get device list
|
||||
char serials[1024];
|
||||
memset(serials, 0, sizeof(serials));
|
||||
int devCount = fobos_rx_list_devices(serials);
|
||||
if (devCount < 0) {
|
||||
flog::error("Failed to get device list: {}", devCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// If no device, give up
|
||||
if (!devCount) { return; }
|
||||
|
||||
// Generate device entries
|
||||
const char* _serials = serials;
|
||||
int index = 0;
|
||||
while (*_serials) {
|
||||
// Read serial until space
|
||||
std::string serial = "";
|
||||
while (*_serials) {
|
||||
// Get a character
|
||||
char c = *(_serials++);
|
||||
|
||||
// If it's a space, we're done
|
||||
if (c == ' ') { break; }
|
||||
|
||||
// Otherwise, add it to the string
|
||||
serial += c;
|
||||
}
|
||||
|
||||
// Create entry
|
||||
devices.define(serial, serial, index++);
|
||||
}
|
||||
}
|
||||
|
||||
void select(const std::string& serial) {
|
||||
// If there are no devices, give up
|
||||
if (devices.empty()) {
|
||||
selectedSerial.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the serial was not found, select the first available serial
|
||||
if (!devices.keyExists(serial)) {
|
||||
select(devices.key(0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the ID in the list
|
||||
int id = devices.keyId(serial);
|
||||
selectedDevId = devices[id];
|
||||
|
||||
// Open the device
|
||||
fobos_dev_t* dev;
|
||||
int err = fobos_rx_open(&dev, selectedDevId);
|
||||
if (err) {
|
||||
flog::error("Failed to open device: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a list of supported samplerates
|
||||
double srList[128];
|
||||
unsigned int srCount;
|
||||
err = fobos_rx_get_samplerates(dev, srList, &srCount);
|
||||
if (err) {
|
||||
flog::error("Failed to get samplerate list: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate samplerate list
|
||||
samplerates.clear();
|
||||
for (int i = 0; i < srCount; i++) {
|
||||
std::string str = getBandwdithScaled(srList[i]);
|
||||
samplerates.define(srList[i], str, srList[i]);
|
||||
}
|
||||
|
||||
// Add some custom samplerates
|
||||
samplerates.define(5e6, "5.0MHz", 5e6);
|
||||
samplerates.define(2.5e6, "2.5MHz", 2.5e6);
|
||||
samplerates.define(1.25e6, "1.25MHz", 1.25e6);
|
||||
|
||||
// Define the ports
|
||||
ports.clear();
|
||||
ports.define("rf", "RF", PORT_RF);
|
||||
ports.define("hf1", "HF1", PORT_HF1);
|
||||
ports.define("hf2", "HF2", PORT_HF2);
|
||||
|
||||
// Define clock sources
|
||||
clockSources.clear();
|
||||
clockSources.define("internal", "Internal", 0);
|
||||
clockSources.define("external", "External", 1);
|
||||
|
||||
// Close the device
|
||||
fobos_rx_close(dev);
|
||||
|
||||
// Save serial number
|
||||
selectedSerial = serial;
|
||||
devId = id;
|
||||
|
||||
// Load default options
|
||||
sampleRate = 50e6;
|
||||
srId = samplerates.valueId(sampleRate);
|
||||
port = PORT_RF;
|
||||
portId = ports.valueId(port);
|
||||
clkSrcId = clockSources.nameId("Internal");
|
||||
lnaGain = 0;
|
||||
vgaGain = 0;
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
if (config.conf["devices"][selectedSerial].contains("samplerate")) {
|
||||
int desiredSr = config.conf["devices"][selectedSerial]["samplerate"];
|
||||
if (samplerates.keyExists(desiredSr)) {
|
||||
srId = samplerates.keyId(desiredSr);
|
||||
sampleRate = samplerates[srId];
|
||||
}
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("port")) {
|
||||
std::string desiredPort = config.conf["devices"][selectedSerial]["port"];
|
||||
if (ports.keyExists(desiredPort)) {
|
||||
portId = ports.keyId(desiredPort);
|
||||
port = ports[portId];
|
||||
}
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("clkSrc")) {
|
||||
std::string desiredClkSrc = config.conf["devices"][selectedSerial]["clkSrc"];
|
||||
if (clockSources.keyExists(desiredClkSrc)) {
|
||||
clkSrcId = clockSources.keyId(desiredClkSrc);
|
||||
}
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("lnaGain")) {
|
||||
lnaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["lnaGain"], FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX);
|
||||
}
|
||||
if (config.conf["devices"][selectedSerial].contains("vgaGain")) {
|
||||
vgaGain = std::clamp<int>(config.conf["devices"][selectedSerial]["vgaGain"], FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX);
|
||||
}
|
||||
config.release();
|
||||
|
||||
// Update the samplerate
|
||||
core::setInputSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
flog::info("FobosSDRSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
flog::info("FobosSDRSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
|
||||
// Open the device
|
||||
int err = fobos_rx_open(&_this->openDev, _this->selectedDevId);
|
||||
if (err) {
|
||||
flog::error("Failed to open device: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the selected port
|
||||
_this->port = _this->ports[_this->portId];
|
||||
|
||||
// Configure the device
|
||||
double actualSr, actualFreq;
|
||||
fobos_rx_set_samplerate(_this->openDev, (_this->sampleRate >= 50e6) ? _this->sampleRate : 50e6, &actualSr);
|
||||
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
|
||||
fobos_rx_set_direct_sampling(_this->openDev, _this->port != PORT_RF);
|
||||
fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]);
|
||||
fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
|
||||
// Configure the DDC
|
||||
if (_this->port == PORT_RF && _this->sampleRate >= 50e6) {
|
||||
// Set the frequency
|
||||
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
|
||||
}
|
||||
else if (_this->port == PORT_RF) {
|
||||
// Set the frequency
|
||||
fobos_rx_set_frequency(_this->openDev, _this->freq, &actualFreq);
|
||||
|
||||
// Configure and start the DDC for decimation only
|
||||
_this->ddc.setInSamplerate(actualSr);
|
||||
_this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate);
|
||||
_this->ddc.setOffset(0.0);
|
||||
_this->ddc.start();
|
||||
}
|
||||
else {
|
||||
// Configure and start the DDC
|
||||
_this->ddc.setInSamplerate(actualSr);
|
||||
_this->ddc.setOutSamplerate(_this->sampleRate, _this->sampleRate);
|
||||
_this->ddc.setOffset(_this->freq);
|
||||
_this->ddc.start();
|
||||
}
|
||||
|
||||
// Compute buffer size (Lower than usual, but it's a workaround for their API having broken streaming)
|
||||
_this->bufferSize = _this->sampleRate / 400.0;
|
||||
|
||||
// Start streaming
|
||||
err = fobos_rx_start_sync(_this->openDev, _this->bufferSize);
|
||||
if (err) {
|
||||
flog::error("Failed to start stream: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start worker
|
||||
_this->run = true;
|
||||
_this->workerThread = std::thread(&FobosSDRSourceModule::worker, _this);
|
||||
|
||||
_this->running = true;
|
||||
flog::info("FobosSDRSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
|
||||
// Stop worker
|
||||
_this->run = false;
|
||||
if (_this->port == PORT_RF && _this->sampleRate >= 50e6) {
|
||||
_this->ddc.out.stopWriter();
|
||||
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
|
||||
_this->ddc.out.clearWriteStop();
|
||||
}
|
||||
else {
|
||||
_this->ddcIn.stopWriter();
|
||||
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
|
||||
_this->ddcIn.clearWriteStop();
|
||||
}
|
||||
|
||||
// Stop streaming
|
||||
fobos_rx_stop_sync(_this->openDev);
|
||||
|
||||
// Stop the DDC
|
||||
_this->ddc.stop();
|
||||
|
||||
// Close the device
|
||||
fobos_rx_close(_this->openDev);
|
||||
|
||||
flog::info("FobosSDRSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
if (_this->port == PORT_RF) {
|
||||
double actual; // Dummy, don't care
|
||||
fobos_rx_set_frequency(_this->openDev, freq, &actual);
|
||||
}
|
||||
else {
|
||||
_this->ddc.setOffset(freq);
|
||||
}
|
||||
}
|
||||
_this->freq = freq;
|
||||
flog::info("FobosSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
FobosSDRSourceModule* _this = (FobosSDRSourceModule*)ctx;
|
||||
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->selectedSerial;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
|
||||
_this->sampleRate = _this->samplerates.value(_this->srId);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["samplerate"] = _this->samplerates.key(_this->srId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
SmGui::SameLine();
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_fobossdr_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->selectedSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Antenna Port");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_port_", _this->name), &_this->portId, _this->ports.txt)) {
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["port"] = _this->ports.key(_this->portId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
SmGui::LeftLabel("Clock Source");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_fobossdr_clk_", _this->name), &_this->clkSrcId, _this->clockSources.txt)) {
|
||||
if (_this->running) {
|
||||
fobos_rx_set_clk_source(_this->openDev, _this->clockSources[_this->clkSrcId]);
|
||||
}
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["clkSrc"] = _this->clockSources.key(_this->clkSrcId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->port == PORT_RF) {
|
||||
SmGui::LeftLabel("LNA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_fobossdr_lna_gain_", _this->name), &_this->lnaGain, FOBOS_LNA_GAIN_MIN, FOBOS_LNA_GAIN_MAX)) {
|
||||
if (_this->running) {
|
||||
fobos_rx_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["lnaGain"] = _this->lnaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("VGA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_fobossdr_vga_gain_", _this->name), &_this->vgaGain, FOBOS_VGA_GAIN_MIN, FOBOS_VGA_GAIN_MAX)) {
|
||||
if (_this->running) {
|
||||
fobos_rx_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (!_this->selectedSerial.empty()) {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["vgaGain"] = _this->vgaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void worker() {
|
||||
// Select different processing depending on the mode
|
||||
if (port == PORT_RF && sampleRate >= 50e6) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddc.out.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Send out samples to the core
|
||||
if (!ddc.out.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
else if (port == PORT_RF) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Send samples to the DDC
|
||||
if (!ddcIn.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
else if (port == PORT_HF1) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Null out the HF2 samples
|
||||
for (int i = 0; i < sampCount; i++) {
|
||||
ddcIn.writeBuf[i].im = 0.0f;
|
||||
}
|
||||
|
||||
// Send samples to the DDC
|
||||
if (!ddcIn.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
else if (port == PORT_HF2) {
|
||||
while (run) {
|
||||
// Read samples
|
||||
unsigned int sampCount = 0;
|
||||
int err = fobos_rx_read_sync(openDev, (float*)ddcIn.writeBuf, &sampCount);
|
||||
if (err) { break; }
|
||||
|
||||
// Null out the HF2 samples
|
||||
for (int i = 0; i < sampCount; i++) {
|
||||
ddcIn.writeBuf[i].re = 0.0f;
|
||||
}
|
||||
|
||||
// Send samples to the DDC
|
||||
if (!ddcIn.swap(sampCount)) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
|
||||
OptionList<std::string, int> devices;
|
||||
OptionList<int, double> samplerates;
|
||||
OptionList<std::string, Port> ports;
|
||||
OptionList<std::string, int> clockSources;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int portId = 0;
|
||||
int clkSrcId = 0;
|
||||
Port port;
|
||||
int lnaGain = 0;
|
||||
int vgaGain = 0;
|
||||
std::string selectedSerial;
|
||||
int selectedDevId;
|
||||
|
||||
fobos_dev_t* openDev;
|
||||
|
||||
int bufferSize;
|
||||
std::thread workerThread;
|
||||
std::atomic<bool> run = false;
|
||||
|
||||
dsp::stream<dsp::complex_t> ddcIn;
|
||||
dsp::channel::RxVFO ddc;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(core::args["root"].s() + "/fobossdr_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new FobosSDRSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (FobosSDRSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
17
source_modules/harogic_source/CMakeLists.txt
Normal file
17
source_modules/harogic_source/CMakeLists.txt
Normal 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 ()
|
||||
510
source_modules/harogic_source/src/main.cpp
Normal file
510
source_modules/harogic_source/src/main.cpp
Normal 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
|
||||
}
|
||||
36
source_modules/hydrasdr_source/CMakeLists.txt
Normal file
36
source_modules/hydrasdr_source/CMakeLists.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(hydrasdr_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
if (MSVC)
|
||||
target_link_directories(hydrasdr_source PRIVATE "C:/Program Files (x86)/hydrasdr_all/bin/")
|
||||
|
||||
target_include_directories(hydrasdr_source PUBLIC "C:/Program Files (x86)/hydrasdr_all/include/libhydrasdr")
|
||||
|
||||
target_link_libraries(hydrasdr_source PRIVATE hydrasdr)
|
||||
elseif (ANDROID)
|
||||
target_include_directories(hydrasdr_source PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/include/libhydrasdr
|
||||
)
|
||||
|
||||
target_link_libraries(hydrasdr_source PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libusb1.0.so
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libhydrasdr.so
|
||||
)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBHYDRASDR REQUIRED libhydrasdr)
|
||||
|
||||
target_include_directories(hydrasdr_source PRIVATE ${LIBHYDRASDR_INCLUDE_DIRS})
|
||||
target_link_directories(hydrasdr_source PRIVATE ${LIBHYDRASDR_LIBRARY_DIRS})
|
||||
target_link_libraries(hydrasdr_source PRIVATE ${LIBHYDRASDR_LIBRARIES})
|
||||
|
||||
# Include it because for some reason pkgconfig doesn't look here?
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
target_include_directories(hydrasdr_source PRIVATE "/usr/local/include")
|
||||
endif()
|
||||
endif ()
|
||||
639
source_modules/hydrasdr_source/src/main.cpp
Normal file
639
source_modules/hydrasdr_source/src/main.cpp
Normal file
@@ -0,0 +1,639 @@
|
||||
#include <imgui.h>
|
||||
#include <utils/flog.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <hydrasdr.h>
|
||||
#include <utils/optionlist.h>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android_backend.h>
|
||||
#endif
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "hydrasdr_source",
|
||||
/* Description: */ "HydraSDR source module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
class HydraSDRSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
HydraSDRSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
// Define the ports
|
||||
ports.define("rx0", "RX0", RF_PORT_RX0);
|
||||
ports.define("rx1", "RX1", RF_PORT_RX1);
|
||||
ports.define("rx2", "RX2", RF_PORT_RX2);
|
||||
|
||||
regStr[0] = 0;
|
||||
valStr[0] = 0;
|
||||
|
||||
sampleRate = 10000000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
refresh();
|
||||
|
||||
// Select device from config
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
selectByString(devSerial);
|
||||
|
||||
sigpath::sourceManager.registerSource("HydraSDR", &handler);
|
||||
}
|
||||
|
||||
~HydraSDRSourceModule() {
|
||||
stop(this);
|
||||
sigpath::sourceManager.unregisterSource("HydraSDR");;
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
#ifndef __ANDROID__
|
||||
devices.clear();
|
||||
|
||||
uint64_t serials[256];
|
||||
int n = hydrasdr_list_devices(serials, 256);
|
||||
|
||||
char buf[1024];
|
||||
for (int i = 0; i < n; i++) {
|
||||
sprintf(buf, "%016" PRIX64, serials[i]);
|
||||
devices.define(buf, buf, serials[i]);
|
||||
}
|
||||
#else
|
||||
// Check for device presence
|
||||
int vid, pid;
|
||||
devFd = backend::getDeviceFD(vid, pid, backend::HYDRASDR_VIDPIDS);
|
||||
if (devFd < 0) { return; }
|
||||
|
||||
// Get device info
|
||||
std::string fakeName = "HydraSDR USB";
|
||||
devices.define(fakeName, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
if (!devices.empty()) {
|
||||
selectBySerial(devices.value(0));
|
||||
}
|
||||
}
|
||||
|
||||
void selectByString(std::string serial) {
|
||||
if (devices.keyExists(serial)) {
|
||||
selectBySerial(devices.value(devices.keyId(serial)));
|
||||
return;
|
||||
}
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
void selectBySerial(uint64_t serial) {
|
||||
hydrasdr_device* dev;
|
||||
try {
|
||||
#ifndef __ANDROID__
|
||||
int err = hydrasdr_open_sn(&dev, serial);
|
||||
#else
|
||||
int err = hydrasdr_open_fd(&dev, devFd);
|
||||
#endif
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
flog::error("Could not open HydraSDR {0}", buf);
|
||||
selectedSerial = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
flog::error("Could not open HydraSDR {}", buf);
|
||||
}
|
||||
devId = devices.valueId(serial);
|
||||
selectedSerial = serial;
|
||||
selectedSerStr = devices.key(devId);
|
||||
|
||||
uint32_t sampleRates[256];
|
||||
hydrasdr_get_samplerates(dev, sampleRates, 0);
|
||||
int n = sampleRates[0];
|
||||
hydrasdr_get_samplerates(dev, sampleRates, n);
|
||||
samplerates.clear();
|
||||
for (int i = 0; i < n; i++) {
|
||||
samplerates.define(sampleRates[i], getBandwdithScaled(sampleRates[i]), sampleRates[i]);
|
||||
}
|
||||
|
||||
// Load config here
|
||||
config.acquire();
|
||||
bool created = false;
|
||||
if (!config.conf["devices"].contains(selectedSerStr)) {
|
||||
created = true;
|
||||
config.conf["devices"][selectedSerStr]["sampleRate"] = 10000000;
|
||||
config.conf["devices"][selectedSerStr]["gainMode"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["sensitiveGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["linearGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lnaGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["mixerGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["vgaGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lnaAgc"] = false;
|
||||
config.conf["devices"][selectedSerStr]["mixerAgc"] = false;
|
||||
config.conf["devices"][selectedSerStr]["biasT"] = false;
|
||||
}
|
||||
|
||||
// Load sample rate
|
||||
srId = 0;
|
||||
sampleRate = samplerates.value(0);
|
||||
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
|
||||
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
|
||||
if (samplerates.keyExists(selectedSr)) {
|
||||
srId = samplerates.keyId(selectedSr);
|
||||
sampleRate = samplerates[srId];
|
||||
}
|
||||
}
|
||||
|
||||
// Load port
|
||||
if (config.conf["devices"][selectedSerStr].contains("port")) {
|
||||
std::string portStr = config.conf["devices"][selectedSerStr]["port"];
|
||||
if (ports.keyExists(portStr)) {
|
||||
portId = ports.keyId(portStr);
|
||||
}
|
||||
}
|
||||
|
||||
// Load gains
|
||||
if (config.conf["devices"][selectedSerStr].contains("gainMode")) {
|
||||
gainMode = config.conf["devices"][selectedSerStr]["gainMode"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("sensitiveGain")) {
|
||||
sensitiveGain = config.conf["devices"][selectedSerStr]["sensitiveGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("linearGain")) {
|
||||
linearGain = config.conf["devices"][selectedSerStr]["linearGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lnaGain")) {
|
||||
lnaGain = config.conf["devices"][selectedSerStr]["lnaGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("mixerGain")) {
|
||||
mixerGain = config.conf["devices"][selectedSerStr]["mixerGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("vgaGain")) {
|
||||
vgaGain = config.conf["devices"][selectedSerStr]["vgaGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lnaAgc")) {
|
||||
lnaAgc = config.conf["devices"][selectedSerStr]["lnaAgc"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("mixerAgc")) {
|
||||
mixerAgc = config.conf["devices"][selectedSerStr]["mixerAgc"];
|
||||
}
|
||||
|
||||
// Load Bias-T
|
||||
if (config.conf["devices"][selectedSerStr].contains("biasT")) {
|
||||
biasT = config.conf["devices"][selectedSerStr]["biasT"];
|
||||
}
|
||||
|
||||
config.release(created);
|
||||
|
||||
hydrasdr_close(dev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
flog::info("HydraSDRSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
|
||||
flog::info("HydraSDRSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
if (_this->selectedSerial == 0) {
|
||||
flog::error("Tried to start HydraSDR source with null serial");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef __ANDROID__
|
||||
int err = hydrasdr_open_sn(&_this->openDev, _this->selectedSerial);
|
||||
#else
|
||||
int err = hydrasdr_open_fd(&_this->openDev, _this->devFd);
|
||||
#endif
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
|
||||
flog::error("Could not open HydraSDR {0}", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
hydrasdr_set_samplerate(_this->openDev, _this->samplerates[_this->srId]);
|
||||
hydrasdr_set_freq(_this->openDev, _this->freq);
|
||||
|
||||
hydrasdr_set_rf_port(_this->openDev, _this->ports[_this->portId]);
|
||||
|
||||
if (_this->gainMode == 0) {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 0);
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 0);
|
||||
hydrasdr_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
else if (_this->gainMode == 1) {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 0);
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 0);
|
||||
hydrasdr_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
else if (_this->gainMode == 2) {
|
||||
if (_this->lnaAgc) {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 0);
|
||||
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->mixerAgc) {
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 0);
|
||||
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
hydrasdr_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
|
||||
hydrasdr_set_rf_bias(_this->openDev, _this->biasT);
|
||||
|
||||
hydrasdr_start_rx(_this->openDev, callback, _this);
|
||||
|
||||
_this->running = true;
|
||||
flog::info("HydraSDRSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
_this->stream.stopWriter();
|
||||
hydrasdr_close(_this->openDev);
|
||||
_this->stream.clearWriteStop();
|
||||
flog::info("HydraSDRSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
hydrasdr_set_freq(_this->openDev, freq);
|
||||
}
|
||||
_this->freq = freq;
|
||||
flog::info("HydraSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)ctx;
|
||||
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_hydrasdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->selectBySerial(_this->devices[_this->devId]);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->selectedSerStr;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (SmGui::Combo(CONCAT("##_hydrasdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
|
||||
_this->sampleRate = _this->samplerates[_this->srId];
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->samplerates.key(_this->srId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
SmGui::SameLine();
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_hydrasdr_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
_this->selectByString(devSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
SmGui::LeftLabel("Antenna Port");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(CONCAT("##_hydrasdr_port_", _this->name), &_this->portId, _this->ports.txt)) {
|
||||
if (_this->running) {
|
||||
hydrasdr_set_rf_port(_this->openDev, _this->ports[_this->portId]);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["port"] = _this->ports.key(_this->portId);
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
SmGui::BeginGroup();
|
||||
SmGui::Columns(3, CONCAT("HydraSDRGainModeColumns##_", _this->name), false);
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::RadioButton(CONCAT("Sensitive##_hydrasdr_gm_", _this->name), _this->gainMode == 0)) {
|
||||
_this->gainMode = 0;
|
||||
if (_this->running) {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 0);
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 0);
|
||||
hydrasdr_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
SmGui::NextColumn();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::RadioButton(CONCAT("Linear##_hydrasdr_gm_", _this->name), _this->gainMode == 1)) {
|
||||
_this->gainMode = 1;
|
||||
if (_this->running) {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 0);
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 0);
|
||||
hydrasdr_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
SmGui::NextColumn();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::RadioButton(CONCAT("Free##_hydrasdr_gm_", _this->name), _this->gainMode == 2)) {
|
||||
_this->gainMode = 2;
|
||||
if (_this->running) {
|
||||
if (_this->lnaAgc) {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 0);
|
||||
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->mixerAgc) {
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 0);
|
||||
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
hydrasdr_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
SmGui::Columns(1, CONCAT("EndHydraSDRGainModeColumns##_", _this->name), false);
|
||||
SmGui::EndGroup();
|
||||
|
||||
// Gain menus
|
||||
|
||||
if (_this->gainMode == 0) {
|
||||
SmGui::LeftLabel("Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_hydrasdr_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) {
|
||||
if (_this->running) {
|
||||
hydrasdr_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_this->gainMode == 1) {
|
||||
SmGui::LeftLabel("Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_hydrasdr_lin_gain_", _this->name), &_this->linearGain, 0, 21)) {
|
||||
if (_this->running) {
|
||||
hydrasdr_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_this->gainMode == 2) {
|
||||
// TODO: Switch to a table for alignment
|
||||
if (_this->lnaAgc) { SmGui::BeginDisabled(); }
|
||||
SmGui::LeftLabel("LNA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_hydrasdr_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (_this->lnaAgc) { SmGui::EndDisabled(); }
|
||||
|
||||
if (_this->mixerAgc) { SmGui::BeginDisabled(); }
|
||||
SmGui::LeftLabel("Mixer Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_hydrasdr_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (_this->mixerAgc) { SmGui::EndDisabled(); }
|
||||
|
||||
SmGui::LeftLabel("VGA Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_hydrasdr_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
hydrasdr_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
// AGC Control
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Checkbox(CONCAT("LNA AGC##_hydrasdr_", _this->name), &_this->lnaAgc)) {
|
||||
if (_this->running) {
|
||||
if (_this->lnaAgc) {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
hydrasdr_set_lna_agc(_this->openDev, 0);
|
||||
hydrasdr_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Checkbox(CONCAT("Mixer AGC##_hydrasdr_", _this->name), &_this->mixerAgc)) {
|
||||
if (_this->running) {
|
||||
if (_this->mixerAgc) {
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
hydrasdr_set_mixer_agc(_this->openDev, 0);
|
||||
hydrasdr_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bias T
|
||||
if (SmGui::Checkbox(CONCAT("Bias T##_hydrasdr_", _this->name), &_this->biasT)) {
|
||||
if (_this->running) {
|
||||
hydrasdr_set_rf_bias(_this->openDev, _this->biasT);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char valStr[256];
|
||||
char regStr[256];
|
||||
|
||||
static int callback(hydrasdr_transfer_t* transfer) {
|
||||
HydraSDRSourceModule* _this = (HydraSDRSourceModule*)transfer->ctx;
|
||||
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
|
||||
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
hydrasdr_device* openDev;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
uint64_t selectedSerial = 0;
|
||||
std::string selectedSerStr = "";
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int portId = 0;
|
||||
|
||||
bool biasT = false;
|
||||
|
||||
int lnaGain = 0;
|
||||
int vgaGain = 0;
|
||||
int mixerGain = 0;
|
||||
int linearGain = 0;
|
||||
int sensitiveGain = 0;
|
||||
|
||||
int gainMode = 0;
|
||||
|
||||
bool lnaAgc = false;
|
||||
bool mixerAgc = false;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
int devFd = 0;
|
||||
#endif
|
||||
|
||||
OptionList<std::string, uint64_t> devices;
|
||||
OptionList<uint32_t, uint32_t> samplerates;
|
||||
OptionList<std::string, hydrasdr_rf_port_t> ports;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(core::args["root"].s() + "/hydrasdr_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new HydraSDRSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
delete (HydraSDRSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
1
source_modules/kcsdr_source/.gitignore
vendored
Normal file
1
source_modules/kcsdr_source/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
vendor/*
|
||||
10
source_modules/kcsdr_source/CMakeLists.txt
Normal file
10
source_modules/kcsdr_source/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(kcsdr_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_link_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10/x64/DLL")
|
||||
target_include_directories(kcsdr_source PRIVATE "vendor/FTD3XXLibrary_1.3.0.10")
|
||||
target_link_libraries(kcsdr_source PRIVATE FTD3XX)
|
||||
209
source_modules/kcsdr_source/src/kcsdr.c
Normal file
209
source_modules/kcsdr_source/src/kcsdr.c
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "kcsdr.h"
|
||||
#include <string.h>
|
||||
#include "../vendor/FTD3XXLibrary_1.3.0.10/FTD3XX.h"
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define KCSDR_PKT_EMPTY_LEN 0x0C
|
||||
|
||||
#define KCSDR_COMMAND_PIPE 0x02
|
||||
#define KCSDR_RX_DATA_PIPE 0x83
|
||||
#define KCSDR_TX_DATA_PIPE 0x03
|
||||
|
||||
struct kcsdr {
|
||||
FT_HANDLE ft;
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct kcsdr_packet {
|
||||
uint8_t zeros0[4];
|
||||
uint8_t length;
|
||||
uint8_t zeros1[2];
|
||||
uint8_t hex_eighty;
|
||||
uint32_t command;
|
||||
uint8_t data[188];
|
||||
};
|
||||
typedef struct kcsdr_packet kcsdr_packet_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
enum kcsdr_command {
|
||||
CMD_NOT_USED_0x00 = 0x00,
|
||||
CMD_SET_PORT = 0x01,
|
||||
CMD_SET_FREQUENCY = 0x02,
|
||||
CMD_SET_ATTENUATION = 0x03,
|
||||
CMD_SET_AMPLIFIER = 0x04,
|
||||
CMD_SET_BANDWIDTH = 0x05,
|
||||
CMD_START = 0x06,
|
||||
CMD_STOP = 0x07,
|
||||
CMD_SET_EXT_AMP = 0x08,
|
||||
CMD_START_REMOTE = 0x09,
|
||||
CMD_STOP_REMOTE = 0x0A
|
||||
};
|
||||
typedef enum kcsdr_command kcsdr_command_t;
|
||||
|
||||
int kcsdr_send_command(kcsdr_t* dev, kcsdr_direction_t dir, kcsdr_command_t cmd, const uint8_t* data, int len) {
|
||||
Sleep(50);
|
||||
|
||||
// Create an empty packet
|
||||
kcsdr_packet_t pkt;
|
||||
memset(&pkt, 0, sizeof(kcsdr_packet_t));
|
||||
|
||||
// Fill out the packet info
|
||||
pkt.length = len + KCSDR_PKT_EMPTY_LEN;
|
||||
pkt.hex_eighty = 0x80; // Whatever the fuck that is
|
||||
pkt.command = (uint32_t)cmd | (uint32_t)dir;
|
||||
|
||||
// Copy the data if there is some
|
||||
if (len) { memcpy(pkt.data, data, len); }
|
||||
|
||||
// Dump the bytes
|
||||
uint8_t* dump = (uint8_t*)&pkt;
|
||||
printf("Sending:");
|
||||
for (int i = 0; i < pkt.length; i++) {
|
||||
printf(" %02X", dump[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Send the command to endpoint 0
|
||||
int sent;
|
||||
FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_COMMAND_PIPE, (uint8_t*)&pkt, sizeof(kcsdr_packet_t), &sent, NULL);
|
||||
if (err != FT_OK) {
|
||||
return -err;
|
||||
}
|
||||
printf("Sent %d bytes (%d)\n", sent, err);
|
||||
|
||||
Sleep(50);
|
||||
|
||||
// Flush existing commands
|
||||
FT_FlushPipe(dev->ft, KCSDR_COMMAND_PIPE);
|
||||
|
||||
return -(int)err;
|
||||
}
|
||||
|
||||
int kcsdr_list_devices(kcsdr_info_t** devices) {
|
||||
// Generate a list of FTDI devices
|
||||
int ftdiDevCount = 0;
|
||||
FT_STATUS err = FT_CreateDeviceInfoList(&ftdiDevCount);
|
||||
if (err != FT_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If no device was found, return nothing
|
||||
if (!ftdiDevCount) {
|
||||
*devices = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get said device list
|
||||
FT_DEVICE_LIST_INFO_NODE* list = malloc(ftdiDevCount * sizeof(FT_DEVICE_LIST_INFO_NODE));
|
||||
err = FT_GetDeviceInfoList(list, &ftdiDevCount);
|
||||
if (err != FT_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate the device info list
|
||||
*devices = malloc(ftdiDevCount * sizeof(kcsdr_info_t));
|
||||
|
||||
// Find all KC908s
|
||||
int kcCount = 0;
|
||||
for (int i = 0; i < ftdiDevCount; i++) {
|
||||
strcpy((*devices)[kcCount++].serial, list[i].SerialNumber);
|
||||
}
|
||||
|
||||
// Free the FTDI list
|
||||
free(list);
|
||||
|
||||
return kcCount;
|
||||
}
|
||||
|
||||
void kcsdr_free_device_list(kcsdr_info_t* devices) {
|
||||
// Free the list
|
||||
if (devices) { free(devices); }
|
||||
}
|
||||
|
||||
int kcsdr_open(kcsdr_t** dev, const char* serial) {
|
||||
// Attempt to open the device using the serial number
|
||||
FT_HANDLE ft;
|
||||
FT_STATUS err = FT_Create(serial, FT_OPEN_BY_SERIAL_NUMBER, &ft);
|
||||
if (err != FT_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set the timeouts for the data pipes
|
||||
FT_SetPipeTimeout(ft, KCSDR_RX_DATA_PIPE, 1000);
|
||||
FT_SetPipeTimeout(ft, KCSDR_TX_DATA_PIPE, 1000);
|
||||
|
||||
// Allocate the device object
|
||||
*dev = malloc(sizeof(kcsdr_t));
|
||||
|
||||
// Fill out the device object
|
||||
(*dev)->ft = ft;
|
||||
|
||||
// Put device into remote control mode
|
||||
return kcsdr_send_command(*dev, KCSDR_DIR_RX, CMD_START_REMOTE, NULL, 0);
|
||||
}
|
||||
|
||||
void kcsdr_close(kcsdr_t* dev) {
|
||||
// Put device back in normal mode
|
||||
kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_STOP_REMOTE, NULL, 0);
|
||||
|
||||
// Close the device
|
||||
FT_Close(dev->ft);
|
||||
|
||||
// Free the device object
|
||||
free(dev);
|
||||
}
|
||||
|
||||
int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port) {
|
||||
// Send SET_PORT command
|
||||
return kcsdr_send_command(dev, dir, CMD_SET_PORT, &port, 1);
|
||||
}
|
||||
|
||||
int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq) {
|
||||
// Send SET_FREQUENCY command
|
||||
return kcsdr_send_command(dev, dir, CMD_SET_FREQUENCY, (uint8_t*)&freq, 8);
|
||||
}
|
||||
|
||||
int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att) {
|
||||
// Send SET_ATTENUATION command
|
||||
return kcsdr_send_command(dev, dir, CMD_SET_ATTENUATION, &att, 1);
|
||||
}
|
||||
|
||||
int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain) {
|
||||
// Send SET_AMPLIFIER command
|
||||
return kcsdr_send_command(dev, dir, CMD_SET_AMPLIFIER, &gain, 1);
|
||||
}
|
||||
|
||||
int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain) {
|
||||
// Send CMD_SET_EXT_AMP command
|
||||
return kcsdr_send_command(dev, KCSDR_DIR_RX, CMD_SET_EXT_AMP, &gain, 1);
|
||||
}
|
||||
|
||||
int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate) {
|
||||
// Set SET_BANDWIDTH command
|
||||
return kcsdr_send_command(dev, dir, CMD_SET_BANDWIDTH, (uint8_t*)&samplerate, 4);
|
||||
}
|
||||
|
||||
int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir) {
|
||||
// Send START command
|
||||
return kcsdr_send_command(dev, dir, CMD_START, NULL, 0);
|
||||
}
|
||||
|
||||
int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir) {
|
||||
// Send STOP command
|
||||
return kcsdr_send_command(dev, dir, CMD_STOP, NULL, 0);
|
||||
}
|
||||
|
||||
int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count) {
|
||||
// Receive samples (TODO: Endpoint might be 0x81)
|
||||
int received;
|
||||
FT_STATUS err = FT_ReadPipeEx(dev->ft, KCSDR_RX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &received, NULL);
|
||||
return (err == FT_OK) ? received / (2*sizeof(uint16_t)) : -(int)err;
|
||||
}
|
||||
|
||||
int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count) {
|
||||
// Transmit samples
|
||||
int sent;
|
||||
FT_STATUS err = FT_WritePipeEx(dev->ft, KCSDR_TX_DATA_PIPE, (uint8_t*)samples, count*2*sizeof(uint16_t), &sent, NULL);
|
||||
return (err == FT_OK) ? sent / (2*sizeof(uint16_t)) : -(int)err;
|
||||
}
|
||||
150
source_modules/kcsdr_source/src/kcsdr.h
Normal file
150
source_modules/kcsdr_source/src/kcsdr.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define KCSDR_SERIAL_LEN 16
|
||||
#define KCSDR_MAX_PORTS 6
|
||||
|
||||
// Detect C++
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* KCSDR Device.
|
||||
*/
|
||||
struct kcsdr;
|
||||
typedef struct kcsdr kcsdr_t;
|
||||
|
||||
/**
|
||||
* Device Information
|
||||
*/
|
||||
struct kcsdr_info {
|
||||
char serial[KCSDR_SERIAL_LEN+1];
|
||||
};
|
||||
typedef struct kcsdr_info kcsdr_info_t;
|
||||
|
||||
/**
|
||||
* RF Direction.
|
||||
*/
|
||||
enum kcsdr_direction {
|
||||
KCSDR_DIR_RX = 0x00,
|
||||
KCSDR_DIR_TX = 0x80
|
||||
};
|
||||
typedef enum kcsdr_direction kcsdr_direction_t;
|
||||
|
||||
/**
|
||||
* Get a list of KCSDR devices on the system.
|
||||
* @param devices Pointer to an array of device info.
|
||||
* @return Number of devices found or error code.
|
||||
*/
|
||||
int kcsdr_list_devices(kcsdr_info_t** devices);
|
||||
|
||||
/**
|
||||
* Free a device list returned by `kcsdr_list_devices()`.
|
||||
* @param devices Device list to free.
|
||||
*/
|
||||
void kcsdr_free_device_list(kcsdr_info_t* devices);
|
||||
|
||||
/**
|
||||
* Open a KCSDR device.
|
||||
* @param dev Newly open device.
|
||||
* @param serial Serial number of the device to open as returned in the device list.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_open(kcsdr_t** dev, const char* serial);
|
||||
|
||||
/**
|
||||
* Close a KCSDR device.
|
||||
* @param dev Device to be closed.
|
||||
*/
|
||||
void kcsdr_close(kcsdr_t* dev);
|
||||
|
||||
/**
|
||||
* Select the RF port.
|
||||
* @param dev Device to control.
|
||||
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX.
|
||||
* @param port RF port number to select.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_set_port(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t port);
|
||||
|
||||
/**
|
||||
* Set the center frequency.
|
||||
* @param dev Device to control.
|
||||
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
|
||||
* @param freq Frequency in Hz.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_set_frequency(kcsdr_t* dev, kcsdr_direction_t dir, uint64_t freq);
|
||||
|
||||
/**
|
||||
* Set the attenuation.
|
||||
* @param dev Device to control.
|
||||
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
|
||||
* @param samplerate Attenuation in dB.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_set_attenuation(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t att);
|
||||
|
||||
/**
|
||||
* Set the internal amplifier gain.
|
||||
* @param dev Device to control.
|
||||
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
|
||||
* @param gain Gain in dB.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_set_amp_gain(kcsdr_t* dev, kcsdr_direction_t dir, uint8_t gain);
|
||||
|
||||
/**
|
||||
* Set the external amplifier gain.
|
||||
* @param dev Device to control.
|
||||
* @param gain Gain in dB.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_set_rx_ext_amp_gain(kcsdr_t* dev, uint8_t gain);
|
||||
|
||||
/**
|
||||
* Set the samplerate.
|
||||
* @param dev Device to control.
|
||||
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX
|
||||
* @param samplerate Samplerate in Hz.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_set_samplerate(kcsdr_t* dev, kcsdr_direction_t dir, uint32_t samplerate);
|
||||
|
||||
/**
|
||||
* Start streaming samples.
|
||||
* @param dev Device to control.
|
||||
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_start(kcsdr_t* dev, kcsdr_direction_t dir);
|
||||
|
||||
/**
|
||||
* Stop streaming samples.
|
||||
* @param dev Device to control.
|
||||
* @param dir Either KCSDR_DIR_RX or KCSDR_DIR_TX.
|
||||
* @return 0 on success, error code otherwise.
|
||||
*/
|
||||
int kcsdr_stop(kcsdr_t* dev, kcsdr_direction_t dir);
|
||||
|
||||
/**
|
||||
* Receive a buffer of samples.
|
||||
* @param samples Sample buffer.
|
||||
* @param count Number of complex samples.
|
||||
* @return Number of samples received.
|
||||
*/
|
||||
int kcsdr_rx(kcsdr_t* dev, int16_t* samples, int count);
|
||||
|
||||
/**
|
||||
* Transmit a buffer of samples.
|
||||
* @param samples Sample buffer.
|
||||
* @param count Number of complex samples.
|
||||
* @return Number of samples transmitted.
|
||||
*/
|
||||
int kcsdr_tx(kcsdr_t* dev, const int16_t* samples, int count);
|
||||
|
||||
// Detect C++
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
324
source_modules/kcsdr_source/src/main.cpp
Normal file
324
source_modules/kcsdr_source/src/main.cpp
Normal file
@@ -0,0 +1,324 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include "kcsdr.h"
|
||||
#include <atomic>
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "kcsdr_source",
|
||||
/* Description: */ "KCSDR Source Module",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
class KCSDRSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
KCSDRSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 2000000.0;
|
||||
samplerates.define(40e6, "40MHz", 40e6);
|
||||
samplerates.define(35e6, "35MHz", 35e6);
|
||||
samplerates.define(30e6, "30MHz", 30e6);
|
||||
samplerates.define(25e6, "25MHz", 25e6);
|
||||
samplerates.define(20e6, "20MHz", 20e6);
|
||||
samplerates.define(15e6, "15MHz", 15e6);
|
||||
samplerates.define(10e6, "10MHz", 10e6);
|
||||
samplerates.define(5e6, "5MHz", 5e6);
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
// Refresh devices
|
||||
refresh();
|
||||
|
||||
// Select first (TODO: Select from config)
|
||||
select("");
|
||||
|
||||
sigpath::sourceManager.registerSource("KCSDR", &handler);
|
||||
}
|
||||
|
||||
~KCSDRSourceModule() {
|
||||
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
void refresh() {
|
||||
devices.clear();
|
||||
|
||||
// Get device list
|
||||
kcsdr_info_t* list;
|
||||
int count = kcsdr_list_devices(&list);
|
||||
if (count < 0) {
|
||||
flog::error("Failed to list devices: {}", count);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create list
|
||||
for (int i = 0; i < count; i++) {
|
||||
devices.define(list[i].serial, list[i].serial, list[i].serial);
|
||||
}
|
||||
|
||||
// Free the device list
|
||||
kcsdr_free_device_list(list);
|
||||
}
|
||||
|
||||
void select(const std::string& serial) {
|
||||
// If there are no devices, give up
|
||||
if (devices.empty()) {
|
||||
selectedSerial.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the serial was not found, select the first available serial
|
||||
if (!devices.keyExists(serial)) {
|
||||
select(devices.key(0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the menu ID
|
||||
devId = devices.keyId(serial);
|
||||
|
||||
// TODO
|
||||
|
||||
// Update the samplerate
|
||||
core::setInputSampleRate(sampleRate);
|
||||
|
||||
// Save serial number
|
||||
selectedSerial = serial;
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
flog::info("KCSDRSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
|
||||
flog::info("KCSDRSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
|
||||
// If no serial is given, do nothing
|
||||
if (_this->selectedSerial.empty()) { return; }
|
||||
|
||||
// Open the device
|
||||
int err = kcsdr_open(&_this->openDev, _this->selectedSerial.c_str());
|
||||
if (err) {
|
||||
flog::error("Failed to open device: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure the device
|
||||
kcsdr_set_port(_this->openDev, KCSDR_DIR_RX, 0);
|
||||
kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, _this->freq);
|
||||
kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att);
|
||||
kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain);
|
||||
kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain);
|
||||
kcsdr_set_samplerate(_this->openDev, KCSDR_DIR_RX, _this->sampleRate);
|
||||
|
||||
// Start the stream
|
||||
kcsdr_start(_this->openDev, KCSDR_DIR_RX);
|
||||
|
||||
// Start worker
|
||||
_this->run = true;
|
||||
_this->workerThread = std::thread(&KCSDRSourceModule::worker, _this);
|
||||
|
||||
_this->running = true;
|
||||
flog::info("KCSDRSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
|
||||
// Stop worker
|
||||
_this->run = false;
|
||||
_this->stream.stopWriter();
|
||||
if (_this->workerThread.joinable()) { _this->workerThread.join(); }
|
||||
_this->stream.clearWriteStop();
|
||||
|
||||
// Stop streaming
|
||||
kcsdr_stop(_this->openDev, KCSDR_DIR_RX);
|
||||
|
||||
// Close the device
|
||||
kcsdr_close(_this->openDev);
|
||||
|
||||
flog::info("KCSDRSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
kcsdr_set_frequency(_this->openDev, KCSDR_DIR_RX, freq);
|
||||
}
|
||||
_this->freq = freq;
|
||||
flog::info("KCSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
KCSDRSourceModule* _this = (KCSDRSourceModule*)ctx;
|
||||
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Combo(CONCAT("##_kcsdr_dev_sel_", _this->name), &_this->devId, _this->devices.txt)) {
|
||||
_this->select(_this->devices.key(_this->devId));
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
if (SmGui::Combo(CONCAT("##_kcsdr_sr_sel_", _this->name), &_this->srId, _this->samplerates.txt)) {
|
||||
_this->sampleRate = _this->samplerates.value(_this->srId);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::SameLine();
|
||||
SmGui::FillWidth();
|
||||
SmGui::ForceSync();
|
||||
if (SmGui::Button(CONCAT("Refresh##_kcsdr_refr_", _this->name))) {
|
||||
_this->refresh();
|
||||
_this->select(_this->selectedSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
|
||||
// SmGui::LeftLabel("RX Port");
|
||||
// SmGui::FillWidth();
|
||||
// if (SmGui::Combo(CONCAT("##_kcsdr_port_", _this->name), &_this->portId, _this->rxPorts.txt)) {
|
||||
// if (_this->running) {
|
||||
// // TODO
|
||||
// }
|
||||
// // TODO: Save
|
||||
// }
|
||||
|
||||
SmGui::LeftLabel("Attenuation");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_kcsdr_att_", _this->name), &_this->att, 0, 31)) {
|
||||
if (_this->running) {
|
||||
kcsdr_set_attenuation(_this->openDev, KCSDR_DIR_RX, _this->att);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_kcsdr_gain_", _this->name), &_this->gain, 0, 31)) {
|
||||
if (_this->running) {
|
||||
kcsdr_set_amp_gain(_this->openDev, KCSDR_DIR_RX, _this->gain);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
|
||||
SmGui::LeftLabel("External Gain");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_kcsdr_ext_gain_", _this->name), &_this->extGain, 0, 31)) {
|
||||
if (_this->running) {
|
||||
kcsdr_set_rx_ext_amp_gain(_this->openDev, _this->extGain);
|
||||
}
|
||||
// TODO: Save
|
||||
}
|
||||
}
|
||||
|
||||
void worker() {
|
||||
// Compute the buffer size
|
||||
int bufferSize = 0x4000/4;//sampleRate / 200;
|
||||
|
||||
// Allocate the sample buffer
|
||||
int16_t* samps = dsp::buffer::alloc<int16_t>(bufferSize*2);
|
||||
|
||||
// Loop
|
||||
while (run) {
|
||||
// Read samples
|
||||
int count = kcsdr_rx(openDev, samps, bufferSize);
|
||||
if (!count) { continue; }
|
||||
if (count < 0) {
|
||||
flog::debug("Failed to read samples: {}", count);
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert the samples to float
|
||||
volk_16i_s32f_convert_32f((float*)stream.writeBuf, samps, 8192.0f, count*2);
|
||||
|
||||
// Send out the samples
|
||||
if (!stream.swap(count)) { break; }
|
||||
}
|
||||
|
||||
// Free the sample buffer
|
||||
dsp::buffer::free(samps);
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
|
||||
OptionList<std::string, std::string> devices;
|
||||
OptionList<int, double> samplerates;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int att = 0;
|
||||
int gain = 30;
|
||||
int extGain = 1;
|
||||
int portId = 0;
|
||||
std::string selectedSerial;
|
||||
|
||||
kcsdr_t* openDev;
|
||||
|
||||
std::thread workerThread;
|
||||
std::atomic<bool> run = false;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new KCSDRSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (KCSDRSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
// Nothing here
|
||||
}
|
||||
@@ -36,10 +36,10 @@ enum SampleType {
|
||||
};
|
||||
|
||||
const size_t SAMPLE_TYPE_SIZE[] {
|
||||
sizeof(int8_t)*2,
|
||||
sizeof(int16_t)*2,
|
||||
sizeof(int32_t)*2,
|
||||
sizeof(float)*2,
|
||||
2*sizeof(int8_t),
|
||||
2*sizeof(int16_t),
|
||||
2*sizeof(int32_t),
|
||||
2*sizeof(float),
|
||||
};
|
||||
|
||||
class NetworkSourceModule : public ModuleManager::Instance {
|
||||
@@ -58,20 +58,6 @@ public:
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
// Define samplerates
|
||||
for (int i = 3000; i <= 192000; i <<= 1) {
|
||||
samplerates.define(i, getSrScaled(i), i);
|
||||
}
|
||||
for (int i = 250000; i < 1000000; i += 250000) {
|
||||
samplerates.define(i, getSrScaled(i), i);
|
||||
}
|
||||
for (int i = 1000000; i < 10000000; i += 500000) {
|
||||
samplerates.define(i, getSrScaled(i), i);
|
||||
}
|
||||
for (int i = 10000000; i <= 100000000; i += 5000000) {
|
||||
samplerates.define(i, getSrScaled(i), i);
|
||||
}
|
||||
|
||||
// Define protocols
|
||||
// protocols.define("TCP (Server)", PROTOCOL_TCP_SERVER);
|
||||
protocols.define("TCP (Client)", PROTOCOL_TCP_CLIENT);
|
||||
@@ -86,8 +72,8 @@ public:
|
||||
// Load config
|
||||
config.acquire();
|
||||
if (config.conf[name].contains("samplerate")) {
|
||||
int sr = config.conf[name]["samplerate"];
|
||||
if (samplerates.keyExists(sr)) { samplerate = samplerates.value(samplerates.keyId(sr)); }
|
||||
samplerate = config.conf[name]["samplerate"];
|
||||
tempSamplerate = samplerate;
|
||||
}
|
||||
if (config.conf[name].contains("protocol")) {
|
||||
std::string protoStr = config.conf[name]["protocol"];
|
||||
@@ -108,7 +94,6 @@ public:
|
||||
config.release();
|
||||
|
||||
// Set menu IDs
|
||||
srId = samplerates.valueId(samplerate);
|
||||
protoId = protocols.valueId(proto);
|
||||
sampTypeId = sampleTypes.valueId(sampType);
|
||||
|
||||
@@ -228,35 +213,24 @@ private:
|
||||
if (_this->running) { SmGui::BeginDisabled(); }
|
||||
|
||||
// Hostname and port field
|
||||
if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
|
||||
if (SmGui::InputText(("##network_source_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
|
||||
config.acquire();
|
||||
config.conf[_this->name]["host"] = _this->hostname;
|
||||
config.release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
|
||||
SmGui::SameLine();
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::InputInt(("##network_source_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
|
||||
_this->port = std::clamp<int>(_this->port, 1, 65535);
|
||||
config.acquire();
|
||||
config.conf[_this->name]["port"] = _this->port;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
// Samplerate selector
|
||||
ImGui::LeftLabel("Samplerate");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##iq_exporter_sr_" + _this->name).c_str(), &_this->srId, _this->samplerates.txt)) {
|
||||
_this->samplerate = _this->samplerates.value(_this->srId);
|
||||
core::setInputSampleRate(_this->samplerate);
|
||||
config.acquire();
|
||||
config.conf[_this->name]["samplerate"] = _this->samplerates.key(_this->srId);
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
// Mode protocol selector
|
||||
ImGui::LeftLabel("Protocol");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
|
||||
SmGui::LeftLabel("Protocol");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(("##network_source_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
|
||||
_this->proto = _this->protocols.value(_this->protoId);
|
||||
config.acquire();
|
||||
config.conf[_this->name]["protocol"] = _this->protocols.key(_this->protoId);
|
||||
@@ -264,15 +238,38 @@ private:
|
||||
}
|
||||
|
||||
// Sample type selector
|
||||
ImGui::LeftLabel("Sample type");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
|
||||
SmGui::LeftLabel("Sample type");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Combo(("##network_source_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
|
||||
_this->sampType = _this->sampleTypes.value(_this->sampTypeId);
|
||||
config.acquire();
|
||||
config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampTypeId);
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
// Samplerate selector
|
||||
SmGui::LeftLabel("Samplerate");
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::InputInt(("##network_source_sr_" + _this->name).c_str(), &_this->tempSamplerate)) {
|
||||
// Prevent silly values from silly users
|
||||
_this->tempSamplerate = std::max<int>(_this->tempSamplerate, 1000);
|
||||
}
|
||||
bool applyEn = (!_this->running && _this->tempSamplerate != _this->samplerate);
|
||||
if (!applyEn) { SmGui::BeginDisabled(); }
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::Button(("Apply##network_source_apply_" + _this->name).c_str())) {
|
||||
_this->samplerate = _this->tempSamplerate;
|
||||
core::setInputSampleRate(_this->samplerate);
|
||||
config.acquire();
|
||||
config.conf[_this->name]["samplerate"] = _this->samplerate;
|
||||
config.release(true);
|
||||
}
|
||||
if (!applyEn) { SmGui::EndDisabled(); }
|
||||
|
||||
if (_this->tempSamplerate != _this->samplerate) {
|
||||
SmGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Warning: Samplerate not applied yet");
|
||||
}
|
||||
|
||||
if (_this->running) { SmGui::EndDisabled(); }
|
||||
}
|
||||
|
||||
@@ -280,14 +277,17 @@ private:
|
||||
// Compute sizes
|
||||
int blockSize = samplerate / 200;
|
||||
int sampleSize = SAMPLE_TYPE_SIZE[sampType];
|
||||
int frameSize = blockSize*sampleSize;
|
||||
|
||||
// Chose amount of bytes to attempt to read
|
||||
bool forceSize = (proto != PROTOCOL_UDP);
|
||||
int frameSize = sampleSize * (forceSize ? blockSize : STREAM_BUFFER_SIZE);
|
||||
|
||||
// Allocate receive buffer
|
||||
uint8_t* buffer = dsp::buffer::alloc<uint8_t>(frameSize);
|
||||
|
||||
while (true) {
|
||||
// Read samples from socket
|
||||
int bytes = sock->recv(buffer, frameSize, true);
|
||||
int bytes = sock->recv(buffer, frameSize, forceSize);
|
||||
if (bytes <= 0) { break; }
|
||||
|
||||
// Convert to CF32 (note: problem if partial sample)
|
||||
@@ -325,7 +325,7 @@ private:
|
||||
double freq;
|
||||
|
||||
int samplerate = 1000000;
|
||||
int srId;
|
||||
int tempSamplerate = 1000000;
|
||||
Protocol proto = PROTOCOL_UDP;
|
||||
int protoId;
|
||||
SampleType sampType = SAMPLE_TYPE_INT16;
|
||||
@@ -333,7 +333,6 @@ private:
|
||||
char hostname[1024] = "localhost";
|
||||
int port = 1234;
|
||||
|
||||
OptionList<int, int> samplerates;
|
||||
OptionList<std::string, Protocol> protocols;
|
||||
OptionList<std::string, SampleType> sampleTypes;
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ SDRPP_MOD_INFO{
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
const std::vector<const char*> deviceWhiteList = {
|
||||
"PlutoSDR",
|
||||
"ANTSDR",
|
||||
"LibreSDR"
|
||||
};
|
||||
|
||||
class PlutoSDRSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
PlutoSDRSourceModule(std::string name) {
|
||||
@@ -130,7 +136,14 @@ private:
|
||||
std::string duri = iio_context_info_get_uri(info);
|
||||
|
||||
// If the device is not a plutosdr, don't include it
|
||||
if (desc.find("PlutoSDR") == std::string::npos) {
|
||||
bool isPluto = false;
|
||||
for (const auto type : deviceWhiteList) {
|
||||
if (desc.find(type) != std::string::npos) {
|
||||
isPluto = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isPluto) {
|
||||
flog::warn("Ignored IIO device: [{}] {}", duri, desc);
|
||||
continue;
|
||||
}
|
||||
@@ -163,6 +176,9 @@ private:
|
||||
// Construct the device name
|
||||
std::string devName = '(' + backend + ") " + model + " [" + serial + ']';
|
||||
|
||||
// Skip duplicate devices
|
||||
if (devices.keyExists(desc) || devices.nameExists(devName) || devices.valueExists(duri)) { continue; }
|
||||
|
||||
// Save device
|
||||
devices.define(desc, devName, duri);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,17 @@ file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(rfnm_source PRIVATE "C:/Users/ryzerth/Documents/Code/librfnm/include/librfnm")
|
||||
target_link_directories(rfnm_source PRIVATE "C:/Users/ryzerth/Documents/Code/librfnm/build/Release")
|
||||
target_link_libraries(rfnm_source PRIVATE librfnm)
|
||||
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 ()
|
||||
@@ -3,9 +3,10 @@
|
||||
#include <gui/gui.h>
|
||||
#include <gui/smgui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <librfnm.h>
|
||||
#include <librfnm/librfnm.h>
|
||||
#include <core.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <atomic>
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "rfnm_source",
|
||||
@@ -90,8 +91,131 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
// // Open the device
|
||||
// librfnm* dev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, serial);
|
||||
// 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();
|
||||
@@ -103,27 +227,8 @@ private:
|
||||
}
|
||||
|
||||
// Get gain range
|
||||
gainMin = -30;//dev->librfnm_s->rx.ch[0].gain_range.min;
|
||||
gainMax = 60;//dev->librfnm_s->rx.ch[0].gain_range.max;
|
||||
|
||||
// // Close device
|
||||
// delete dev;
|
||||
|
||||
// Define samplerates
|
||||
samplerates.clear();
|
||||
samplerates.define(61440000, "61.44 MHz", 2);
|
||||
samplerates.define(122880000, "122.88 MHz", 1);
|
||||
|
||||
// TODO: Load options
|
||||
srId = samplerates.keyId(61440000);
|
||||
bwId = bandwidths.nameId("Auto");
|
||||
gain = 0;
|
||||
|
||||
// Update samplerate
|
||||
sampleRate = samplerates.key(srId);
|
||||
|
||||
// Save serial number
|
||||
selectedSerial = serial;
|
||||
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) {
|
||||
@@ -144,19 +249,6 @@ private:
|
||||
// Open the device
|
||||
_this->openDev = new librfnm(librfnm_transport::LIBRFNM_TRANSPORT_USB, _this->selectedSerial);
|
||||
|
||||
// Configure the device
|
||||
_this->openDev->librfnm_s->rx.ch[0].enable = RFNM_CH_ON;
|
||||
_this->openDev->librfnm_s->rx.ch[0].samp_freq_div_n = _this->samplerates[_this->srId];
|
||||
_this->openDev->librfnm_s->rx.ch[0].freq = _this->freq;
|
||||
_this->openDev->librfnm_s->rx.ch[0].gain = _this->gain;
|
||||
_this->openDev->librfnm_s->rx.ch[0].rfic_lpf_bw = 100;
|
||||
_this->openDev->librfnm_s->rx.ch[0].fm_notch = _this->fmNotch ? rfnm_fm_notch::RFNM_FM_NOTCH_ON : rfnm_fm_notch::RFNM_FM_NOTCH_OFF;
|
||||
_this->openDev->librfnm_s->rx.ch[0].path = _this->openDev->librfnm_s->rx.ch[0].path_preferred;
|
||||
rfnm_api_failcode fail = _this->openDev->set(LIBRFNM_APPLY_CH0_RX);
|
||||
if (fail != rfnm_api_failcode::RFNM_API_OK) {
|
||||
flog::error("Failed to configure device: {}", (int)fail);
|
||||
}
|
||||
|
||||
// Configure the stream
|
||||
_this->bufferSize = -1;
|
||||
_this->openDev->rx_stream(librfnm_stream_format::LIBRFNM_STREAM_FORMAT_CS16, &_this->bufferSize);
|
||||
@@ -171,7 +263,24 @@ private:
|
||||
_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;
|
||||
@@ -184,13 +293,20 @@ private:
|
||||
_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->librfnm_s->rx.ch[0].enable = RFNM_CH_ON;
|
||||
_this->openDev->set(LIBRFNM_APPLY_CH0_RX);
|
||||
_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;
|
||||
@@ -206,8 +322,8 @@ private:
|
||||
static void tune(double freq, void* ctx) {
|
||||
RFNMSourceModule* _this = (RFNMSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
_this->openDev->librfnm_s->rx.ch[0].freq = freq;
|
||||
rfnm_api_failcode fail = _this->openDev->set(LIBRFNM_APPLY_CH0_RX);
|
||||
_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);
|
||||
}
|
||||
@@ -244,11 +360,48 @@ private:
|
||||
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
|
||||
}
|
||||
|
||||
@@ -256,16 +409,16 @@ private:
|
||||
SmGui::FillWidth();
|
||||
if (SmGui::SliderInt(CONCAT("##_rfnm_gain_", _this->name), &_this->gain, _this->gainMin, _this->gainMax)) {
|
||||
if (_this->running) {
|
||||
_this->openDev->librfnm_s->rx.ch[0].gain = _this->gain;
|
||||
rfnm_api_failcode fail = _this->openDev->set(LIBRFNM_APPLY_CH0_RX);
|
||||
_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->librfnm_s->rx.ch[0].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(LIBRFNM_APPLY_CH0_RX);
|
||||
_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
|
||||
}
|
||||
@@ -274,12 +427,18 @@ private:
|
||||
void worker() {
|
||||
librfnm_rx_buf* lrxbuf;
|
||||
int sampCount = bufferSize/4;
|
||||
uint8_t ch = (1 << currentPath.chId);
|
||||
|
||||
// TODO: Define number of buffers per swap to maintain 200 fps
|
||||
// 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; }
|
||||
|
||||
while (true) {
|
||||
int count = 0;
|
||||
while (run) {
|
||||
// Receive a buffer
|
||||
auto fail = openDev->rx_dqbuf(&lrxbuf, LIBRFNM_CH0, 1000);
|
||||
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;
|
||||
@@ -287,13 +446,17 @@ private:
|
||||
else if (fail) { break; }
|
||||
|
||||
// Convert buffer to CF32
|
||||
volk_16i_s32f_convert_32f((float*)stream.writeBuf, (int16_t*)lrxbuf->buf, 32768.0f, sampCount * 2);
|
||||
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 (!stream.swap(sampCount)) { break; }
|
||||
if (count >= bufCount) {
|
||||
if (!stream.swap(count*sampCount)) { break; }
|
||||
count = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flog::debug("Worker exiting");
|
||||
@@ -308,12 +471,15 @@ private:
|
||||
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;
|
||||
@@ -321,8 +487,11 @@ private:
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
|
||||
#define RFSPACE_MAX_SIZE 8192
|
||||
#define RFSPACE_HEARTBEAT_INTERVAL_MS 1000
|
||||
|
||||
@@ -121,14 +121,23 @@ public:
|
||||
#ifndef __ANDROID__
|
||||
devCount = rtlsdr_get_device_count();
|
||||
char buf[1024];
|
||||
char snBuf[1024];
|
||||
char venBuf[256];
|
||||
char prodBuf[256];
|
||||
char snBuf[256];
|
||||
for (int i = 0; i < devCount; i++) {
|
||||
// Gather device info
|
||||
const char* devName = rtlsdr_get_device_name(i);
|
||||
int snErr = rtlsdr_get_device_usb_strings(i, NULL, NULL, snBuf);
|
||||
int snErr = rtlsdr_get_device_usb_strings(i, venBuf, prodBuf, snBuf);
|
||||
|
||||
// Build name
|
||||
sprintf(buf, "[%s] %s##%d", (!snErr && snBuf[0]) ? snBuf : "No Serial", devName, i);
|
||||
if (venBuf[0] && prodBuf[0]) {
|
||||
sprintf(buf, "%s %s [%s]##%d", venBuf, prodBuf, (!snErr && snBuf[0]) ? snBuf : "No Serial", i);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%s [%s]##%d", devName, (!snErr && snBuf[0]) ? snBuf : "No Serial", i);
|
||||
}
|
||||
|
||||
// Add device to list
|
||||
devNames.push_back(buf);
|
||||
devListTxt += buf;
|
||||
devListTxt += '\0';
|
||||
@@ -199,8 +208,6 @@ public:
|
||||
config.conf["devices"][selectedDevName]["tunerAgc"] = tunerAgc;
|
||||
config.conf["devices"][selectedDevName]["gain"] = gainId;
|
||||
}
|
||||
if (gainId >= gainList.size()) { gainId = gainList.size() - 1; }
|
||||
updateGainTxt();
|
||||
|
||||
// Load config
|
||||
if (config.conf["devices"][selectedDevName].contains("sampleRate")) {
|
||||
@@ -240,9 +247,11 @@ public:
|
||||
|
||||
if (config.conf["devices"][selectedDevName].contains("gain")) {
|
||||
gainId = config.conf["devices"][selectedDevName]["gain"];
|
||||
updateGainTxt();
|
||||
}
|
||||
|
||||
if (gainId >= gainList.size()) { gainId = gainList.size() - 1; }
|
||||
updateGainTxt();
|
||||
|
||||
config.release(created);
|
||||
|
||||
rtlsdr_close(openDev);
|
||||
@@ -595,4 +604,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
}
|
||||
|
||||
9
source_modules/sddc_source/CMakeLists.txt
Normal file
9
source_modules/sddc_source/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(sddc_source)
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
add_subdirectory("./libsddc")
|
||||
target_link_libraries(sddc_source PRIVATE sddc)
|
||||
2
source_modules/sddc_source/libsddc/.gitignore
vendored
Normal file
2
source_modules/sddc_source/libsddc/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
.vscode/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user