mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2026-04-20 23:22:43 +00:00
Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6df4d58e5 | ||
|
|
f817f7365b | ||
|
|
8eebbacb96 | ||
|
|
7be83ffe65 | ||
|
|
6fa945333c | ||
|
|
a3168f0365 | ||
|
|
acb96dd70a | ||
|
|
e8c73f4dab | ||
|
|
8c247c5679 | ||
|
|
ce34af01ee | ||
|
|
1f1315f015 | ||
|
|
11ffdcfd53 | ||
|
|
2330af01e5 | ||
|
|
621c031364 | ||
|
|
2bf3faebae | ||
|
|
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 | ||
|
|
bb9024fadd | ||
|
|
d1dc20f4e2 | ||
|
|
309717b5f8 | ||
|
|
762444d340 | ||
|
|
18300e8916 | ||
|
|
a93bb9d468 | ||
|
|
ea0362b927 | ||
|
|
ffc642f270 | ||
|
|
1b5975f563 | ||
|
|
733dc55723 | ||
|
|
b841180f84 | ||
|
|
e99e84e809 | ||
|
|
7a4281dd76 | ||
|
|
c89763a989 | ||
|
|
27edc260c9 | ||
|
|
2ea7ac496f | ||
|
|
314d78d9d2 | ||
|
|
4e455e6661 | ||
|
|
58b86fcee5 | ||
|
|
27072e9fe7 | ||
|
|
da1417b5ab | ||
|
|
e60eca5d6d | ||
|
|
ccb10bfb9a |
322
.github/workflows/build_all.yml
vendored
322
.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,32 +110,44 @@ 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 ../../
|
||||
|
||||
- name: Install SDRplay API
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
|
||||
|
||||
- name: Install libiio
|
||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- 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: Install libdlcr
|
||||
run: wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip && mkdir dlcr_host && cd dlcr_host && 7z x ../dlcr_host_v0.3.0.zip && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 .. -DCMAKE_BUILD_TYPE=Release && make -j2 && 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 -DOPT_BUILD_DRAGONLABS_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@@ -136,7 +164,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,19 +173,19 @@ jobs:
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako --break-system-packages
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages
|
||||
|
||||
- name: Install volk
|
||||
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install SDRplay API
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
|
||||
|
||||
- name: Install libiio
|
||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- 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 +193,24 @@ 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: Install libdlcr
|
||||
run: wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip && mkdir dlcr_host && cd dlcr_host && 7z x ../dlcr_host_v0.3.0.zip && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 .. -DCMAKE_BUILD_TYPE=Release && make -j2 && 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 -DOPT_BUILD_DRAGONLABS_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@@ -186,29 +226,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 +248,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 +292,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 +380,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 +424,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:
|
||||
@@ -318,14 +468,14 @@ jobs:
|
||||
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,35 +487,52 @@ 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_raspios_bullseye_armhf:
|
||||
runs-on: ARM
|
||||
build_ubuntu_noble_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_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: 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: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: make VERBOSE=1 -j3
|
||||
|
||||
- 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_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_noble_aarch64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_noble_aarch64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_android:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -395,7 +562,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_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:
|
||||
@@ -408,14 +594,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_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_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.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_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
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,3 +17,7 @@ m17_decoder/libcorrect
|
||||
SDR++.app
|
||||
android/deps
|
||||
android/app/assets
|
||||
source_modules/badgesdr_source
|
||||
decoder_modules/mystery_decoder
|
||||
decoder_modules/tetra_decoder
|
||||
misc_modules/modulation_monitor
|
||||
@@ -12,14 +12,21 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy
|
||||
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON)
|
||||
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON)
|
||||
option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON)
|
||||
option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF)
|
||||
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
|
||||
option(OPT_BUILD_DRAGONLABS_SOURCE "Build Dragon Labs Source Module (Dependencies: libdlcr)" 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)
|
||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
|
||||
option(OPT_BUILD_RFNM_SOURCE "Build RFNM Source Module (Dependencies: librfnm)" OFF)
|
||||
option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON)
|
||||
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
|
||||
@@ -39,13 +46,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
|
||||
@@ -61,6 +71,7 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
|
||||
# Other options
|
||||
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON)
|
||||
option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF)
|
||||
option(COPY_MSVC_REDISTRIBUTABLES "Copy over the Visual C++ Redistributable" OFF)
|
||||
|
||||
# Module cmake path
|
||||
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
|
||||
@@ -125,22 +136,46 @@ if (OPT_BUILD_AUDIO_SOURCE)
|
||||
add_subdirectory("source_modules/audio_source")
|
||||
endif (OPT_BUILD_AUDIO_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BADGESDR_SOURCE)
|
||||
add_subdirectory("source_modules/badgesdr_source")
|
||||
endif (OPT_BUILD_BADGESDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BLADERF_SOURCE)
|
||||
add_subdirectory("source_modules/bladerf_source")
|
||||
endif (OPT_BUILD_BLADERF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_DRAGONLABS_SOURCE)
|
||||
add_subdirectory("source_modules/dragonlabs_source")
|
||||
endif (OPT_BUILD_DRAGONLABS_SOURCE)
|
||||
|
||||
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)
|
||||
@@ -157,6 +192,10 @@ if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("source_modules/plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFNM_SOURCE)
|
||||
add_subdirectory("source_modules/rfnm_source")
|
||||
endif (OPT_BUILD_RFNM_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFSPACE_SOURCE)
|
||||
add_subdirectory("source_modules/rfspace_source")
|
||||
endif (OPT_BUILD_RFSPACE_SOURCE)
|
||||
@@ -169,6 +208,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)
|
||||
@@ -225,6 +268,10 @@ if (OPT_BUILD_ATV_DECODER)
|
||||
add_subdirectory("decoder_modules/atv_decoder")
|
||||
endif (OPT_BUILD_ATV_DECODER)
|
||||
|
||||
if (OPT_BUILD_DAB_DECODER)
|
||||
add_subdirectory("decoder_modules/dab_decoder")
|
||||
endif (OPT_BUILD_DAB_DECODER)
|
||||
|
||||
if (OPT_BUILD_FALCON9_DECODER)
|
||||
add_subdirectory("decoder_modules/falcon9_decoder")
|
||||
endif (OPT_BUILD_FALCON9_DECODER)
|
||||
@@ -249,6 +296,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)
|
||||
@@ -302,6 +357,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 ()
|
||||
|
||||
|
||||
@@ -343,3 +413,5 @@ endif ()
|
||||
# Create uninstall target
|
||||
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
|
||||
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
|
||||
# Create headers target
|
||||
|
||||
@@ -10,11 +10,11 @@ android {
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.1.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.
|
||||
|
||||
@@ -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,6 +99,9 @@ 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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(Correct C)
|
||||
include(CheckLibraryExists)
|
||||
include(CheckIncludeFiles)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -171,18 +171,30 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["moduleInstances"]["Audio Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source";
|
||||
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Dragon Labs Source"]["module"] = "dragonlabs_source";
|
||||
defConfig["moduleInstances"]["Dragon Labs 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 +205,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 +236,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 +299,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 +330,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,14 +37,20 @@ namespace sdrpp_credits {
|
||||
const char* hardwareDonators[] = {
|
||||
"Aaronia AG",
|
||||
"Airspy",
|
||||
"Alex 4Z5LV",
|
||||
"Analog Devices",
|
||||
"CaribouLabs",
|
||||
"Deepace",
|
||||
"Ettus Research",
|
||||
"Harogic",
|
||||
"Howard Su",
|
||||
"MicroPhase",
|
||||
"Microtelecom",
|
||||
"MyriadRF",
|
||||
"Nuand",
|
||||
"RFNM",
|
||||
"RFspace",
|
||||
"RigExpert",
|
||||
"RTL-SDRblog",
|
||||
"SDRplay"
|
||||
};
|
||||
@@ -64,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",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace dsp {
|
||||
class generic_block {
|
||||
public:
|
||||
virtual ~generic_block() {}
|
||||
virtual void start() {}
|
||||
virtual void stop() {}
|
||||
virtual int run() { return -1; }
|
||||
@@ -16,8 +17,6 @@ namespace dsp {
|
||||
|
||||
class block : public generic_block {
|
||||
public:
|
||||
virtual void init() {}
|
||||
|
||||
virtual ~block() {
|
||||
if (!_block_init) { return; }
|
||||
stop();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <volk/volk.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp::buffer {
|
||||
template<class T>
|
||||
|
||||
@@ -163,10 +163,10 @@ namespace dsp {
|
||||
|
||||
private:
|
||||
Processor<T, T>* blockBefore(Processor<T, T>* block) {
|
||||
// TODO: This is wrong and must be fixed when I get more time
|
||||
Processor<T, T>* prev = NULL;
|
||||
for (auto& ln : links) {
|
||||
if (ln == block) { return NULL; }
|
||||
if (states[ln]) { return ln; }
|
||||
if (ln == block) { return prev; }
|
||||
if (states[ln]) { prev = ln; }
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -22,18 +22,17 @@ namespace dsp::demod {
|
||||
dsp::taps::free(filterTaps);
|
||||
}
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass, bool highPass) {
|
||||
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) {
|
||||
_samplerate = samplerate;
|
||||
_bandwidth = bandwidth;
|
||||
_lowPass = lowPass;
|
||||
_highPass = highPass;
|
||||
|
||||
demod.init(NULL, bandwidth / 2.0, _samplerate);
|
||||
loadDummyTaps();
|
||||
fir.init(NULL, filterTaps);
|
||||
|
||||
// Initialize taps
|
||||
updateFilter(lowPass, highPass);
|
||||
updateFilter(lowPass);
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
demod.out.free();
|
||||
@@ -49,7 +48,7 @@ namespace dsp::demod {
|
||||
base_type::tempStop();
|
||||
_samplerate = samplerate;
|
||||
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
||||
updateFilter(_lowPass, _highPass);
|
||||
updateFilter(_lowPass);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
@@ -59,19 +58,13 @@ namespace dsp::demod {
|
||||
if (bandwidth == _bandwidth) { return; }
|
||||
_bandwidth = bandwidth;
|
||||
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
||||
updateFilter(_lowPass, _highPass);
|
||||
updateFilter(_lowPass);
|
||||
}
|
||||
|
||||
void setLowPass(bool lowPass) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
updateFilter(lowPass, _highPass);
|
||||
}
|
||||
|
||||
void setHighPass(bool highPass) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
updateFilter(_lowPass, highPass);
|
||||
updateFilter(lowPass);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
@@ -86,14 +79,14 @@ namespace dsp::demod {
|
||||
inline int process(int count, dsp::complex_t* in, T* out) {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
demod.process(count, in, out);
|
||||
if (filtering) {
|
||||
if (_lowPass) {
|
||||
std::lock_guard<std::mutex> lck(filterMtx);
|
||||
fir.process(count, out, out);
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
demod.process(count, in, demod.out.writeBuf);
|
||||
if (filtering) {
|
||||
if (_lowPass) {
|
||||
std::lock_guard<std::mutex> lck(filterMtx);
|
||||
fir.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
||||
}
|
||||
@@ -114,25 +107,17 @@ namespace dsp::demod {
|
||||
}
|
||||
|
||||
private:
|
||||
void updateFilter(bool lowPass, bool highPass) {
|
||||
void updateFilter(bool lowPass) {
|
||||
std::lock_guard<std::mutex> lck(filterMtx);
|
||||
|
||||
// Update values
|
||||
_lowPass = lowPass;
|
||||
_highPass = highPass;
|
||||
filtering = (lowPass || highPass);
|
||||
|
||||
// Free filter taps
|
||||
dsp::taps::free(filterTaps);
|
||||
|
||||
// Generate filter depending on low and high pass settings
|
||||
if (_lowPass && _highPass) {
|
||||
filterTaps = dsp::taps::bandPass<float>(300.0, _bandwidth / 2.0, 100.0, _samplerate);
|
||||
}
|
||||
else if (_highPass) {
|
||||
filterTaps = dsp::taps::highPass(300.0, 100.0, _samplerate);
|
||||
}
|
||||
else if (_lowPass) {
|
||||
// Generate filter depending on the low pass settings
|
||||
if (_lowPass) {
|
||||
filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
|
||||
}
|
||||
else {
|
||||
@@ -152,7 +137,6 @@ namespace dsp::demod {
|
||||
double _samplerate;
|
||||
double _bandwidth;
|
||||
bool _lowPass;
|
||||
bool _highPass;
|
||||
bool filtering;
|
||||
|
||||
Quadrature demod;
|
||||
|
||||
303
core/src/dsp/noise_reduction/ctcss_squelch.h
Normal file
303
core/src/dsp/noise_reduction/ctcss_squelch.h
Normal file
@@ -0,0 +1,303 @@
|
||||
#pragma once
|
||||
#include "../channel/rx_vfo.h"
|
||||
#include "../demod/quadrature.h"
|
||||
#include "../filter/fir.h"
|
||||
#include "../taps/high_pass.h"
|
||||
#include <fftw3.h>
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
|
||||
#define CTCSS_DECODE_SAMPLERATE 500//250.0
|
||||
#define CTCSS_DECODE_BANDWIDTH 200.0
|
||||
#define CTCSS_DECODE_OFFSET 160.55
|
||||
|
||||
namespace dsp::noise_reduction {
|
||||
enum CTCSSTone {
|
||||
/**
|
||||
* Indicates that any valid tone will let audio through.
|
||||
*/
|
||||
CTCSS_TONE_ANY = -2,
|
||||
|
||||
/**
|
||||
* Indicates that no tone is being received, or to act as a decoder only, letting audio through continuously.
|
||||
*/
|
||||
CTCSS_TONE_NONE = -1,
|
||||
|
||||
/**
|
||||
* CTCSS Tone Frequency.
|
||||
*/
|
||||
CTCSS_TONE_67Hz,
|
||||
CTCSS_TONE_69_3Hz,
|
||||
CTCSS_TONE_71_9Hz,
|
||||
CTCSS_TONE_74_4Hz,
|
||||
CTCSS_TONE_77Hz,
|
||||
CTCSS_TONE_79_7Hz,
|
||||
CTCSS_TONE_82_5Hz,
|
||||
CTCSS_TONE_85_4Hz,
|
||||
CTCSS_TONE_88_5Hz,
|
||||
CTCSS_TONE_91_5Hz,
|
||||
CTCSS_TONE_94_8Hz,
|
||||
CTCSS_TONE_97_4Hz,
|
||||
CTCSS_TONE_100Hz,
|
||||
CTCSS_TONE_103_5Hz,
|
||||
CTCSS_TONE_107_2Hz,
|
||||
CTCSS_TONE_110_9Hz,
|
||||
CTCSS_TONE_114_8Hz,
|
||||
CTCSS_TONE_118_8Hz,
|
||||
CTCSS_TONE_123Hz,
|
||||
CTCSS_TONE_127_3Hz,
|
||||
CTCSS_TONE_131_8Hz,
|
||||
CTCSS_TONE_136_5Hz,
|
||||
CTCSS_TONE_141_3Hz,
|
||||
CTCSS_TONE_146_2Hz,
|
||||
CTCSS_TONE_150Hz,
|
||||
CTCSS_TONE_151_4Hz,
|
||||
CTCSS_TONE_156_7Hz,
|
||||
CTCSS_TONE_159_8Hz,
|
||||
CTCSS_TONE_162_2Hz,
|
||||
CTCSS_TONE_165_5Hz,
|
||||
CTCSS_TONE_167_9Hz,
|
||||
CTCSS_TONE_171_3Hz,
|
||||
CTCSS_TONE_173_8Hz,
|
||||
CTCSS_TONE_177_3Hz,
|
||||
CTCSS_TONE_179_9Hz,
|
||||
CTCSS_TONE_183_5Hz,
|
||||
CTCSS_TONE_186_2Hz,
|
||||
CTCSS_TONE_189_9Hz,
|
||||
CTCSS_TONE_192_8Hz,
|
||||
CTCSS_TONE_196_6Hz,
|
||||
CTCSS_TONE_199_5Hz,
|
||||
CTCSS_TONE_203_5Hz,
|
||||
CTCSS_TONE_206_5Hz,
|
||||
CTCSS_TONE_210_7Hz,
|
||||
CTCSS_TONE_218_1Hz,
|
||||
CTCSS_TONE_225_7Hz,
|
||||
CTCSS_TONE_229_1Hz,
|
||||
CTCSS_TONE_233_6Hz,
|
||||
CTCSS_TONE_241_8Hz,
|
||||
CTCSS_TONE_250_3Hz,
|
||||
CTCSS_TONE_254_1Hz,
|
||||
_CTCSS_TONE_COUNT
|
||||
};
|
||||
|
||||
const float CTCSS_TONES[_CTCSS_TONE_COUNT] = {
|
||||
67.0f,
|
||||
69.3f,
|
||||
71.9f,
|
||||
74.4f,
|
||||
77.0f,
|
||||
79.7f,
|
||||
82.5f,
|
||||
85.4f,
|
||||
88.5f,
|
||||
91.5f,
|
||||
94.8f,
|
||||
97.4f,
|
||||
100.0f,
|
||||
103.5f,
|
||||
107.2f,
|
||||
110.9f,
|
||||
114.8f,
|
||||
118.8f,
|
||||
123.0f,
|
||||
127.3f,
|
||||
131.8f,
|
||||
136.5f,
|
||||
141.3f,
|
||||
146.2f,
|
||||
150.0f,
|
||||
151.4f,
|
||||
156.7f,
|
||||
159.8f,
|
||||
162.2f,
|
||||
165.5f,
|
||||
167.9f,
|
||||
171.3f,
|
||||
173.8f,
|
||||
177.3f,
|
||||
179.9f,
|
||||
183.5f,
|
||||
186.2f,
|
||||
189.9f,
|
||||
192.8f,
|
||||
196.6f,
|
||||
199.5f,
|
||||
203.5f,
|
||||
206.5f,
|
||||
210.7f,
|
||||
218.1f,
|
||||
225.7f,
|
||||
229.1f,
|
||||
233.6f,
|
||||
241.8f,
|
||||
250.3f,
|
||||
254.1f
|
||||
};
|
||||
|
||||
class CTCSSSquelch : public Processor<stereo_t, stereo_t> {
|
||||
using base_type = Processor<stereo_t, stereo_t>;
|
||||
public:
|
||||
CTCSSSquelch() {}
|
||||
|
||||
CTCSSSquelch(stream<stereo_t>* in, double samplerate) { init(in, samplerate); }
|
||||
|
||||
~CTCSSSquelch() {
|
||||
// If not initialized, do nothing
|
||||
if (!base_type::_block_init) { return; }
|
||||
|
||||
// Stop the DSP thread
|
||||
base_type::stop();
|
||||
}
|
||||
|
||||
void init(stream<stereo_t>* in, double samplerate) {
|
||||
// Save settings
|
||||
_samplerate = samplerate;
|
||||
|
||||
// Create dummy taps just for initialization
|
||||
float dummy[1] = { 1.0f };
|
||||
auto dummyTaps = dsp::taps::fromArray(1, dummy);
|
||||
|
||||
// Initialize the DDC and FM demod
|
||||
ddc.init(NULL, samplerate, CTCSS_DECODE_SAMPLERATE, CTCSS_DECODE_BANDWIDTH, CTCSS_DECODE_OFFSET);
|
||||
fm.init(NULL, 1.0, CTCSS_DECODE_SAMPLERATE);
|
||||
|
||||
// Initilize the base block class
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setSamplerate(double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_samplerate = samplerate;
|
||||
ddc.setInSamplerate(samplerate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setRequiredTone(CTCSSTone tone) {
|
||||
assert(base_type::_block_init);
|
||||
requiredTone = tone;
|
||||
}
|
||||
|
||||
CTCSSTone getCurrentTone() {
|
||||
assert(base_type::_block_init);
|
||||
return currentTone;
|
||||
}
|
||||
|
||||
inline int process(int count, const stereo_t* in, stereo_t* out) {
|
||||
// Shift and resample to the correct samplerate
|
||||
int ddcOutCount = ddc.process(count, (complex_t*)in, ddc.out.writeBuf);
|
||||
|
||||
// FM Demod the CTCSS tone
|
||||
fm.process(ddcOutCount, ddc.out.writeBuf, fm.out.writeBuf);
|
||||
|
||||
// Get the required tone
|
||||
const CTCSSTone rtone = requiredTone;
|
||||
|
||||
// Detect the tone frequency
|
||||
for (int i = 0; i < ddcOutCount; i++) {
|
||||
// Compute the running mean
|
||||
const float val = fm.out.writeBuf[i];
|
||||
mean = 0.95f*mean + 0.05f*val;
|
||||
|
||||
// Compute the running variance
|
||||
const float err = val - mean;
|
||||
var = 0.95f*var + 0.05f*err*err;
|
||||
|
||||
// Run a schmitt trigger on the variance
|
||||
bool nvarOk = varOk ? (var < 1100.0f) : (var < 1000.0f);
|
||||
|
||||
// Check if the tone has to be rematched
|
||||
if (nvarOk && (!varOk || mean < minFreq || mean > maxFreq)) {
|
||||
// Compute the absolute frequency
|
||||
float freq = mean + CTCSS_DECODE_OFFSET;
|
||||
|
||||
// Check it against the known tones
|
||||
if (freq < CTCSS_TONES[0] - 2.5) {
|
||||
currentTone = CTCSS_TONE_NONE;
|
||||
}
|
||||
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1] + 2.5) {
|
||||
currentTone = CTCSS_TONE_NONE;
|
||||
}
|
||||
else if (freq < CTCSS_TONES[0]) {
|
||||
currentTone = (CTCSSTone)0;
|
||||
}
|
||||
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1]) {
|
||||
currentTone = (CTCSSTone)(_CTCSS_TONE_COUNT-1);
|
||||
}
|
||||
else {
|
||||
int a = 0;
|
||||
int b = _CTCSS_TONE_COUNT-1;
|
||||
while (b - a > 1) {
|
||||
int c = (a + b) >> 1;
|
||||
((CTCSS_TONES[c] < freq) ? a : b) = c;
|
||||
}
|
||||
currentTone = (CTCSSTone)((freq - CTCSS_TONES[a] < CTCSS_TONES[b] - freq) ? a : b);
|
||||
}
|
||||
|
||||
// Update the mute status
|
||||
mute = !(currentTone == rtone || (currentTone != CTCSS_TONE_NONE && rtone == CTCSS_TONE_ANY));
|
||||
|
||||
// Unmuted the audio if needed
|
||||
// TODO
|
||||
|
||||
// Recompute min and max freq if a valid tone is detected
|
||||
if (currentTone != CTCSS_TONE_NONE) {
|
||||
float c = CTCSS_TONES[currentTone];
|
||||
float l = (currentTone > CTCSS_TONE_67Hz) ? CTCSS_TONES[currentTone - 1] : c - 2.5f;
|
||||
float r = (currentTone < CTCSS_TONE_254_1Hz) ? CTCSS_TONES[currentTone + 1] : c + 2.5f;
|
||||
minFreq = (l+c) / 2.0f;
|
||||
maxFreq = (r+c) / 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a rising edge on the variance
|
||||
if (!nvarOk && varOk) {
|
||||
// Mute the audio
|
||||
// TODO
|
||||
mute = true;
|
||||
currentTone = CTCSS_TONE_NONE;
|
||||
}
|
||||
|
||||
// Save the new variance state
|
||||
varOk = nvarOk;
|
||||
}
|
||||
|
||||
// DEBUG ONLY
|
||||
if ((rtone != CTCSS_TONE_NONE) && mute) {
|
||||
memset(out, 0, count * sizeof(stereo_t));
|
||||
}
|
||||
else {
|
||||
memcpy(out, in, count * sizeof(stereo_t));
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
double _samplerate;
|
||||
std::atomic<CTCSSTone> requiredTone = CTCSS_TONE_ANY;
|
||||
|
||||
float mean = 0.0f;
|
||||
float var = 0.0f;
|
||||
bool varOk = false;
|
||||
float minFreq = 0.0f;
|
||||
float maxFreq = 0.0f;
|
||||
bool mute = true;
|
||||
std::atomic<CTCSSTone> currentTone = CTCSS_TONE_NONE;
|
||||
|
||||
channel::RxVFO ddc;
|
||||
demod::Quadrature fm;
|
||||
};
|
||||
}
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
// TODO: Rewrite better!!!!!
|
||||
namespace dsp::noise_reduction {
|
||||
class Squelch : public Processor<complex_t, complex_t> {
|
||||
class PowerSquelch : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t>;
|
||||
public:
|
||||
Squelch() {}
|
||||
PowerSquelch() {}
|
||||
|
||||
Squelch(stream<complex_t>* in, double level) {}
|
||||
PowerSquelch(stream<complex_t>* in, double level) {}
|
||||
|
||||
~Squelch() {
|
||||
~PowerSquelch() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
buffer::free(normBuffer);
|
||||
@@ -31,8 +31,11 @@ namespace dsp::noise_reduction {
|
||||
}
|
||||
|
||||
inline int process(int count, const complex_t* in, complex_t* out) {
|
||||
float sum;
|
||||
// Compute the amplitude of each sample
|
||||
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)in, count);
|
||||
|
||||
// Compute the mean amplitude
|
||||
float sum = 0.0f;
|
||||
volk_32f_accumulator_s32f(&sum, normBuffer, count);
|
||||
sum /= (float)count;
|
||||
|
||||
@@ -46,8 +49,6 @@ namespace dsp::noise_reduction {
|
||||
return count;
|
||||
}
|
||||
|
||||
//DEFAULT_PROC_RUN();
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
@@ -10,6 +10,8 @@ namespace dsp {
|
||||
|
||||
Operator(stream<A>* a, stream<B>* b) { init(a, b); }
|
||||
|
||||
virtual ~Operator() {}
|
||||
|
||||
virtual void init(stream<A>* a, stream<B>* b) {
|
||||
_a = a;
|
||||
_b = b;
|
||||
|
||||
@@ -5,8 +5,6 @@ namespace dsp {
|
||||
template <class T>
|
||||
class Source : public block {
|
||||
public:
|
||||
Source() {}
|
||||
|
||||
Source() { init(); }
|
||||
|
||||
virtual ~Source() {}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
namespace dsp {
|
||||
class untyped_stream {
|
||||
public:
|
||||
virtual ~untyped_stream() {}
|
||||
virtual bool swap(int size) { return false; }
|
||||
virtual int read() { return -1; }
|
||||
virtual void flush() {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <volk/volk.h>
|
||||
#include "../buffer/buffer.h"
|
||||
|
||||
namespace dsp {
|
||||
template<class T>
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace icons {
|
||||
ImTextureID UNMUTED;
|
||||
ImTextureID NORMAL_TUNING;
|
||||
ImTextureID CENTER_TUNING;
|
||||
ImTextureID ALIGN_CENTER;
|
||||
|
||||
GLuint loadTexture(std::string path) {
|
||||
int w, h, n;
|
||||
@@ -46,7 +45,6 @@ namespace icons {
|
||||
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
|
||||
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
|
||||
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
|
||||
ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace icons {
|
||||
extern ImTextureID UNMUTED;
|
||||
extern ImTextureID NORMAL_TUNING;
|
||||
extern ImTextureID CENTER_TUNING;
|
||||
extern ImTextureID ALIGN_CENTER;
|
||||
|
||||
GLuint loadTexture(std::string path);
|
||||
bool load(std::string resDir);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <gui/menus/display.h>
|
||||
#include <gui/menus/bandplan.h>
|
||||
#include <gui/menus/sink.h>
|
||||
#include <gui/menus/streams.h>
|
||||
#include <gui/menus/vfo_color.h>
|
||||
#include <gui/menus/module_manager.h>
|
||||
#include <gui/menus/theme.h>
|
||||
@@ -73,7 +72,6 @@ void MainWindow::init() {
|
||||
|
||||
gui::menu.registerEntry("Source", sourcemenu::draw, NULL);
|
||||
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Streams", streamsmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
|
||||
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
|
||||
@@ -167,7 +165,6 @@ void MainWindow::init() {
|
||||
|
||||
sourcemenu::init();
|
||||
sinkmenu::init();
|
||||
streamsmenu::init();
|
||||
bandplanmenu::init();
|
||||
displaymenu::init();
|
||||
vfo_color_menu::init();
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace displaymenu {
|
||||
std::string colorMapAuthor = "";
|
||||
int selectedWindow = 0;
|
||||
int fftRate = 20;
|
||||
int fftSizeId = 0;
|
||||
int uiScaleId = 0;
|
||||
bool restartRequired = false;
|
||||
bool fftHold = false;
|
||||
@@ -28,34 +29,9 @@ namespace displaymenu {
|
||||
bool snrSmoothing = false;
|
||||
int snrSmoothingSpeed = 20;
|
||||
|
||||
OptionList<int, int> fftSizes;
|
||||
OptionList<float, float> uiScales;
|
||||
|
||||
const int FFTSizes[] = {
|
||||
524288,
|
||||
262144,
|
||||
131072,
|
||||
65536,
|
||||
32768,
|
||||
16384,
|
||||
8192,
|
||||
4096,
|
||||
2048,
|
||||
1024
|
||||
};
|
||||
|
||||
const char* FFTSizesStr = "524288\0"
|
||||
"262144\0"
|
||||
"131072\0"
|
||||
"65536\0"
|
||||
"32768\0"
|
||||
"16384\0"
|
||||
"8192\0"
|
||||
"4096\0"
|
||||
"2048\0"
|
||||
"1024\0";
|
||||
|
||||
int fftSizeId = 0;
|
||||
|
||||
const IQFrontEnd::FFTWindow fftWindowList[] = {
|
||||
IQFrontEnd::FFTWindow::RECTANGULAR,
|
||||
IQFrontEnd::FFTWindow::BLACKMAN,
|
||||
@@ -69,6 +45,18 @@ namespace displaymenu {
|
||||
}
|
||||
|
||||
void init() {
|
||||
// Define FFT sizes
|
||||
fftSizes.define(524288, "524288", 524288);
|
||||
fftSizes.define(262144, "262144", 262144);
|
||||
fftSizes.define(131072, "131072", 131072);
|
||||
fftSizes.define(65536, "65536", 65536);
|
||||
fftSizes.define(32768, "32768", 32768);
|
||||
fftSizes.define(16384, "16384", 16384);
|
||||
fftSizes.define(8192, "8192", 8192);
|
||||
fftSizes.define(4096, "4096", 4096);
|
||||
fftSizes.define(2048, "2048", 2048);
|
||||
fftSizes.define(1024, "1024", 1024);
|
||||
|
||||
showWaterfall = core::configManager.conf["showWaterfall"];
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
std::string colormapName = core::configManager.conf["colorMap"];
|
||||
@@ -90,15 +78,12 @@ namespace displaymenu {
|
||||
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
|
||||
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
|
||||
|
||||
fftSizeId = 3;
|
||||
int fftSize = core::configManager.conf["fftSize"];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (fftSize == FFTSizes[i]) {
|
||||
fftSizeId = i;
|
||||
break;
|
||||
fftSizeId = fftSizes.valueId(65536);
|
||||
int size = core::configManager.conf["fftSize"];
|
||||
if (fftSizes.keyExists(size)) {
|
||||
fftSizeId = fftSizes.keyId(size);
|
||||
}
|
||||
}
|
||||
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
|
||||
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
|
||||
|
||||
fftRate = core::configManager.conf["fftRate"];
|
||||
sigpath::iqFrontEnd.setFFTRate(fftRate);
|
||||
@@ -229,10 +214,10 @@ namespace displaymenu {
|
||||
|
||||
ImGui::LeftLabel("FFT Size");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) {
|
||||
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
|
||||
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) {
|
||||
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftSize"] = FFTSizes[fftSizeId];
|
||||
core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId);
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
#include "streams.h"
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <imgui.h>
|
||||
#include <utils/flog.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/icons.h>
|
||||
#include <utils/optionlist.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
namespace streamsmenu {
|
||||
std::vector<SinkID> sinksToBeRemoved;
|
||||
|
||||
std::recursive_mutex sinkTypesMtx;
|
||||
OptionList<std::string, std::string> sinkTypes;
|
||||
|
||||
std::map<std::string, int> selectedSinkTypeId;
|
||||
std::map<std::string, int> addSinkTypeId;
|
||||
|
||||
int addType = 0;
|
||||
|
||||
void updateSinkTypeList(const std::string& removed = "") {
|
||||
std::lock_guard<std::recursive_mutex> lck1(sinkTypesMtx);
|
||||
auto lck2 = sigpath::streamManager.getSinkTypesLock();
|
||||
const auto& types = sigpath::streamManager.getSinkTypes();
|
||||
sinkTypes.clear();
|
||||
for (const auto& type : types) {
|
||||
if (type == removed) { continue; }
|
||||
sinkTypes.define(type, type, type);
|
||||
}
|
||||
}
|
||||
|
||||
void onSinkProviderRegistered(const std::string& type) {
|
||||
// Update the list
|
||||
updateSinkTypeList();
|
||||
|
||||
// Update the ID of the Add dropdown
|
||||
// TODO
|
||||
|
||||
// Update the selected ID of each drop down
|
||||
// TODO
|
||||
}
|
||||
|
||||
void onSinkProviderUnregister(const std::string& type) {
|
||||
// Update the list
|
||||
updateSinkTypeList(type);
|
||||
|
||||
// Update the ID of the Add dropdown
|
||||
// TODO
|
||||
|
||||
// Update the selected ID of each drop down
|
||||
// TODO
|
||||
}
|
||||
|
||||
void init() {
|
||||
sigpath::streamManager.onSinkProviderRegistered.bind(onSinkProviderRegistered);
|
||||
sigpath::streamManager.onSinkProviderUnregister.bind(onSinkProviderUnregister);
|
||||
updateSinkTypeList();
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
auto lck = sigpath::streamManager.getStreamsLock();
|
||||
const auto& streams = sigpath::streamManager.getStreams();
|
||||
|
||||
int count = 0;
|
||||
int maxCount = streams.size();
|
||||
for (auto& [name, stream] : streams) {
|
||||
// Stream name
|
||||
ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f));
|
||||
ImGui::Text("%s", name.c_str());
|
||||
|
||||
// Display ever sink
|
||||
if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders)) {
|
||||
auto lck2 = stream->getSinksLock();
|
||||
auto sinks = stream->getSinks();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
std::string sid = sink->getStringID();
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
float tableWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Sink type
|
||||
int ttttt = 0;
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(CONCAT("##sdrpp_streams_type_", sid), &ttttt, sinkTypes.txt)) {
|
||||
|
||||
}
|
||||
|
||||
sink->showMenu();
|
||||
float vol = sink->getVolume();
|
||||
bool muted = sink->getMuted();
|
||||
float pan = sink->getPanning();
|
||||
bool linked = true;
|
||||
|
||||
float height = ImGui::GetTextLineHeightWithSpacing() + 2;
|
||||
ImGui::PushID(ImGui::GetID(("sdrpp_streams_center_btn_" + sid).c_str()));
|
||||
if (ImGui::ImageButton(icons::ALIGN_CENTER, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
sink->setPanning(0.0f);
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_pan_", sid), &pan, -1, 1, "")) {
|
||||
sink->setPanning(pan);
|
||||
}
|
||||
|
||||
if (muted) {
|
||||
ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + sid).c_str()));
|
||||
if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
sink->setMuted(false);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
else {
|
||||
ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + sid).c_str()));
|
||||
if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
sink->setMuted(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_vol_", sid), &vol, 0, 1, "")) {
|
||||
sink->setVolume(vol);
|
||||
}
|
||||
|
||||
|
||||
int startCur = ImGui::GetCursorPosX();
|
||||
if (ImGui::Checkbox(CONCAT("Link volume##sdrpp_streams_vol_", sid), &linked)) {
|
||||
// TODO
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", sid), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
|
||||
sinksToBeRemoved.push_back(id);
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
lck2.unlock();
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
float tableWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::Spacing();
|
||||
int startCur = ImGui::GetCursorPosX();
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx);
|
||||
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &addType, sinkTypes.txt);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
|
||||
stream->addSink(sinkTypes.value(addType));
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
|
||||
// Remove sinks that need to be removed
|
||||
if (!sinksToBeRemoved.empty()) {
|
||||
for (auto& id : sinksToBeRemoved) {
|
||||
stream->removeSink(id);
|
||||
}
|
||||
sinksToBeRemoved.clear();
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count < maxCount) {
|
||||
ImGui::Spacing();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace streamsmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
|
||||
class Instance {
|
||||
public:
|
||||
virtual ~Instance() {}
|
||||
virtual void postInit() = 0;
|
||||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
|
||||
@@ -5,5 +5,4 @@ namespace sigpath {
|
||||
VFOManager vfoManager;
|
||||
SourceManager sourceManager;
|
||||
SinkManager sinkManager;
|
||||
StreamManager streamManager;
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "vfo_manager.h"
|
||||
#include "source.h"
|
||||
#include "sink.h"
|
||||
#include "stream.h"
|
||||
#include <module.h>
|
||||
|
||||
namespace sigpath {
|
||||
@@ -11,5 +10,4 @@ namespace sigpath {
|
||||
SDRPP_EXPORT VFOManager vfoManager;
|
||||
SDRPP_EXPORT SourceManager sourceManager;
|
||||
SDRPP_EXPORT SinkManager sinkManager;
|
||||
SDRPP_EXPORT StreamManager streamManager;
|
||||
};
|
||||
@@ -85,8 +85,8 @@ 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);
|
||||
onRetune.emit(freq);
|
||||
selectedHandler->tuneHandler(abs(((tuneMode == TuningMode::NORMAL) ? (freq + tuneOffset) : ifFreq)), selectedHandler->ctx);
|
||||
onRetune.emit(freq + tuneOffset);
|
||||
currentFreq = freq;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,519 +0,0 @@
|
||||
#include "stream.h"
|
||||
#include <utils/flog.h>
|
||||
|
||||
Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
|
||||
entry(entry),
|
||||
stream(stream),
|
||||
streamName(name),
|
||||
id(id),
|
||||
stringId(stringId)
|
||||
{}
|
||||
|
||||
void Sink::showMenu() {}
|
||||
|
||||
SinkEntry::SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate) :
|
||||
manager(manager),
|
||||
parentStream(parentStream),
|
||||
id(id)
|
||||
{
|
||||
this->type = type;
|
||||
this->inputSamplerate = inputSamplerate;
|
||||
|
||||
// Generate string ID
|
||||
stringId = parentStream->getName();
|
||||
char buf[16];
|
||||
sprintf(buf, "%d", (int)id);
|
||||
stringId += buf;
|
||||
|
||||
// Initialize DSP
|
||||
resamp.init(&input, inputSamplerate, inputSamplerate);
|
||||
volumeAdjust.init(&resamp.out, 1.0f, false);
|
||||
|
||||
// Initialize the sink
|
||||
setType(type);
|
||||
}
|
||||
|
||||
std::string SinkEntry::getType() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return type;
|
||||
}
|
||||
|
||||
void SinkEntry::setType(const std::string& type) {
|
||||
// Get unique lock on the entry
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
|
||||
// Delete existing sink
|
||||
if (sink) {
|
||||
provider->destroySink(std::move(sink));
|
||||
}
|
||||
|
||||
// Get shared lock on sink types
|
||||
auto lck2 = manager->getSinkTypesLock();
|
||||
|
||||
// Get the provider or throw error
|
||||
const auto& types = manager->getSinkTypes();
|
||||
if (std::find(types.begin(), types.end(), type) == types.end()) {
|
||||
this->type.clear();
|
||||
throw SinkEntryCreateException("Invalid sink type");
|
||||
}
|
||||
|
||||
// Create sink
|
||||
this->type = type;
|
||||
provider = manager->providers[type];
|
||||
sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id, stringId);
|
||||
}
|
||||
|
||||
SinkID SinkEntry::getID() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
float SinkEntry::getVolume() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return volume;
|
||||
}
|
||||
|
||||
void SinkEntry::setVolume(float volume) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
this->volume = volume;
|
||||
volumeAdjust.setVolume(volume);
|
||||
onVolumeChanged(volume);
|
||||
}
|
||||
|
||||
bool SinkEntry::getMuted() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return muted;
|
||||
}
|
||||
|
||||
void SinkEntry::setMuted(bool muted) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
this->muted = muted;
|
||||
volumeAdjust.setMuted(muted);
|
||||
onMutedChanged(muted);
|
||||
}
|
||||
|
||||
float SinkEntry::getPanning() const {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
return panning;
|
||||
}
|
||||
|
||||
void SinkEntry::setPanning(float panning) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
this->panning = panning;
|
||||
// TODO
|
||||
onPanningChanged(panning);
|
||||
}
|
||||
|
||||
void SinkEntry::showMenu() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
sink->showMenu();
|
||||
}
|
||||
|
||||
void SinkEntry::startSink() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
sink->start();
|
||||
}
|
||||
|
||||
void SinkEntry::stopSink() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
sink->stop();
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> SinkEntry::getLock() const {
|
||||
return std::lock_guard<std::recursive_mutex>(mtx);
|
||||
}
|
||||
|
||||
void SinkEntry::setSamplerate(double samplerate) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.setOutSamplerate(samplerate);
|
||||
}
|
||||
|
||||
void SinkEntry::startDSP() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.start();
|
||||
volumeAdjust.start();
|
||||
}
|
||||
|
||||
void SinkEntry::stopDSP() {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.stop();
|
||||
volumeAdjust.stop();
|
||||
}
|
||||
|
||||
void SinkEntry::destroy(bool forgetSettings) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
if (sink) {
|
||||
provider->destroySink(std::move(sink));
|
||||
}
|
||||
type.clear();
|
||||
}
|
||||
|
||||
void SinkEntry::setInputSamplerate(double samplerate) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
resamp.setInSamplerate(samplerate);
|
||||
}
|
||||
|
||||
std::string SinkEntry::getStringID() const {
|
||||
return stringId;
|
||||
}
|
||||
|
||||
Stream::Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
||||
manager(manager),
|
||||
name(name)
|
||||
{
|
||||
this->samplerate = samplerate;
|
||||
|
||||
// Initialize DSP
|
||||
split.init(stream);
|
||||
}
|
||||
|
||||
Stream::~Stream() {
|
||||
// Copy sink IDs
|
||||
std::vector<SinkID> ids;
|
||||
for (auto& [id, sink] : sinks) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
|
||||
// Remove them all
|
||||
for (auto& id : ids) {
|
||||
removeSink(id, false);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& Stream::getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
SinkID Stream::addSink(const std::string& type, SinkID id) {
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Find a free ID if not provided
|
||||
if (id < 0) {
|
||||
for (id = 0; sinks.find(id) != sinks.end(); id++);
|
||||
}
|
||||
else {
|
||||
// Check that the provided ID is valid
|
||||
if (sinks.find(id) != sinks.end()) {
|
||||
flog::error("Tried to create sink for stream '{}' with existing ID: {}", name, id);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create sink entry
|
||||
std::shared_ptr<SinkEntry> sink;
|
||||
try {
|
||||
sink = std::make_shared<SinkEntry>(manager, this, type, id, samplerate);
|
||||
}
|
||||
catch (SinkEntryCreateException e) {
|
||||
flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start the sink and DSP
|
||||
sink->startSink();
|
||||
if (running) { sink->startDSP(); }
|
||||
|
||||
// Bind the sinks's input
|
||||
split.bindStream(&sink->input);
|
||||
|
||||
// Add sink to list
|
||||
sinks[id] = sink;
|
||||
|
||||
// Release lock and emit event
|
||||
lck.unlock();
|
||||
onSinkAdded(sink);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Stream::removeSink(SinkID id, bool forgetSettings) {
|
||||
// Acquire shared lock
|
||||
std::shared_ptr<SinkEntry> sink;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check that the ID exists
|
||||
if (sinks.find(id) == sinks.end()) {
|
||||
flog::error("Tried to remove sink with unknown ID: {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get sink
|
||||
sink = sinks[id];
|
||||
}
|
||||
|
||||
// Emit event
|
||||
onSinkRemove(sink);
|
||||
|
||||
// Acquire unique lock
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check that it's still in the list
|
||||
if (sinks.find(id) == sinks.end()) {
|
||||
flog::error("Tried to remove sink with unknown ID: {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from list
|
||||
sinks.erase(id);
|
||||
|
||||
// Unbind the sink's steam
|
||||
split.unbindStream(&sink->input);
|
||||
|
||||
// Stop the sink and DSP
|
||||
sink->stopDSP();
|
||||
sink->stopSink();
|
||||
|
||||
// Delete instance
|
||||
sink->destroy(forgetSettings);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> Stream::getSinksLock() {
|
||||
return std::shared_lock<std::shared_mutex>(sinksMtx);
|
||||
}
|
||||
|
||||
const std::map<SinkID, std::shared_ptr<SinkEntry>>& Stream::getSinks() const {
|
||||
return sinks;
|
||||
}
|
||||
|
||||
MasterStream::MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
|
||||
Stream(manager, name, stream, samplerate)
|
||||
{}
|
||||
|
||||
void MasterStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) {
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// If all that's needed is to set the input, do it and return
|
||||
if (samplerate == 0.0) {
|
||||
split.setInput(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update samplerate
|
||||
this->samplerate = samplerate;
|
||||
|
||||
// Stop DSP
|
||||
if (running) {
|
||||
split.stop();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->stopDSP();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set input and samplerate
|
||||
split.setInput(stream);
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->setInputSamplerate(samplerate);
|
||||
}
|
||||
|
||||
// Start DSP
|
||||
if (running) {
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->startDSP();
|
||||
}
|
||||
split.start();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterStream::setSamplerate(double samplerate) {
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Update samplerate
|
||||
this->samplerate = samplerate;
|
||||
|
||||
// TODO: Maybe simply disallow while running?
|
||||
|
||||
// Stop DSP if it was running
|
||||
if (running) {
|
||||
split.stop();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->stopDSP();
|
||||
}
|
||||
}
|
||||
|
||||
// Set samplerate
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->setInputSamplerate(samplerate);
|
||||
}
|
||||
|
||||
// Start DSP if it was running
|
||||
if (running) {
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->startDSP();
|
||||
}
|
||||
split.start();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterStream::startDSP() {
|
||||
// TODO: Maybe add a different mutex for the stream?
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check if already running
|
||||
if (running) { return; }
|
||||
|
||||
// Start all DSP
|
||||
split.start();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->startDSP();
|
||||
}
|
||||
running = true;
|
||||
}
|
||||
|
||||
void MasterStream::stopDSP() {
|
||||
// TODO: Maybe add a different mutex for the stream?
|
||||
std::unique_lock<std::shared_mutex> lck(sinksMtx);
|
||||
|
||||
// Check if already running
|
||||
if (!running) { return; }
|
||||
|
||||
// Start all DSP
|
||||
split.stop();
|
||||
for (auto& [id, sink] : sinks) {
|
||||
sink->stopDSP();
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
|
||||
std::shared_ptr<MasterStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) {
|
||||
std::unique_lock<std::shared_mutex> lck(streamsMtx);
|
||||
|
||||
// Check that no stream with that name already exists
|
||||
if (streams.find(name) != streams.end()) {
|
||||
flog::error("Tried to created stream with an existing name: {}", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create and save stream
|
||||
std::shared_ptr<MasterStream> newStream(new MasterStream(this, name, stream, samplerate));
|
||||
streams[name] = newStream;
|
||||
|
||||
// Release lock and emit event
|
||||
lck.unlock();
|
||||
onStreamCreated(newStream);
|
||||
|
||||
return newStream;
|
||||
}
|
||||
|
||||
void StreamManager::destroyStream(std::shared_ptr<MasterStream>& stream) {
|
||||
// Emit event
|
||||
onStreamDestroy(stream);
|
||||
|
||||
// Aquire complete lock on the stream list
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lck(streamsMtx);
|
||||
|
||||
// Get iterator of the stream
|
||||
auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<Stream>> e) {
|
||||
return e.second == stream;
|
||||
});
|
||||
if (it == streams.end()) {
|
||||
flog::error("Tried to delete a stream using an invalid pointer. Stream not found in list");
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete entry from list
|
||||
flog::debug("Stream pointer uses, should be 2 and is {}", (int)stream.use_count());
|
||||
streams.erase(it);
|
||||
}
|
||||
|
||||
// Reset passed pointer
|
||||
stream.reset();
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> StreamManager::getStreamsLock() {
|
||||
return std::shared_lock<std::shared_mutex>(streamsMtx);
|
||||
}
|
||||
|
||||
const std::map<std::string, std::shared_ptr<Stream>>& StreamManager::getStreams() const {
|
||||
return streams;
|
||||
}
|
||||
|
||||
void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) {
|
||||
std::unique_lock<std::shared_mutex> lck(providersMtx);
|
||||
|
||||
// Check that a provider with that name doesn't already exist
|
||||
if (providers.find(name) != providers.end()) {
|
||||
flog::error("Tried to register a sink provider with an existing name: {}", name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add provider to the list and sort name list
|
||||
providers[name] = provider;
|
||||
sinkTypes.push_back(name);
|
||||
std::sort(sinkTypes.begin(), sinkTypes.end());
|
||||
|
||||
// Release lock and emit event
|
||||
lck.unlock();
|
||||
onSinkProviderRegistered(name);
|
||||
}
|
||||
|
||||
void StreamManager::unregisterSinkProvider(SinkProvider* provider) {
|
||||
// Get provider name for event
|
||||
std::string type;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lck(providersMtx);
|
||||
auto it = std::find_if(providers.begin(), providers.end(), [&provider](std::pair<const std::string, SinkProvider *> e) {
|
||||
return e.second == provider;
|
||||
});
|
||||
if (it == providers.end()) {
|
||||
flog::error("Tried to unregister sink provider using invalid pointer");
|
||||
return;
|
||||
}
|
||||
type = (*it).first;
|
||||
}
|
||||
|
||||
// Emit event
|
||||
onSinkProviderUnregister(type);
|
||||
|
||||
// Acquire shared lock on streams
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lck1(providersMtx);
|
||||
std::shared_lock<std::shared_mutex> lck2(streamsMtx);
|
||||
for (auto& [name, stream] : streams) {
|
||||
// Aquire lock on sink list
|
||||
auto sLock = stream->getSinksLock();
|
||||
const auto& sinks = stream->getSinks();
|
||||
|
||||
// Find all sinks with the type that is about to be removed
|
||||
std::vector<SinkID> toRemove;
|
||||
for (auto& [id, sink] : sinks) {
|
||||
if (sink->getType() != type) { continue; }
|
||||
toRemove.push_back(id);
|
||||
}
|
||||
|
||||
// Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING)
|
||||
sLock.unlock();
|
||||
for (auto& id : toRemove) {
|
||||
stream->removeSink(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from the lists
|
||||
if (providers.find(type) != providers.end()) {
|
||||
providers.erase(type);
|
||||
}
|
||||
else {
|
||||
flog::error("Could not remove sink provider from list");
|
||||
}
|
||||
|
||||
auto it = std::find(sinkTypes.begin(), sinkTypes.end(), type);
|
||||
if (it != sinkTypes.end()) {
|
||||
sinkTypes.erase(it);
|
||||
}
|
||||
else {
|
||||
flog::error("Could not remove sink provider from sink type list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> StreamManager::getSinkTypesLock() {
|
||||
return std::shared_lock<std::shared_mutex>(providersMtx);
|
||||
}
|
||||
|
||||
const std::vector<std::string>& StreamManager::getSinkTypes() const {
|
||||
// TODO: This allows code to modify the names...
|
||||
return sinkTypes;
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/routing/splitter.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/audio/volume.h>
|
||||
#include <utils/new_event.h>
|
||||
#include <shared_mutex>
|
||||
#include <stdexcept>
|
||||
|
||||
class SinkEntry;
|
||||
class Stream;
|
||||
class MasterStream;
|
||||
class StreamManager;
|
||||
|
||||
using SinkID = int;
|
||||
|
||||
class Sink {
|
||||
public:
|
||||
Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId);
|
||||
virtual ~Sink() {}
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void showMenu();
|
||||
|
||||
protected:
|
||||
SinkEntry* const entry;
|
||||
dsp::stream<dsp::stereo_t>* const stream;
|
||||
const std::string streamName;
|
||||
const SinkID id;
|
||||
const std::string stringId;
|
||||
};
|
||||
|
||||
class SinkProvider {
|
||||
friend Sink;
|
||||
public:
|
||||
/**
|
||||
* Create a sink instance.
|
||||
* @param name Name of the audio stream.
|
||||
* @param index Index of the sink in the menu. Should be use to keep settings.
|
||||
*/
|
||||
virtual std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) = 0;
|
||||
|
||||
/**
|
||||
* Destroy a sink instance. This function is so that the provide knows at all times how many instances there are.
|
||||
* @param sink Instance of the sink.
|
||||
*/
|
||||
virtual void destroySink(std::unique_ptr<Sink> sink) {
|
||||
sink.reset();
|
||||
}
|
||||
};
|
||||
|
||||
class SinkEntryCreateException : public std::runtime_error {
|
||||
public:
|
||||
SinkEntryCreateException(const char* what) : std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
// TODO: Would be cool to have data and audio sinks instead of just audio.
|
||||
class SinkEntry {
|
||||
friend Sink;
|
||||
friend Stream;
|
||||
friend MasterStream;
|
||||
public:
|
||||
SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
|
||||
|
||||
/**
|
||||
* Get the type of the sink.
|
||||
* @return Type of the sink.
|
||||
*/
|
||||
std::string getType() const;
|
||||
|
||||
/**
|
||||
* Change the type of the sink.
|
||||
* @param type New sink type.
|
||||
*/
|
||||
void setType(const std::string& type);
|
||||
|
||||
/**
|
||||
* Get the ID of the sink.
|
||||
* @return ID of the sink.
|
||||
*/
|
||||
SinkID getID() const;
|
||||
|
||||
/**
|
||||
* Get sink volume.
|
||||
* @return Volume as value between 0.0 and 1.0.
|
||||
*/
|
||||
float getVolume() const;
|
||||
|
||||
/**
|
||||
* Set sink volume.
|
||||
* @param volume Volume as value between 0.0 and 1.0.
|
||||
*/
|
||||
void setVolume(float volume);
|
||||
|
||||
/**
|
||||
* Check if the sink is muted.
|
||||
* @return True if muted, false if not.
|
||||
*/
|
||||
bool getMuted() const;
|
||||
|
||||
/**
|
||||
* Set wether or not the sink is muted
|
||||
* @param muted True to mute, false to unmute.
|
||||
*/
|
||||
void setMuted(bool muted);
|
||||
|
||||
/**
|
||||
* Get sink panning.
|
||||
* @return Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
|
||||
*/
|
||||
float getPanning() const;
|
||||
|
||||
/**
|
||||
* Set sink panning.
|
||||
* @param panning Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
|
||||
*/
|
||||
void setPanning(float panning);
|
||||
|
||||
/**
|
||||
* Show the sink type-specific menu.
|
||||
*/
|
||||
void showMenu();
|
||||
|
||||
/**
|
||||
* Get the string form ID unique to both the sink and stream. Be used to reference settings.
|
||||
* @return Unique string ID.
|
||||
*/
|
||||
std::string getStringID() const;
|
||||
|
||||
// Emitted when the type of the sink was changed
|
||||
NewEvent<const std::string&> onTypeChanged;
|
||||
// Emmited when volume of the sink was changed
|
||||
NewEvent<float> onVolumeChanged;
|
||||
// Emitted when the muted state of the sink was changed
|
||||
NewEvent<bool> onMutedChanged;
|
||||
// Emitted when the panning of the sink was changed
|
||||
NewEvent<float> onPanningChanged;
|
||||
|
||||
// TODO: Need to allow the sink to change the entry samplerate and start/stop the DSP
|
||||
// This will also require allowing it to get a lock on the sink so others don't attempt to mess with it.
|
||||
std::lock_guard<std::recursive_mutex> getLock() const;
|
||||
void startDSP();
|
||||
void stopDSP();
|
||||
void setSamplerate(double samplerate);
|
||||
|
||||
private:
|
||||
void startSink();
|
||||
void stopSink();
|
||||
|
||||
void destroy(bool forgetSettings);
|
||||
void setInputSamplerate(double samplerate);
|
||||
|
||||
mutable std::recursive_mutex mtx;
|
||||
dsp::stream<dsp::stereo_t> input;
|
||||
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
||||
dsp::audio::Volume volumeAdjust;
|
||||
|
||||
SinkProvider* provider = NULL;
|
||||
std::unique_ptr<Sink> sink;
|
||||
std::string type;
|
||||
const SinkID id;
|
||||
double inputSamplerate;
|
||||
Stream* const parentStream;
|
||||
StreamManager* const manager;
|
||||
|
||||
std::string stringId;
|
||||
|
||||
float volume = 1.0f;
|
||||
bool muted = false;
|
||||
float panning = 0.0f;
|
||||
};
|
||||
|
||||
class Stream {
|
||||
protected:
|
||||
Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
||||
public:
|
||||
~Stream();
|
||||
|
||||
/**
|
||||
* Get the name of the stream.
|
||||
* @return Name of the stream.
|
||||
*/
|
||||
const std::string& getName() const;
|
||||
|
||||
/**
|
||||
* Add a sink to the stream.
|
||||
* @param type Type of the sink.
|
||||
* @param id ID of the sink. Optional, -1 if automatic.
|
||||
* @return ID of the new sink or -1 on error.
|
||||
*/
|
||||
SinkID addSink(const std::string& type, SinkID id = -1);
|
||||
|
||||
/**
|
||||
* Remove a sink from a stream.
|
||||
* @param id ID of the sink.
|
||||
* @param forgetSettings Forget the settings for the sink.
|
||||
*/
|
||||
void removeSink(SinkID id, bool forgetSettings = true);
|
||||
|
||||
/**
|
||||
* Aquire a lock for the sink list.
|
||||
* @return Shared lock for the sink list.
|
||||
*/
|
||||
std::shared_lock<std::shared_mutex> getSinksLock();
|
||||
|
||||
/**
|
||||
* Get the list of all sinks belonging to this stream.
|
||||
* @return Sink list.
|
||||
*/
|
||||
const std::map<SinkID, std::shared_ptr<SinkEntry>>& getSinks() const;
|
||||
|
||||
// Emitted when the samplerate of the stream was changed
|
||||
NewEvent<double> onSamplerateChanged;
|
||||
// Emitted when a sink was added
|
||||
NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded;
|
||||
// Emitted when a sink is being removed
|
||||
NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove;
|
||||
|
||||
protected:
|
||||
StreamManager* const manager;
|
||||
const std::string name;
|
||||
double samplerate;
|
||||
dsp::routing::Splitter<dsp::stereo_t> split;
|
||||
bool running = false;
|
||||
|
||||
std::map<SinkID, std::shared_ptr<SinkEntry>> sinks;
|
||||
std::shared_mutex sinksMtx;
|
||||
};
|
||||
|
||||
class MasterStream : public Stream {
|
||||
friend StreamManager;
|
||||
MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
||||
public:
|
||||
/**
|
||||
* Set DSP stream input.
|
||||
* @param stream DSP stream.
|
||||
* @param samplerate New samplerate (optional, 0.0 if not used).
|
||||
*/
|
||||
void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0);
|
||||
|
||||
/**
|
||||
* Set the samplerate of the input stream.
|
||||
* @param samplerate Samplerate in Hz.
|
||||
*/
|
||||
void setSamplerate(double samplerate);
|
||||
|
||||
/**
|
||||
* Start the DSP.
|
||||
*/
|
||||
void startDSP();
|
||||
|
||||
/**
|
||||
* Stop the DSP.
|
||||
*/
|
||||
void stopDSP();
|
||||
};
|
||||
|
||||
class StreamManager {
|
||||
friend SinkEntry;
|
||||
public:
|
||||
/**
|
||||
* Create an audio stream.
|
||||
* @param name Name of the stream.
|
||||
* @param stream DSP stream that outputs the audio.
|
||||
* @param samplerate Samplerate of the audio data.
|
||||
* @return Audio stream instance.
|
||||
*/
|
||||
std::shared_ptr<MasterStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
|
||||
|
||||
/**
|
||||
* Destroy an audio stream.
|
||||
* @param stream Stream to destroy. The passed shared pointer will be automatically reset.
|
||||
*/
|
||||
void destroyStream(std::shared_ptr<MasterStream>& stream);
|
||||
|
||||
/**
|
||||
* Aquire a lock for the stream list.
|
||||
* @return Shared lock for the stream list.
|
||||
*/
|
||||
std::shared_lock<std::shared_mutex> getStreamsLock();
|
||||
|
||||
/**
|
||||
* Get a list of streams and their associated names.
|
||||
* @return Map of names to stream instance.
|
||||
*/
|
||||
const std::map<std::string, std::shared_ptr<Stream>>& getStreams() const;
|
||||
|
||||
/**
|
||||
* Register a sink provider.
|
||||
* @param name Name of the sink type.
|
||||
* @param provider Sink provider instance.
|
||||
*/
|
||||
void registerSinkProvider(const std::string& name, SinkProvider* provider);
|
||||
|
||||
/**
|
||||
* Unregister a sink provider.
|
||||
* @param name Name of the sink type.
|
||||
*/
|
||||
void unregisterSinkProvider(SinkProvider* provider);
|
||||
|
||||
/**
|
||||
* Aquire a lock for the sink type list.
|
||||
* @return Shared lock for the sink type list.
|
||||
*/
|
||||
std::shared_lock<std::shared_mutex> getSinkTypesLock();
|
||||
|
||||
/**
|
||||
* Get a list of sink types.
|
||||
* @return List of sink type names in alphabetical order.
|
||||
*/
|
||||
const std::vector<std::string>& getSinkTypes() const;
|
||||
|
||||
// Emitted when a stream was created
|
||||
NewEvent<std::shared_ptr<Stream>> onStreamCreated;
|
||||
// Emitted when a stream is about to be destroyed
|
||||
NewEvent<std::shared_ptr<Stream>> onStreamDestroy;
|
||||
// Emitted when a sink provider was registered
|
||||
NewEvent<const std::string&> onSinkProviderRegistered;
|
||||
// Emitted when a sink provider is about to be unregistered
|
||||
NewEvent<const std::string&> onSinkProviderUnregister;
|
||||
|
||||
private:
|
||||
std::map<std::string, std::shared_ptr<Stream>> streams;
|
||||
std::shared_mutex streamsMtx;
|
||||
|
||||
std::map<std::string, SinkProvider*> providers;
|
||||
std::vector<std::string> sinkTypes;
|
||||
std::shared_mutex providersMtx;
|
||||
};
|
||||
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);
|
||||
}
|
||||
@@ -63,7 +63,6 @@ namespace net::http {
|
||||
|
||||
std::string MessageHeader::getField(const std::string name) {
|
||||
// TODO: Check if exists
|
||||
// TODO: Maybe declare the set/get field functions to do type conversions automatically?
|
||||
return fields[name];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_STR "1.1.0"
|
||||
#define VERSION_STR "1.3.0"
|
||||
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];
|
||||
rc++;
|
||||
}
|
||||
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;
|
||||
// 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 {
|
||||
locked = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (++counter >= 100) {
|
||||
counter = 0;
|
||||
//flog::warn("Left: {}, Right: {}, Error: {}, Freq: {}, Phase: {}", left, right, error, pcl.freq, pcl.phase);
|
||||
// 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;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical scan logic
|
||||
_this->ypos++;
|
||||
bool rollover = _this->ypos >= 625;
|
||||
if (rollover) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
|
||||
_this->evenFrame = !_this->evenFrame;
|
||||
// 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();
|
||||
}
|
||||
else {
|
||||
_this->ypos += 2;
|
||||
_this->line++;
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
// 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
|
||||
@@ -20,16 +20,27 @@ enum IFNRPreset {
|
||||
IFNR_PRESET_BROADCAST
|
||||
};
|
||||
|
||||
enum SquelchMode {
|
||||
SQUELCH_MODE_OFF,
|
||||
SQUELCH_MODE_POWER,
|
||||
SQUELCH_MODE_SNR,
|
||||
SQUELCH_MODE_CTCSS_MUTE,
|
||||
SQUELCH_MODE_CTCSS_DECODE,
|
||||
SQUELCH_MODE_DCS_MUTE,
|
||||
SQUELCH_MODE_DCS_DECODE,
|
||||
};
|
||||
|
||||
namespace demod {
|
||||
class Demodulator {
|
||||
public:
|
||||
virtual ~Demodulator() {}
|
||||
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) = 0;
|
||||
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) = 0;
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void showMenu() = 0;
|
||||
virtual void setBandwidth(double bandwidth) = 0;
|
||||
virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0;
|
||||
virtual void AFSampRateChanged(double newSR) = 0;
|
||||
virtual const char* getName() = 0;
|
||||
virtual double getIFSampleRate() = 0;
|
||||
virtual double getAFSampleRate() = 0;
|
||||
@@ -44,6 +55,8 @@ namespace demod {
|
||||
virtual int getDefaultDeemphasisMode() = 0;
|
||||
virtual bool getFMIFNRAllowed() = 0;
|
||||
virtual bool getNBAllowed() = 0;
|
||||
virtual bool getHighPassAllowed() = 0;
|
||||
virtual bool getSquelchAllowed() = 0;
|
||||
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace demod {
|
||||
public:
|
||||
AM() {}
|
||||
|
||||
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~AM() { stop(); }
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@@ -40,6 +40,12 @@ namespace demod {
|
||||
|
||||
void showMenu() {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
|
||||
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
|
||||
_config->release(true);
|
||||
}
|
||||
ImGui::LeftLabel("AGC Attack");
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderFloat(("##_radio_am_agc_attack_" + name).c_str(), &agcAttack, 1.0f, 200.0f)) {
|
||||
@@ -56,18 +62,14 @@ namespace demod {
|
||||
_config->conf[name][getName()]["agcDecay"] = agcDecay;
|
||||
_config->release(true);
|
||||
}
|
||||
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
|
||||
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
|
||||
_config->release(true);
|
||||
}
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) { demod.setBandwidth(bandwidth); }
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "AM"; }
|
||||
@@ -84,6 +86,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getHighPassAllowed() { return true; }
|
||||
bool getSquelchAllowed() { return true; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
CW() {}
|
||||
|
||||
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~CW() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
this->_config = config;
|
||||
this->afbwChangeHandler = afbwChangeHandler;
|
||||
@@ -74,6 +74,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "CW"; }
|
||||
@@ -90,6 +92,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getHighPassAllowed() { return false; }
|
||||
bool getSquelchAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
DSB() {}
|
||||
|
||||
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~DSB() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@@ -61,6 +61,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "DSB"; }
|
||||
@@ -77,6 +79,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getHighPassAllowed() { return true; }
|
||||
bool getSquelchAllowed() { return true; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -7,15 +7,15 @@ namespace demod {
|
||||
public:
|
||||
LSB() {}
|
||||
|
||||
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~LSB() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@@ -61,6 +61,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "LSB"; }
|
||||
@@ -77,6 +79,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getHighPassAllowed() { return true; }
|
||||
bool getSquelchAllowed() { return true; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace demod {
|
||||
public:
|
||||
NFM() {}
|
||||
|
||||
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~NFM() { stop(); }
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
this->_config = config;
|
||||
|
||||
@@ -22,14 +22,11 @@ namespace demod {
|
||||
if (config->conf[name][getName()].contains("lowPass")) {
|
||||
_lowPass = config->conf[name][getName()]["lowPass"];
|
||||
}
|
||||
if (config->conf[name][getName()].contains("highPass")) {
|
||||
_highPass = config->conf[name][getName()]["highPass"];
|
||||
}
|
||||
_config->release();
|
||||
|
||||
|
||||
// Define structure
|
||||
demod.init(input, getIFSampleRate(), bandwidth, _lowPass, _highPass);
|
||||
demod.init(input, getIFSampleRate(), bandwidth, _lowPass);
|
||||
}
|
||||
|
||||
void start() { demod.start(); }
|
||||
@@ -43,12 +40,6 @@ namespace demod {
|
||||
_config->conf[name][getName()]["lowPass"] = _lowPass;
|
||||
_config->release(true);
|
||||
}
|
||||
if (ImGui::Checkbox(("High Pass##_radio_wfm_highpass_" + name).c_str(), &_highPass)) {
|
||||
demod.setHighPass(_highPass);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["highPass"] = _highPass;
|
||||
_config->release(true);
|
||||
}
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) {
|
||||
@@ -57,6 +48,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "FM"; }
|
||||
@@ -73,6 +66,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return true; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getHighPassAllowed() { return true; }
|
||||
bool getSquelchAllowed() { return true; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
@@ -81,7 +76,6 @@ namespace demod {
|
||||
ConfigManager* _config = NULL;
|
||||
|
||||
bool _lowPass = true;
|
||||
bool _highPass = false;
|
||||
|
||||
std::string name;
|
||||
};
|
||||
|
||||
@@ -7,18 +7,17 @@ namespace demod {
|
||||
public:
|
||||
RAW() {}
|
||||
|
||||
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~RAW() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
audioSampleRate = 48000;
|
||||
// TODO: This needs to be selectable
|
||||
audioSampleRate = audioSR;
|
||||
|
||||
// Define structure
|
||||
c2s.init(input);
|
||||
@@ -40,6 +39,10 @@ namespace demod {
|
||||
c2s.setInput(input);
|
||||
}
|
||||
|
||||
void AFSampRateChanged(double newSR) {
|
||||
audioSampleRate = newSR;
|
||||
}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "RAW"; }
|
||||
@@ -56,6 +59,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getHighPassAllowed() { return false; }
|
||||
bool getSquelchAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -8,15 +8,15 @@ namespace demod {
|
||||
public:
|
||||
USB() {}
|
||||
|
||||
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
init(name, config, input, bandwidth);
|
||||
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~USB() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@@ -62,6 +62,8 @@ namespace demod {
|
||||
|
||||
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "USB"; }
|
||||
@@ -78,6 +80,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getHighPassAllowed() { return true; }
|
||||
bool getSquelchAllowed() { return true; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace demod {
|
||||
public:
|
||||
WFM() : diag(0.5, 4096) {}
|
||||
|
||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) {
|
||||
init(name, config, input, bandwidth);
|
||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) {
|
||||
init(name, config, input, bandwidth, audioSR);
|
||||
}
|
||||
|
||||
~WFM() {
|
||||
@@ -25,7 +25,7 @@ namespace demod {
|
||||
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler);
|
||||
}
|
||||
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
|
||||
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
@@ -100,18 +100,18 @@ namespace demod {
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
|
||||
setStereo(_stereo);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["stereo"] = _stereo;
|
||||
_config->release(true);
|
||||
}
|
||||
if (ImGui::Checkbox(("Low Pass##_radio_wfm_lowpass_" + name).c_str(), &_lowPass)) {
|
||||
demod.setLowPass(_lowPass);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["lowPass"] = _lowPass;
|
||||
_config->release(true);
|
||||
}
|
||||
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
|
||||
setStereo(_stereo);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["stereo"] = _stereo;
|
||||
_config->release(true);
|
||||
}
|
||||
if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) {
|
||||
demod.setRDSOut(_rds);
|
||||
_config->acquire();
|
||||
@@ -252,6 +252,8 @@ namespace demod {
|
||||
demod.setInput(input);
|
||||
}
|
||||
|
||||
void AFSampRateChanged(double newSR) {}
|
||||
|
||||
// ============= INFO =============
|
||||
|
||||
const char* getName() { return "WFM"; }
|
||||
@@ -268,6 +270,8 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
|
||||
bool getFMIFNRAllowed() { return true; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getHighPassAllowed() { return true; }
|
||||
bool getSquelchAllowed() { return true; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
// ============= DEDICATED FUNCTIONS =============
|
||||
|
||||
@@ -5,10 +5,14 @@ enum {
|
||||
RADIO_IFACE_CMD_SET_MODE,
|
||||
RADIO_IFACE_CMD_GET_BANDWIDTH,
|
||||
RADIO_IFACE_CMD_SET_BANDWIDTH,
|
||||
RADIO_IFACE_CMD_GET_SQUELCH_ENABLED,
|
||||
RADIO_IFACE_CMD_SET_SQUELCH_ENABLED,
|
||||
RADIO_IFACE_CMD_GET_SQUELCH_MODE,
|
||||
RADIO_IFACE_CMD_SET_SQUELCH_MODE,
|
||||
RADIO_IFACE_CMD_GET_SQUELCH_LEVEL,
|
||||
RADIO_IFACE_CMD_SET_SQUELCH_LEVEL,
|
||||
RADIO_IFACE_CMD_GET_CTCSS_TONE,
|
||||
RADIO_IFACE_CMD_SET_CTCSS_TONE,
|
||||
RADIO_IFACE_CMD_GET_HIGHPASS,
|
||||
RADIO_IFACE_CMD_SET_HIGHPASS
|
||||
};
|
||||
|
||||
enum {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#include <dsp/chain.h>
|
||||
#include <dsp/noise_reduction/noise_blanker.h>
|
||||
#include <dsp/noise_reduction/fm_if.h>
|
||||
#include <dsp/noise_reduction/squelch.h>
|
||||
#include <dsp/noise_reduction/power_squelch.h>
|
||||
#include <dsp/noise_reduction/ctcss_squelch.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/filter/deephasis.h>
|
||||
#include <core.h>
|
||||
@@ -49,6 +50,22 @@ public:
|
||||
ifnrPresets.define("Voice", IFNR_PRESET_VOICE);
|
||||
ifnrPresets.define("Narrow Band", IFNR_PRESET_NARROW_BAND);
|
||||
|
||||
squelchModes.define("off", "Off", SQUELCH_MODE_OFF);
|
||||
squelchModes.define("power", "Power", SQUELCH_MODE_POWER);
|
||||
//squelchModes.define("snr", "SNR", SQUELCH_MODE_SNR);
|
||||
squelchModes.define("ctcss_mute", "CTCSS (Mute)", SQUELCH_MODE_CTCSS_MUTE);
|
||||
squelchModes.define("ctcss_decode", "CTCSS (Decode Only)", SQUELCH_MODE_CTCSS_DECODE);
|
||||
//squelchModes.define("dcs_mute", "DCS (Mute)", SQUELCH_MODE_DCS_MUTE);
|
||||
//squelchModes.define("dcs_decode", "DCS (Decode Only)", SQUELCH_MODE_DCS_DECODE);
|
||||
|
||||
for (int i = 0; i < dsp::noise_reduction::_CTCSS_TONE_COUNT; i++) {
|
||||
float tone = dsp::noise_reduction::CTCSS_TONES[i];
|
||||
char buf[64];
|
||||
sprintf(buf, "%.1fHz", tone);
|
||||
ctcssTones.define((int)round(tone) * 10, buf, (dsp::noise_reduction::CTCSSTone)i);
|
||||
}
|
||||
ctcssTones.define(-1, "Any", dsp::noise_reduction::CTCSS_TONE_ANY);
|
||||
|
||||
// Initialize the config if it doesn't exist
|
||||
bool created = false;
|
||||
config.acquire();
|
||||
@@ -72,25 +89,44 @@ public:
|
||||
|
||||
nb.init(NULL, 500.0 / 24000.0, 10.0);
|
||||
fmnr.init(NULL, 32);
|
||||
squelch.init(NULL, MIN_SQUELCH);
|
||||
powerSquelch.init(NULL, MIN_SQUELCH);
|
||||
|
||||
ifChain.addBlock(&nb, false);
|
||||
ifChain.addBlock(&squelch, false);
|
||||
ifChain.addBlock(&powerSquelch, false);
|
||||
ifChain.addBlock(&fmnr, false);
|
||||
|
||||
// Initialize audio DSP chain
|
||||
afChain.init(&dummyAudioStream);
|
||||
|
||||
ctcss.init(NULL, 50000.0);
|
||||
resamp.init(NULL, 250000.0, 48000.0);
|
||||
hpTaps = dsp::taps::highPass(300.0, 100.0, 48000.0);
|
||||
hpf.init(NULL, hpTaps);
|
||||
deemp.init(NULL, 50e-6, 48000.0);
|
||||
|
||||
afChain.addBlock(&ctcss, false);
|
||||
afChain.addBlock(&resamp, true);
|
||||
afChain.addBlock(&hpf, false);
|
||||
afChain.addBlock(&deemp, false);
|
||||
|
||||
// Initialize the sink
|
||||
stream = sigpath::streamManager.createStream(name, afChain.out, 48000);
|
||||
srChangeHandler.ctx = this;
|
||||
srChangeHandler.handler = sampleRateChangeHandler;
|
||||
stream.init(afChain.out, &srChangeHandler, audioSampleRate);
|
||||
sigpath::sinkManager.registerStream(name, &stream);
|
||||
|
||||
// Select the demodulator
|
||||
selectDemodByID((DemodID)selectedDemodID);
|
||||
|
||||
// Start IF chain
|
||||
ifChain.start();
|
||||
|
||||
// Start AF chain
|
||||
afChain.start();
|
||||
|
||||
// Start stream, the rest was started when selecting the demodulator
|
||||
stream.start();
|
||||
|
||||
// Register the menu
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
|
||||
@@ -101,10 +137,11 @@ public:
|
||||
~RadioModule() {
|
||||
core::modComManager.unregisterInterface(name);
|
||||
gui::menu.removeEntry(name);
|
||||
stream.stop();
|
||||
if (enabled) {
|
||||
disable();
|
||||
}
|
||||
sigpath::streamManager.destroyStream(stream);
|
||||
sigpath::sinkManager.unregisterStream(name);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
@@ -116,7 +153,9 @@ public:
|
||||
vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
|
||||
}
|
||||
ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); });
|
||||
ifChain.start();
|
||||
selectDemodByID((DemodID)selectedDemodID);
|
||||
afChain.start();
|
||||
}
|
||||
|
||||
void disable() {
|
||||
@@ -124,7 +163,6 @@ public:
|
||||
ifChain.stop();
|
||||
if (selectedDemod) { selectedDemod->stop(); }
|
||||
afChain.stop();
|
||||
stream->stopDSP();
|
||||
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
|
||||
vfo = NULL;
|
||||
}
|
||||
@@ -217,6 +255,33 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Squelch
|
||||
if (_this->squelchAllowed) {
|
||||
ImGui::LeftLabel("Squelch Mode");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##_radio_sqelch_mode_" + _this->name).c_str(), &_this->squelchModeId, _this->squelchModes.txt)) {
|
||||
_this->setSquelchMode(_this->squelchModes[_this->squelchModeId]);
|
||||
}
|
||||
switch (_this->squelchModes[_this->squelchModeId]) {
|
||||
case SQUELCH_MODE_POWER:
|
||||
ImGui::LeftLabel("Squelch Level");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::SliderFloat(("##_radio_sqelch_lvl_" + _this->name).c_str(), &_this->squelchLevel, _this->MIN_SQUELCH, _this->MAX_SQUELCH, "%.3fdB")) {
|
||||
_this->setSquelchLevel(_this->squelchLevel);
|
||||
}
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_CTCSS_MUTE:
|
||||
if (_this->squelchModes[_this->squelchModeId] == SQUELCH_MODE_CTCSS_MUTE) {
|
||||
ImGui::LeftLabel("CTCSS Tone");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##_radio_ctcss_tone_" + _this->name).c_str(), &_this->ctcssToneId, _this->ctcssTones.txt)) {
|
||||
_this->setCTCSSTone(_this->ctcssTones[_this->ctcssToneId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Noise blanker
|
||||
if (_this->nbAllowed) {
|
||||
if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) {
|
||||
@@ -231,19 +296,6 @@ private:
|
||||
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
|
||||
// Squelch
|
||||
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
|
||||
_this->setSquelchEnabled(_this->squelchEnabled);
|
||||
}
|
||||
if (!_this->squelchEnabled && _this->enabled) { style::beginDisabled(); }
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderFloat(("##_radio_sqelch_lvl_" + _this->name).c_str(), &_this->squelchLevel, _this->MIN_SQUELCH, _this->MAX_SQUELCH, "%.3fdB")) {
|
||||
_this->setSquelchLevel(_this->squelchLevel);
|
||||
}
|
||||
if (!_this->squelchEnabled && _this->enabled) { style::endDisabled(); }
|
||||
|
||||
// FM IF Noise Reduction
|
||||
if (_this->FMIFNRAllowed) {
|
||||
if (ImGui::Checkbox(("IF Noise Reduction##_radio_fmifnr_ena_" + _this->name).c_str(), &_this->FMIFNREnabled)) {
|
||||
@@ -260,9 +312,53 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// High pass
|
||||
if (_this->highPassAllowed) {
|
||||
if (ImGui::Checkbox(("High Pass##_radio_hpf_" + _this->name).c_str(), &_this->highPass)) {
|
||||
_this->setHighPass(_this->highPass);
|
||||
}
|
||||
}
|
||||
|
||||
// Demodulator specific menu
|
||||
_this->selectedDemod->showMenu();
|
||||
|
||||
// Display the squelch diagnostics
|
||||
switch (_this->squelchModes[_this->squelchModeId]) {
|
||||
case SQUELCH_MODE_CTCSS_MUTE:
|
||||
ImGui::TextUnformatted("Received Tone:");
|
||||
ImGui::SameLine();
|
||||
{
|
||||
auto ctone = _this->ctcss.getCurrentTone();
|
||||
auto dtone = _this->ctcssTones[_this->ctcssToneId];
|
||||
if (ctone != dsp::noise_reduction::CTCSS_TONE_NONE) {
|
||||
if (dtone == dsp::noise_reduction::CTCSS_TONE_ANY || ctone == dtone) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
|
||||
}
|
||||
else {
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::TextUnformatted("None");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_CTCSS_DECODE:
|
||||
ImGui::TextUnformatted("Received Tone:");
|
||||
ImGui::SameLine();
|
||||
{
|
||||
auto ctone = _this->ctcss.getCurrentTone();
|
||||
if (ctone != dsp::noise_reduction::CTCSS_TONE_NONE) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
|
||||
}
|
||||
else {
|
||||
ImGui::TextUnformatted("None");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
@@ -297,7 +393,7 @@ private:
|
||||
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
|
||||
|
||||
// Initialize
|
||||
demod->init(name, &config, ifChain.out, bw);
|
||||
demod->init(name, &config, ifChain.out, bw, stream.getSampleRate());
|
||||
|
||||
return demod;
|
||||
}
|
||||
@@ -321,34 +417,22 @@ private:
|
||||
}
|
||||
|
||||
void selectDemod(demod::Demodulator* demod) {
|
||||
// Stop the IF chain
|
||||
ifChain.stop();
|
||||
|
||||
// Stop the current demodulator
|
||||
// Stopcurrently selected demodulator and select new
|
||||
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
if (selectedDemod) {
|
||||
selectedDemod->stop();
|
||||
}
|
||||
|
||||
// Stop AF chain
|
||||
afChain.stop();
|
||||
|
||||
// Stop audio stream's DSP
|
||||
stream->stopDSP();
|
||||
|
||||
// Destroy the old demodulator
|
||||
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
if (selectedDemod) {
|
||||
delete selectedDemod;
|
||||
}
|
||||
|
||||
// Select the new demodulator
|
||||
selectedDemod = demod;
|
||||
|
||||
// Give the demodulator the most recent audio SR
|
||||
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||
|
||||
// Set the demodulator's input
|
||||
selectedDemod->setInput(ifChain.out);
|
||||
|
||||
// Set AF chain's input
|
||||
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Load config
|
||||
bandwidth = selectedDemod->getDefaultBandwidth();
|
||||
@@ -356,15 +440,20 @@ private:
|
||||
maxBandwidth = selectedDemod->getMaxBandwidth();
|
||||
bandwidthLocked = selectedDemod->getBandwidthLocked();
|
||||
snapInterval = selectedDemod->getDefaultSnapInterval();
|
||||
squelchLevel = MIN_SQUELCH;
|
||||
deempAllowed = selectedDemod->getDeempAllowed();
|
||||
deempId = deempModes.valueId((DeemphasisMode)selectedDemod->getDefaultDeemphasisMode());
|
||||
squelchEnabled = false;
|
||||
squelchModeId = squelchModes.valueId(SQUELCH_MODE_OFF);
|
||||
squelchLevel = MIN_SQUELCH;
|
||||
ctcssToneId = ctcssTones.valueId(dsp::noise_reduction::CTCSS_TONE_67Hz);
|
||||
highPass = false;
|
||||
|
||||
postProcEnabled = selectedDemod->getPostProcEnabled();
|
||||
FMIFNRAllowed = selectedDemod->getFMIFNRAllowed();
|
||||
FMIFNREnabled = false;
|
||||
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
|
||||
nbAllowed = selectedDemod->getNBAllowed();
|
||||
squelchAllowed = selectedDemod->getSquelchAllowed();
|
||||
highPassAllowed = selectedDemod->getHighPassAllowed();
|
||||
nbEnabled = false;
|
||||
nbLevel = 0.0f;
|
||||
double ifSamplerate = selectedDemod->getIFSampleRate();
|
||||
@@ -376,11 +465,24 @@ private:
|
||||
if (config.conf[name][selectedDemod->getName()].contains("snapInterval")) {
|
||||
snapInterval = config.conf[name][selectedDemod->getName()]["snapInterval"];
|
||||
}
|
||||
|
||||
if (config.conf[name][selectedDemod->getName()].contains("squelchMode")) {
|
||||
std::string squelchModeStr = config.conf[name][selectedDemod->getName()]["squelchMode"];
|
||||
if (squelchModes.keyExists(squelchModeStr)) {
|
||||
squelchModeId = squelchModes.keyId(squelchModeStr);
|
||||
}
|
||||
}
|
||||
if (config.conf[name][selectedDemod->getName()].contains("squelchLevel")) {
|
||||
squelchLevel = config.conf[name][selectedDemod->getName()]["squelchLevel"];
|
||||
}
|
||||
if (config.conf[name][selectedDemod->getName()].contains("squelchEnabled")) {
|
||||
squelchEnabled = config.conf[name][selectedDemod->getName()]["squelchEnabled"];
|
||||
if (config.conf[name][selectedDemod->getName()].contains("ctcssTone")) {
|
||||
int ctcssToneX10 = config.conf[name][selectedDemod->getName()]["ctcssTone"];
|
||||
if (ctcssTones.keyExists(ctcssToneX10)) {
|
||||
ctcssToneId = ctcssTones.keyId(ctcssToneX10);
|
||||
}
|
||||
}
|
||||
if (config.conf[name][selectedDemod->getName()].contains("highPass")) {
|
||||
highPass = config.conf[name][selectedDemod->getName()]["highPass"];
|
||||
}
|
||||
if (config.conf[name][selectedDemod->getName()].contains("deempMode")) {
|
||||
if (!config.conf[name][selectedDemod->getName()]["deempMode"].is_string()) {
|
||||
@@ -430,36 +532,33 @@ private:
|
||||
setFMIFNREnabled(FMIFNRAllowed ? FMIFNREnabled : false);
|
||||
|
||||
// Configure squelch
|
||||
setSquelchMode(squelchAllowed ? squelchModes[squelchModeId] : SQUELCH_MODE_OFF);
|
||||
setSquelchLevel(squelchLevel);
|
||||
setSquelchEnabled(squelchEnabled);
|
||||
setCTCSSTone(ctcssTones[ctcssToneId]);
|
||||
|
||||
// Configure AF chain
|
||||
if (postProcEnabled) {
|
||||
// Configure resampler
|
||||
deemp.setSamplerate(selectedDemod->getAFSampleRate());
|
||||
afChain.stop();
|
||||
double afsr = selectedDemod->getAFSampleRate();
|
||||
ctcss.setSamplerate(afsr);
|
||||
resamp.setInSamplerate(afsr);
|
||||
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
setAudioSampleRate(audioSampleRate);
|
||||
|
||||
// Configure the HPF
|
||||
setHighPass(highPass && highPassAllowed);
|
||||
|
||||
// Configure deemphasis
|
||||
setDeemphasisMode(deempModes[deempId]);
|
||||
}
|
||||
else {
|
||||
// Disable everything if post processing is disabled
|
||||
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
}
|
||||
|
||||
// Update audo samplerate
|
||||
stream->setSamplerate(selectedDemod->getAFSampleRate());
|
||||
|
||||
// Start the IF chain
|
||||
ifChain.start();
|
||||
|
||||
// Start new demodulator
|
||||
selectedDemod->start();
|
||||
|
||||
// Start the AF chain
|
||||
afChain.start();
|
||||
|
||||
// Start the audio stream
|
||||
stream->startDSP();
|
||||
}
|
||||
|
||||
|
||||
@@ -475,12 +574,57 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setAudioSampleRate(double sr) {
|
||||
audioSampleRate = sr;
|
||||
if (!selectedDemod) { return; }
|
||||
selectedDemod->AFSampRateChanged(audioSampleRate);
|
||||
if (!postProcEnabled && vfo) {
|
||||
// If postproc is disabled, IF SR = AF SR
|
||||
minBandwidth = selectedDemod->getMinBandwidth();
|
||||
maxBandwidth = selectedDemod->getMaxBandwidth();
|
||||
bandwidth = selectedDemod->getIFSampleRate();
|
||||
vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked());
|
||||
vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth);
|
||||
return;
|
||||
}
|
||||
|
||||
afChain.stop();
|
||||
|
||||
// Configure resampler
|
||||
resamp.setOutSamplerate(audioSampleRate);
|
||||
|
||||
// Configure the HPF sample rate
|
||||
hpTaps = dsp::taps::highPass(300.0, 100.0, audioSampleRate);
|
||||
hpf.setTaps(hpTaps);
|
||||
|
||||
// Configure deemphasis sample rate
|
||||
deemp.setSamplerate(audioSampleRate);
|
||||
|
||||
afChain.start();
|
||||
}
|
||||
|
||||
void setHighPass(bool enabled) {
|
||||
// Update the state
|
||||
highPass = enabled;
|
||||
|
||||
// Check if post-processing is enabled and that a demodulator is selected
|
||||
if (!postProcEnabled || !selectedDemod) { return; }
|
||||
|
||||
// Set the state of the HPF in the AF chain
|
||||
afChain.setBlockEnabled(&hpf, enabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
config.conf[name][selectedDemod->getName()]["highPass"] = enabled;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setDeemphasisMode(DeemphasisMode mode) {
|
||||
deempId = deempModes.valueId(mode);
|
||||
if (!postProcEnabled || !selectedDemod) { return; }
|
||||
bool deempEnabled = (mode != DEEMP_MODE_NONE);
|
||||
if (deempEnabled) { deemp.setTau(deempTaus[mode]); }
|
||||
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
|
||||
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
@@ -502,6 +646,7 @@ private:
|
||||
void setNBLevel(float level) {
|
||||
nbLevel = std::clamp<float>(level, MIN_NB, MAX_NB);
|
||||
nb.setLevel(nbLevel);
|
||||
if (!selectedDemod) { return; }
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
@@ -509,20 +654,59 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setSquelchEnabled(bool enable) {
|
||||
squelchEnabled = enable;
|
||||
void setSquelchMode(SquelchMode mode) {
|
||||
squelchModeId = squelchModes.valueId(mode);
|
||||
if (!selectedDemod) { return; }
|
||||
ifChain.setBlockEnabled(&squelch, squelchEnabled, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
|
||||
|
||||
// Disable all squelch blocks
|
||||
ifChain.disableBlock(&powerSquelch, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
|
||||
afChain.disableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Enable the block depending on the mode
|
||||
switch (mode) {
|
||||
case SQUELCH_MODE_OFF:
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_POWER:
|
||||
// Enable the power squelch block
|
||||
ifChain.enableBlock(&powerSquelch, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_SNR:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_CTCSS_MUTE:
|
||||
// Set the required tone and enable the CTCSS squelch block
|
||||
ctcss.setRequiredTone(ctcssTones[ctcssToneId]);
|
||||
afChain.enableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_CTCSS_DECODE:
|
||||
// Set the required tone to none and enable the CTCSS squelch block
|
||||
ctcss.setRequiredTone(dsp::noise_reduction::CTCSS_TONE_NONE);
|
||||
afChain.enableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_DCS_MUTE:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case SQUELCH_MODE_DCS_DECODE:
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
config.conf[name][selectedDemod->getName()]["squelchEnabled"] = squelchEnabled;
|
||||
config.conf[name][selectedDemod->getName()]["squelchMode"] = squelchModes.key(squelchModeId);
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setSquelchLevel(float level) {
|
||||
squelchLevel = std::clamp<float>(level, MIN_SQUELCH, MAX_SQUELCH);
|
||||
squelch.setLevel(squelchLevel);
|
||||
powerSquelch.setLevel(squelchLevel);
|
||||
if (!selectedDemod) { return; }
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
@@ -530,6 +714,24 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setCTCSSTone(dsp::noise_reduction::CTCSSTone tone) {
|
||||
// Check for an invalid value
|
||||
if (tone == dsp::noise_reduction::CTCSS_TONE_NONE) { return; }
|
||||
|
||||
// If not in CTCSS mute mode, do nothing
|
||||
if (squelchModes[squelchModeId] != SQUELCH_MODE_CTCSS_MUTE) { return; }
|
||||
|
||||
// Set the tone
|
||||
ctcssToneId = ctcssTones.valueId(tone);
|
||||
ctcss.setRequiredTone(tone);
|
||||
if (!selectedDemod) { return; }
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
config.conf[name][selectedDemod->getName()]["ctcssTone"] = ctcssTones.key(ctcssToneId);
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setFMIFNREnabled(bool enabled) {
|
||||
FMIFNREnabled = enabled;
|
||||
if (!selectedDemod) { return; }
|
||||
@@ -564,6 +766,11 @@ private:
|
||||
_this->setBandwidth(newBw);
|
||||
}
|
||||
|
||||
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
_this->setAudioSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
if (!_this->selectedDemod) { return; }
|
||||
@@ -572,14 +779,16 @@ private:
|
||||
|
||||
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
if (!_this->enabled || !_this->selectedDemod) { return; }
|
||||
|
||||
// If no demod is selected, reject the command
|
||||
if (!_this->selectedDemod) { return; }
|
||||
|
||||
// Execute commands
|
||||
if (code == RADIO_IFACE_CMD_GET_MODE && out) {
|
||||
int* _out = (int*)out;
|
||||
*_out = _this->selectedDemodID;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_MODE && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_MODE && in && _this->enabled) {
|
||||
int* _in = (int*)in;
|
||||
_this->selectDemodByID((DemodID)*_in);
|
||||
}
|
||||
@@ -587,27 +796,43 @@ private:
|
||||
float* _out = (float*)out;
|
||||
*_out = _this->bandwidth;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in && _this->enabled) {
|
||||
float* _in = (float*)in;
|
||||
if (_this->bandwidthLocked) { return; }
|
||||
_this->setBandwidth(*_in);
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_GET_SQUELCH_ENABLED && out) {
|
||||
bool* _out = (bool*)out;
|
||||
*_out = _this->squelchEnabled;
|
||||
else if (code == RADIO_IFACE_CMD_GET_SQUELCH_MODE && out) {
|
||||
SquelchMode* _out = (SquelchMode*)out;
|
||||
*_out = _this->squelchModes[_this->squelchModeId];
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in) {
|
||||
bool* _in = (bool*)in;
|
||||
_this->setSquelchEnabled(*_in);
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_MODE && in && _this->enabled) {
|
||||
SquelchMode* _in = (SquelchMode*)in;
|
||||
_this->setSquelchMode(*_in);
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_GET_SQUELCH_LEVEL && out) {
|
||||
float* _out = (float*)out;
|
||||
*_out = _this->squelchLevel;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in && _this->enabled) {
|
||||
float* _in = (float*)in;
|
||||
_this->setSquelchLevel(*_in);
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_GET_CTCSS_TONE && out) {
|
||||
dsp::noise_reduction::CTCSSTone* _out = (dsp::noise_reduction::CTCSSTone*)out;
|
||||
*_out = _this->ctcssTones[_this->ctcssToneId];
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_CTCSS_TONE && in && _this->enabled) {
|
||||
dsp::noise_reduction::CTCSSTone* _in = (dsp::noise_reduction::CTCSSTone*)in;
|
||||
_this->setCTCSSTone(*_in);
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_GET_HIGHPASS && out) {
|
||||
bool* _out = (bool*)out;
|
||||
*_out = _this->highPass;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_HIGHPASS && in && _this->enabled) {
|
||||
bool* _in = (bool*)in;
|
||||
_this->setHighPass(*_in);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
@@ -618,6 +843,7 @@ private:
|
||||
|
||||
// Handlers
|
||||
EventHandler<double> onUserChangedBandwidthHandler;
|
||||
EventHandler<float> srChangeHandler;
|
||||
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
|
||||
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
|
||||
|
||||
@@ -627,19 +853,25 @@ private:
|
||||
dsp::chain<dsp::complex_t> ifChain;
|
||||
dsp::noise_reduction::NoiseBlanker nb;
|
||||
dsp::noise_reduction::FMIF fmnr;
|
||||
dsp::noise_reduction::Squelch squelch;
|
||||
dsp::noise_reduction::PowerSquelch powerSquelch;
|
||||
|
||||
// Audio chain
|
||||
dsp::stream<dsp::stereo_t> dummyAudioStream;
|
||||
dsp::chain<dsp::stereo_t> afChain;
|
||||
dsp::noise_reduction::CTCSSSquelch ctcss;
|
||||
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
||||
dsp::tap<float> hpTaps;
|
||||
dsp::filter::FIR<dsp::stereo_t, float> hpf;
|
||||
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
|
||||
|
||||
std::shared_ptr<MasterStream> stream;
|
||||
SinkManager::Stream stream;
|
||||
|
||||
demod::Demodulator* selectedDemod = NULL;
|
||||
|
||||
OptionList<std::string, DeemphasisMode> deempModes;
|
||||
OptionList<std::string, IFNRPreset> ifnrPresets;
|
||||
OptionList<std::string, SquelchMode> squelchModes;
|
||||
OptionList<int, dsp::noise_reduction::CTCSSTone> ctcssTones;
|
||||
|
||||
double audioSampleRate = 48000.0;
|
||||
float minBandwidth;
|
||||
@@ -650,8 +882,13 @@ private:
|
||||
int selectedDemodID = 1;
|
||||
bool postProcEnabled;
|
||||
|
||||
bool squelchEnabled = false;
|
||||
int squelchModeId = 0;
|
||||
float squelchLevel;
|
||||
int ctcssToneId = 0;
|
||||
bool squelchAllowed = false;
|
||||
|
||||
bool highPass = false;
|
||||
bool highPassAllowed = false;
|
||||
|
||||
int deempId = 0;
|
||||
bool deempAllowed;
|
||||
|
||||
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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
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,52 @@ 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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
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,52 @@ 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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
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,52 @@ 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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:buster
|
||||
FROM debian:trixie
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
COPY do_build.sh /root
|
||||
RUN chmod +x /root/do_build.sh
|
||||
78
docker_builds/debian_trixie/do_build.sh
Normal file
78
docker_builds/debian_trixie/do_build.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
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,33 @@ 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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
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 -DOPT_BUILD_DRAGONLABS_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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
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,52 @@ 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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
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,52 @@ 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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_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.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
|
||||
7z x ./SDRplay_RSP_API-Linux-3.14.0
|
||||
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
|
||||
cp inc/* /usr/include/
|
||||
|
||||
# Install libperseus
|
||||
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'
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:mantic
|
||||
FROM ubuntu:noble
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
COPY do_build.sh /root
|
||||
RUN chmod +x /root/do_build.sh
|
||||
78
docker_builds/ubuntu_noble/do_build.sh
Normal file
78
docker_builds/ubuntu_noble/do_build.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_SOURCE=ON
|
||||
make VERBOSE=1 -j2
|
||||
|
||||
cd ..
|
||||
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'
|
||||
4
docker_builds/ubuntu_oracular/Dockerfile
Normal file
4
docker_builds/ubuntu_oracular/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM ubuntu:oracular
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
COPY do_build.sh /root
|
||||
RUN chmod +x /root/do_build.sh
|
||||
78
docker_builds/ubuntu_oracular/do_build.sh
Normal file
78
docker_builds/ubuntu_oracular/do_build.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/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 ../../
|
||||
|
||||
# Install libdlcr
|
||||
wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip
|
||||
mkdir dlcr_host
|
||||
cd dlcr_host
|
||||
7z x ../dlcr_host_v0.3.0.zip
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j2
|
||||
make install
|
||||
cd ../../
|
||||
|
||||
cd SDRPlusPlus
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_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 =========================
|
||||
|
||||
@@ -8,7 +8,7 @@ mkdir sdrpp_debian_amd64/DEBIAN
|
||||
# Create package info
|
||||
echo Create package info
|
||||
echo Package: sdrpp >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Version: 1.1.0$BUILD_NO >> sdrpp_debian_amd64/DEBIAN/control
|
||||
echo Version: 1.2.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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user