mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-07-08 18:15:21 +02:00
Compare commits
297 Commits
noise_redu
...
fobos_test
Author | SHA1 | Date | |
---|---|---|---|
17eccf5156 | |||
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 | |||
2813aa7c93 | |||
c61fc400a6 | |||
779ef7ecf1 | |||
6fdab5e0c2 | |||
ea08fac32e | |||
632a4eebab | |||
e118598f57 | |||
a2d49b2f87 | |||
38abfc715e | |||
07eebd7018 | |||
d12021fc2f | |||
db1682a2ac | |||
fdfb1dbf5e | |||
17f698577f | |||
8eaa987d90 | |||
12f7efed32 | |||
065a5b4c40 | |||
70f90fd570 | |||
a2054ad780 | |||
e1c48e9a1f | |||
867a8680e1 | |||
bf831e3a50 | |||
eb8b852ea6 | |||
67520ea45e | |||
a3f0ad238a | |||
feb9789896 | |||
3a5096092d | |||
9dc0196a16 | |||
b1603f0e72 | |||
09467439e3 | |||
021928bbda | |||
7c933d5103 | |||
a987c112a3 | |||
f1339f08cf | |||
650a61930c | |||
61ffb3e6bf | |||
9ab3c97c44 | |||
edc08ddc08 | |||
95052c34ff | |||
34171d4edc | |||
726e1069bf | |||
61c14bab48 | |||
01ab1831e8 | |||
2b752bb267 | |||
5204cfec56 | |||
c616892eda | |||
5f23c1f312 | |||
5e0c4449f8 | |||
cd3e2b6c05 | |||
ba5380f9bb | |||
daf0f8c159 | |||
63aa45de9e | |||
c0a84f8703 | |||
f66f2c25e1 | |||
bddfe5396f | |||
d5fa76df06 | |||
8029cef4da | |||
d84bb9bdec | |||
a0ff745b63 | |||
a08d2a0f85 | |||
7ab743d05b | |||
122e67ef65 | |||
fbeb2195da | |||
1f2b50c9bb | |||
f486c657c1 | |||
f1f04d59fe | |||
ef42ea01d8 | |||
3fc893568a | |||
4b6835141e | |||
a9e59bdf3c | |||
f0bd17f9f4 | |||
a8ed213ed3 | |||
f8183739f7 | |||
120745de19 | |||
05ab17add3 | |||
2ef8ee3629 | |||
14cb839863 | |||
9501371c6c | |||
ff23d7e43f | |||
f541328e5c | |||
be8edbfa9e | |||
11a7c382e8 | |||
54276177ae | |||
bc77bab45f | |||
97d0a07ec7 | |||
6b5de78e80 | |||
1cd8c2510a | |||
32cbd726fd | |||
175992b081 | |||
31c9e5767e | |||
e6a02a3944 | |||
00e6832055 | |||
bc8baca190 | |||
08e75b6d14 | |||
e9ec79f6ef | |||
cd996292bc | |||
06b7ad5c98 | |||
38a95b4011 | |||
f6052d913a | |||
2b00370cf3 | |||
09f4071803 | |||
e61ef29e0f | |||
eb36f86d41 | |||
29889a289f | |||
859af77bd3 | |||
0a3d1de02f | |||
db3fbd2975 | |||
f5adc7c587 | |||
255988ee46 | |||
68bf2fc16f | |||
3aa167701e | |||
97c1a132a5 | |||
118e56897c | |||
af8c085d43 | |||
708f74e179 | |||
eab4264604 | |||
4b77d8c395 | |||
854ed89b82 | |||
27ab5bf3c1 | |||
159f59b858 | |||
93cafe7109 | |||
74ae8a45d9 | |||
691216a298 | |||
3e58d4ba31 | |||
86dcec7495 | |||
5a003e99d2 | |||
f197cf6bd9 | |||
8cefeadbd4 | |||
23ae66151b | |||
fa76b4e865 | |||
5e195a0d43 | |||
5e299d9d23 | |||
fd5813df6d | |||
eabb842b6b | |||
5a1945f779 | |||
052167962d | |||
6fc41a81a7 | |||
e710b6c6dc | |||
a91434c5fe | |||
193580caf3 | |||
ead2ac6128 | |||
505cbb0ba2 | |||
b1030cbdfb | |||
f3c5b2c31f | |||
2432390600 | |||
794d6ff5ac | |||
4803271115 | |||
78daed7879 | |||
5f297b1a69 | |||
7c5d4226eb | |||
ec086ebbdf | |||
d10d420467 | |||
5bf989f49d | |||
4b3b6976d6 | |||
55ddd383d2 | |||
a043ab2dd3 | |||
d270e1c5e8 | |||
e41f24a95e | |||
766b3db363 | |||
0632342bb7 | |||
27b07ed0e8 | |||
a824c83848 | |||
0e50ee0e67 | |||
a55d1d9c06 | |||
97187b790f | |||
9c1361a8a9 | |||
4d0d14856b | |||
365ab2325e | |||
4da7e686f3 | |||
c1d9ab64f8 | |||
ea33135bf1 | |||
2081384905 | |||
6b31134af2 | |||
99d2786c25 | |||
320f4459ed | |||
de5816f79f | |||
7b9c01ec73 | |||
f06eccd97c | |||
88baa8a48e | |||
b436fd0745 | |||
6fa4299136 | |||
220dcbcc76 | |||
15ad065feb | |||
dddf84510e | |||
acd9ad9781 | |||
168e28cc44 | |||
3b9867c1d7 | |||
ff7ef78b8f | |||
87add9ad83 | |||
6cd09f9b60 | |||
8d05c1e181 | |||
1c081cad78 | |||
aa929a1e79 | |||
5acdab0d22 | |||
3e3846daa1 | |||
47617e1acd | |||
1df51020aa | |||
aa1fd9e573 | |||
21816fb438 | |||
3a06612ff5 | |||
a53bf05ed3 | |||
78c57db116 | |||
c16281f68a | |||
32c580ba57 | |||
664b5d85e2 | |||
6bcb62bfb2 | |||
0e7c754b8b | |||
71327cd695 | |||
f296730302 | |||
b89fdba433 | |||
2c3b522787 | |||
528763d10e | |||
582aeed640 | |||
9c0b57a036 | |||
604f95fd96 | |||
c892e51000 | |||
3336ae4aa5 | |||
190cea8e4e | |||
13a268a3e1 | |||
800a8b22c7 | |||
2eb030dd83 | |||
365fe1930c | |||
342a677c3f | |||
b5c41bcb3a | |||
f578adceef | |||
1e01313612 | |||
e64c343645 | |||
b9effce7d6 | |||
45a13227de |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,6 +7,8 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# WARNING: Filling out the template below is NOT optional. Issues not filling out this template will be closed without review.
|
||||
|
||||
FIRST: Before reporting any bug, make sure that the bug you are reporting has not been reported before. Also, try to use the [nightly version](https://www.sdrpp.org/nightly) if possible in case I've already fixed the bug.
|
||||
|
||||
**Hardware**
|
||||
|
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@ -1,7 +1,4 @@
|
||||
# Important
|
||||
|
||||
Only minor bug fixes and bandplans are accepted.
|
||||
|
||||
Pull requests adding features or any bug fix that requires significant code changes will be automatically rejected.
|
||||
|
||||
Only bandplan, colormaps and themes are accepted. Code pull requests are **NOT welcome**.
|
||||
Open an issue requesting a feature or discussing a possible bugfix instead.
|
265
.github/workflows/build_all.yml
vendored
265
.github/workflows/build_all.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
@ -34,13 +34,20 @@ jobs:
|
||||
|
||||
- name: Patch Pothos with earlier libusb version
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/"
|
||||
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/"
|
||||
|
||||
- name: Download 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://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu" -OutFile ${{runner.workspace}}/SDRPlay.zip
|
||||
run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip
|
||||
|
||||
- name: Install SDRPlay API
|
||||
run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/"
|
||||
run: 7z x ${{runner.workspace}}/SDRplay.zip -o"C:/Program Files/"
|
||||
|
||||
- name: Download codec2
|
||||
run: git clone https://github.com/AlexandreRouma/codec2
|
||||
@ -58,14 +65,23 @@ jobs:
|
||||
run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2"
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows
|
||||
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 -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos ; cd libfobos ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
|
||||
run: cmake -DCOPY_MSVC_REDISTRIBUTABLES=ON "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@ -76,47 +92,53 @@ jobs:
|
||||
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_windows_x64
|
||||
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
||||
|
||||
build_macos:
|
||||
runs-on: macos-11
|
||||
build_macos_intel:
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Update brew repositories
|
||||
run: brew update
|
||||
|
||||
- name: Fix stuff
|
||||
run: rm -f /usr/local/bin/2to3* /usr/local/bin/idle3* /usr/local/bin/pydoc3* /usr/local/bin/python3* /usr/local/bin/python3-config* && brew reinstall gettext
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2 zstd && pip3 install mako
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako
|
||||
|
||||
- name: Install volk
|
||||
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
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/SDRplay_RSP_API-MacOSX-3.07.3.pkg && sudo installer -pkg SDRplay_RSP_API-MacOSX-3.07.3.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: git clone https://github.com/analogdevicesinc/libiio && cd libiio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_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_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 .. && 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_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install libperseus
|
||||
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && sudo make install && cd ..
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install more recent librtlsdr
|
||||
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@ -127,17 +149,74 @@ jobs:
|
||||
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_intel.zip SDR++.app
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_macos_intel
|
||||
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
|
||||
|
||||
|
||||
build_macos_arm:
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages
|
||||
|
||||
- name: Install volk
|
||||
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install SDRplay API
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
|
||||
|
||||
- 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 ../../
|
||||
|
||||
- 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 ..
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install more recent librtlsdr
|
||||
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: make VERBOSE=1 -j3
|
||||
|
||||
- name: Create Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_arm.zip SDR++.app
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_macos_arm
|
||||
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
|
||||
|
||||
build_debian_buster:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
|
||||
|
||||
@ -149,7 +228,7 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_buster_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
@ -158,7 +237,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
|
||||
@ -171,16 +250,38 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_bullseye_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_bookworm:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_sid:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
|
||||
@ -193,38 +294,16 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_sid_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
# build_ubuntu_bionic:
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
|
||||
# - name: Create Docker Image
|
||||
# run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && docker build . --tag sdrpp_build
|
||||
|
||||
# - name: Run Container
|
||||
# run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
# - name: Recover Deb Archive
|
||||
# working-directory: ${{runner.workspace}}
|
||||
# run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
# - name: Save Deb Archive
|
||||
# uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: sdrpp_ubuntu_bionic_amd64
|
||||
# path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_focal:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
|
||||
@ -237,7 +316,7 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_focal_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
@ -246,7 +325,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
|
||||
@ -259,23 +338,67 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_jammy_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_mantic:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_mantic && 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_mantic_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_noble:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_noble_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_raspios_bullseye_armhf:
|
||||
runs-on: ARM
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON
|
||||
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@ -286,7 +409,7 @@ jobs:
|
||||
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
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_raspios_bullseye_armhf
|
||||
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
|
||||
@ -295,7 +418,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch container
|
||||
working-directory: ${{runner.workspace}}
|
||||
@ -313,33 +436,37 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
|
||||
|
||||
- name: Save APK
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_android
|
||||
path: ${{runner.workspace}}/sdrpp.apk
|
||||
|
||||
create_full_archive:
|
||||
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_raspios_bullseye_armhf', 'build_android']
|
||||
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android']
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download All Builds
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Create Archive
|
||||
run: >
|
||||
mkdir sdrpp_all &&
|
||||
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_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
|
||||
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
|
||||
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
||||
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
|
||||
mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb &&
|
||||
mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb &&
|
||||
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
|
||||
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_all
|
||||
path: sdrpp_all/
|
||||
@ -351,7 +478,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download All Builds
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Update Nightly
|
||||
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
|
||||
@ -360,7 +487,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install codespell
|
||||
run: sudo apt update -y && sudo apt install -y codespell
|
||||
@ -372,7 +499,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run check_clang_format
|
||||
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true
|
||||
|
101
CMakeLists.txt
101
CMakeLists.txt
@ -12,42 +12,52 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy
|
||||
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON)
|
||||
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON)
|
||||
option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON)
|
||||
option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF)
|
||||
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
|
||||
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
||||
option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF)
|
||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
|
||||
option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF)
|
||||
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
|
||||
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
|
||||
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)
|
||||
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF)
|
||||
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON)
|
||||
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" OFF)
|
||||
option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF)
|
||||
option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
|
||||
option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF)
|
||||
|
||||
# Sinks
|
||||
option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF)
|
||||
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON)
|
||||
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
|
||||
option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF)
|
||||
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
|
||||
|
||||
# Decoders
|
||||
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
|
||||
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF)
|
||||
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
|
||||
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
|
||||
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||
|
||||
# Misc
|
||||
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
|
||||
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
|
||||
option(OPT_BUILD_IQ_EXPORTER "Build the IQ Exporter module" ON)
|
||||
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
|
||||
option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
|
||||
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
|
||||
@ -57,6 +67,7 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
|
||||
# Other options
|
||||
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON)
|
||||
option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF)
|
||||
option(COPY_MSVC_REDISTRIBUTABLES "Copy over the Visual C++ Redistributable" OFF)
|
||||
|
||||
# Module cmake path
|
||||
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
|
||||
@ -121,6 +132,10 @@ if (OPT_BUILD_AUDIO_SOURCE)
|
||||
add_subdirectory("source_modules/audio_source")
|
||||
endif (OPT_BUILD_AUDIO_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BADGESDR_SOURCE)
|
||||
add_subdirectory("source_modules/badgesdr_source")
|
||||
endif (OPT_BUILD_BADGESDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BLADERF_SOURCE)
|
||||
add_subdirectory("source_modules/bladerf_source")
|
||||
endif (OPT_BUILD_BLADERF_SOURCE)
|
||||
@ -129,10 +144,18 @@ if (OPT_BUILD_FILE_SOURCE)
|
||||
add_subdirectory("source_modules/file_source")
|
||||
endif (OPT_BUILD_FILE_SOURCE)
|
||||
|
||||
if (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
add_subdirectory("source_modules/fobossdr_source")
|
||||
endif (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HACKRF_SOURCE)
|
||||
add_subdirectory("source_modules/hackrf_source")
|
||||
endif (OPT_BUILD_HACKRF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HAROGIC_SOURCE)
|
||||
add_subdirectory("source_modules/harogic_source")
|
||||
endif (OPT_BUILD_HAROGIC_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HERMES_SOURCE)
|
||||
add_subdirectory("source_modules/hermes_source")
|
||||
endif (OPT_BUILD_HERMES_SOURCE)
|
||||
@ -141,9 +164,21 @@ if (OPT_BUILD_LIMESDR_SOURCE)
|
||||
add_subdirectory("source_modules/limesdr_source")
|
||||
endif (OPT_BUILD_LIMESDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
add_subdirectory("source_modules/sdrpp_server_source")
|
||||
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
if (OPT_BUILD_NETWORK_SOURCE)
|
||||
add_subdirectory("source_modules/network_source")
|
||||
endif (OPT_BUILD_NETWORK_SOURCE)
|
||||
|
||||
if (OPT_BUILD_PERSEUS_SOURCE)
|
||||
add_subdirectory("source_modules/perseus_source")
|
||||
endif (OPT_BUILD_PERSEUS_SOURCE)
|
||||
|
||||
if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("source_modules/plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFNM_SOURCE)
|
||||
add_subdirectory("source_modules/rfnm_source")
|
||||
endif (OPT_BUILD_RFNM_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFSPACE_SOURCE)
|
||||
add_subdirectory("source_modules/rfspace_source")
|
||||
@ -157,6 +192,10 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
add_subdirectory("source_modules/rtl_tcp_source")
|
||||
endif (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
add_subdirectory("source_modules/sdrpp_server_source")
|
||||
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDRPLAY_SOURCE)
|
||||
add_subdirectory("source_modules/sdrplay_source")
|
||||
endif (OPT_BUILD_SDRPLAY_SOURCE)
|
||||
@ -177,10 +216,6 @@ if (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
add_subdirectory("source_modules/spyserver_source")
|
||||
endif (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
|
||||
if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("source_modules/plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_USRP_SOURCE)
|
||||
add_subdirectory("source_modules/usrp_source")
|
||||
endif (OPT_BUILD_USRP_SOURCE)
|
||||
@ -213,6 +248,10 @@ if (OPT_BUILD_ATV_DECODER)
|
||||
add_subdirectory("decoder_modules/atv_decoder")
|
||||
endif (OPT_BUILD_ATV_DECODER)
|
||||
|
||||
if (OPT_BUILD_DAB_DECODER)
|
||||
add_subdirectory("decoder_modules/dab_decoder")
|
||||
endif (OPT_BUILD_DAB_DECODER)
|
||||
|
||||
if (OPT_BUILD_FALCON9_DECODER)
|
||||
add_subdirectory("decoder_modules/falcon9_decoder")
|
||||
endif (OPT_BUILD_FALCON9_DECODER)
|
||||
@ -229,10 +268,18 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
add_subdirectory("decoder_modules/meteor_demodulator")
|
||||
endif (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
|
||||
if (OPT_BUILD_PAGER_DECODER)
|
||||
add_subdirectory("decoder_modules/pager_decoder")
|
||||
endif (OPT_BUILD_PAGER_DECODER)
|
||||
|
||||
if (OPT_BUILD_RADIO)
|
||||
add_subdirectory("decoder_modules/radio")
|
||||
endif (OPT_BUILD_RADIO)
|
||||
|
||||
if (OPT_BUILD_RYFI_DECODER)
|
||||
add_subdirectory("decoder_modules/ryfi_decoder")
|
||||
endif (OPT_BUILD_RYFI_DECODER)
|
||||
|
||||
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
@ -247,6 +294,10 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
|
||||
add_subdirectory("misc_modules/frequency_manager")
|
||||
endif (OPT_BUILD_FREQUENCY_MANAGER)
|
||||
|
||||
if (OPT_BUILD_IQ_EXPORTER)
|
||||
add_subdirectory("misc_modules/iq_exporter")
|
||||
endif (OPT_BUILD_IQ_EXPORTER)
|
||||
|
||||
if (OPT_BUILD_RECORDER)
|
||||
add_subdirectory("misc_modules/recorder")
|
||||
endif (OPT_BUILD_RECORDER)
|
||||
@ -267,7 +318,12 @@ if (OPT_BUILD_SCHEDULER)
|
||||
add_subdirectory("misc_modules/scheduler")
|
||||
endif (OPT_BUILD_SCHEDULER)
|
||||
|
||||
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||
if (MSVC)
|
||||
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||
else ()
|
||||
add_executable(sdrpp "src/main.cpp")
|
||||
endif ()
|
||||
|
||||
target_link_libraries(sdrpp PRIVATE sdrpp_core)
|
||||
|
||||
# Compiler arguments
|
||||
@ -277,6 +333,21 @@ target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
|
||||
if (MSVC)
|
||||
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
|
||||
if (COPY_MSVC_REDISTRIBUTABLES)
|
||||
# Get the list of Visual C++ runtime DLLs
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP True)
|
||||
include(InstallRequiredSystemLibraries)
|
||||
|
||||
# Create a space sperated list
|
||||
set(REDIST_DLLS_STR "")
|
||||
foreach(DLL IN LISTS CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS)
|
||||
set(REDIST_DLLS_STR COMMAND xcopy /F \"${DLL}\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y ${REDIST_DLLS_STR})
|
||||
endforeach()
|
||||
|
||||
# Create target
|
||||
add_custom_target(do_always_msvc ALL ${REDIST_DLLS_STR})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
||||
@ -297,7 +368,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PAGER_DECODER=ON
|
||||
|
||||
# Create module cmake file
|
||||
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
|
||||
@ -317,4 +388,6 @@ endif ()
|
||||
|
||||
# Create uninstall target
|
||||
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
|
||||
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
|
||||
# Create headers target
|
@ -10,7 +10,7 @@ android {
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.1.0"
|
||||
versionName "1.2.0"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
|
@ -1,64 +1,6 @@
|
||||
# Pull Requests
|
||||
|
||||
TODO
|
||||
|
||||
# Code Style
|
||||
|
||||
## Naming Convention
|
||||
|
||||
- Files: `snake_case.h` `snake_case.cpp`
|
||||
- Namespaces: `CamelCase`
|
||||
- Classes: `CamelCase`
|
||||
- Structs: `CamelCase_t`
|
||||
- Members: `camelCase`
|
||||
- Enum: `SNAKE_CASE`
|
||||
- Macros: `SNAKE_CASE`
|
||||
|
||||
## Brace Style
|
||||
|
||||
```c++
|
||||
int myFunction() {
|
||||
if (shortIf) { shortFunctionName(); }
|
||||
|
||||
if (longIf) {
|
||||
longFunction();
|
||||
otherStuff();
|
||||
myLongFunction();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: If it makes the code cleaner, remember to use the `?` keyword instead of a `if else` statement.
|
||||
|
||||
## Pointers
|
||||
|
||||
Please use `type* name` for pointers.
|
||||
|
||||
## Structure
|
||||
|
||||
Headers and their associated C++ files shall be in the same directory. All headers must use `#pragma once` instead of other include guards. Only include files in a header that are being used in that header. Include the rest in the associated C++ file.
|
||||
|
||||
# Modules
|
||||
|
||||
## Module Naming Convention
|
||||
|
||||
All modules names must be `snake_case`. If the module is a source, it must end with `_source`. If it is a sink, it must end with `_sink`.
|
||||
|
||||
For example, lets take the module named `cool_source`:
|
||||
|
||||
- Directory: `cool_source`
|
||||
- Class: `CoolSourceModule`
|
||||
- Binary: `cool_source.<os dynlib extension>`
|
||||
|
||||
## Integration into main repository
|
||||
|
||||
If the module meets the code quality requirements, it may be added to the official repository. A module that doesn't require any external dependencies that the core doesn't already use may be enabled for build by default. Otherwise, they must be disabled for build by default with a `OPT_BUILD_MODULE_NAME` variable set to `OFF`.
|
||||
|
||||
# JSON Formatting
|
||||
|
||||
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
|
||||
|
||||
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
||||
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
|
||||
|
||||
## Band Frequency Allocation
|
||||
|
||||
@ -118,8 +60,8 @@ Please follow this guide to properly format the JSON files for custom color maps
|
||||
}
|
||||
```
|
||||
|
||||
# Best Practices
|
||||
# JSON Formatting
|
||||
|
||||
* All additions and/or bug fixes to the core must not add additional dependencies.
|
||||
* Use VSCode for development, VS seems to cause issues.
|
||||
* DO NOT use libboost for any code meant for this repository
|
||||
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
|
||||
|
||||
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
@ -108,7 +108,6 @@ elseif (ANDROID)
|
||||
)
|
||||
|
||||
target_link_libraries(sdrpp_core PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libcpu_features.a
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libvolk.so
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libzstd.so
|
||||
|
@ -45,7 +45,7 @@ uint8_t *history_buffer_get_slice(history_buffer *buf) { return buf->history[buf
|
||||
|
||||
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
|
||||
unsigned int search_every) {
|
||||
shift_register_t bestpath;
|
||||
shift_register_t bestpath = 0;
|
||||
distance_t leasterror = USHRT_MAX;
|
||||
// search for a state with the least error
|
||||
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {
|
||||
|
@ -88,7 +88,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
|
||||
try {
|
||||
carg.ival = std::stoi(arg);
|
||||
}
|
||||
catch (std::exception e) {
|
||||
catch (const std::exception& e) {
|
||||
printf("Invalid argument, failed to parse integer\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
@ -98,7 +98,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
|
||||
try {
|
||||
carg.fval = std::stod(arg);
|
||||
}
|
||||
catch (std::exception e) {
|
||||
catch (const std::exception& e) {
|
||||
printf("Invalid argument, failed to parse float\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
|
@ -36,8 +36,8 @@ void ConfigManager::load(json def, bool lock) {
|
||||
file >> conf;
|
||||
file.close();
|
||||
}
|
||||
catch (std::exception e) {
|
||||
flog::error("Config file '{0}' is corrupted, resetting it", path);
|
||||
catch (const std::exception& e) {
|
||||
flog::error("Config file '{}' is corrupted, resetting it: {}", path, e.what());
|
||||
conf = def;
|
||||
save(false);
|
||||
}
|
||||
|
@ -117,6 +117,10 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["colorMap"] = "Classic";
|
||||
defConfig["fftHold"] = false;
|
||||
defConfig["fftHoldSpeed"] = 60;
|
||||
defConfig["fftSmoothing"] = false;
|
||||
defConfig["fftSmoothingSpeed"] = 100;
|
||||
defConfig["snrSmoothing"] = false;
|
||||
defConfig["snrSmoothingSpeed"] = 20;
|
||||
defConfig["fastFFT"] = false;
|
||||
defConfig["fftHeight"] = 300;
|
||||
defConfig["fftRate"] = 20;
|
||||
@ -169,14 +173,22 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
|
||||
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source";
|
||||
defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
|
||||
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source";
|
||||
defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
|
||||
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["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";
|
||||
@ -187,10 +199,12 @@ int sdrpp_main(int argc, char* argv[]) {
|
||||
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
|
||||
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source";
|
||||
defConfig["moduleInstances"]["SoapySDR 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";
|
||||
|
@ -12,6 +12,7 @@ namespace sdrpp_credits {
|
||||
"Howard0su",
|
||||
"John Donkersley",
|
||||
"Joshua Kimsey",
|
||||
"Manawyrm",
|
||||
"Martin Hauke",
|
||||
"Marvin Sinister",
|
||||
"Maxime Biette",
|
||||
@ -21,7 +22,6 @@ namespace sdrpp_credits {
|
||||
"Shuyuan Liu",
|
||||
"Syne Ardwin (WI9SYN)",
|
||||
"Szymon Zakrent",
|
||||
"Tobias Mädel",
|
||||
"Youssef Touil",
|
||||
"Zimm"
|
||||
};
|
||||
@ -37,13 +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"
|
||||
};
|
||||
@ -54,6 +61,7 @@ namespace sdrpp_credits {
|
||||
"Croccydile",
|
||||
"Dale L Puckett (K0HYD)",
|
||||
"Daniele D'Agnelli",
|
||||
"David Taylor (GM8ARV)",
|
||||
"D. Jones",
|
||||
"Dexruus",
|
||||
"EB3FRN",
|
||||
@ -81,6 +89,7 @@ namespace sdrpp_credits {
|
||||
"Syne Ardwin (WI9SYN)",
|
||||
"W4IPA",
|
||||
"William Arcand (W1WRA)",
|
||||
"William Pitchford",
|
||||
"Yves Rougy",
|
||||
"Zipper"
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -93,7 +93,7 @@ namespace dsp {
|
||||
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
|
||||
// Check that the block is part of the chain
|
||||
if (!blockExists(block)) {
|
||||
throw std::runtime_error("[chain] Tried to enable a block that isn't part of the chain");
|
||||
throw std::runtime_error("[chain] Tried to disable a block that isn't part of the chain");
|
||||
}
|
||||
|
||||
// If already disabled, don't do anything
|
||||
@ -163,10 +163,12 @@ namespace dsp {
|
||||
|
||||
private:
|
||||
Processor<T, T>* blockBefore(Processor<T, T>* block) {
|
||||
// TODO: This is wrong and must be fixed when I get more time
|
||||
for (auto& ln : links) {
|
||||
if (ln == block) { return NULL; }
|
||||
if (states[ln]) { return ln; }
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Processor<T, T>* blockAfter(Processor<T, T>* block) {
|
||||
|
@ -41,7 +41,11 @@ namespace dsp::channel {
|
||||
}
|
||||
|
||||
inline int process(int count, const complex_t* in, complex_t* out) {
|
||||
#if VOLK_VERSION >= 030100
|
||||
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, &phaseDelta, &phase, count);
|
||||
#else
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count);
|
||||
#endif
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,10 @@ namespace dsp::compression {
|
||||
|
||||
void init(stream<complex_t>* in, PCMType pcmType) {
|
||||
_pcmType = pcmType;
|
||||
|
||||
// Set the output buffer size to the max size of a complex buffer + 8 bytes for the header
|
||||
out.setBufferSize(STREAM_BUFFER_SIZE*sizeof(complex_t) + 8);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ namespace dsp::convert {
|
||||
|
||||
StereoToMono(stream<stereo_t>* in) { base_type::init(in); }
|
||||
|
||||
static inline int process(int count, const stereo_t* in, float* out) {
|
||||
inline int process(int count, const stereo_t* in, float* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = (in[i].l + in[i].r) / 2.0f;
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ namespace dsp::demod {
|
||||
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
|
||||
alFir.init(NULL, audioFirTaps);
|
||||
arFir.init(NULL, audioFirTaps);
|
||||
xlator.init(NULL, -57000.0, samplerate);
|
||||
rdsResamp.init(NULL, samplerate, 5000.0);
|
||||
|
||||
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||
@ -56,9 +57,9 @@ namespace dsp::demod {
|
||||
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||
|
||||
lprDelay.out.free();
|
||||
lmrDelay.out.free();
|
||||
arFir.out.free();
|
||||
alFir.out.free();
|
||||
xlator.out.free();
|
||||
rdsResamp.out.free();
|
||||
|
||||
base_type::init(in);
|
||||
@ -92,6 +93,7 @@ namespace dsp::demod {
|
||||
alFir.setTaps(audioFirTaps);
|
||||
arFir.setTaps(audioFirTaps);
|
||||
|
||||
xlator.setOffset(-57000.0, samplerate);
|
||||
rdsResamp.setInSamplerate(samplerate);
|
||||
|
||||
reset();
|
||||
@ -139,7 +141,7 @@ namespace dsp::demod {
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, float* rdsout = NULL) {
|
||||
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, complex_t* rdsout = NULL) {
|
||||
// Demodulate
|
||||
demod.process(count, in, demod.out.writeBuf);
|
||||
if (_stereo) {
|
||||
@ -152,24 +154,24 @@ namespace dsp::demod {
|
||||
|
||||
// Delay
|
||||
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
||||
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||
lmrDelay.process(count, rtoc.out.writeBuf, lmrDelay.out.writeBuf);
|
||||
|
||||
// conjugate PLL output to down convert twice the L-R signal
|
||||
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
|
||||
|
||||
// Do RDS demod
|
||||
if (_rdsOut) {
|
||||
// Since the PLL output is no longer needed after this, use it as the output
|
||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
||||
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
|
||||
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
|
||||
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
|
||||
// Translate to 0Hz
|
||||
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Resample to the output samplerate
|
||||
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||
}
|
||||
|
||||
// Convert output back to real for further processing
|
||||
convert::ComplexToReal::process(count, rtoc.out.writeBuf, lmr);
|
||||
convert::ComplexToReal::process(count, lmrDelay.out.writeBuf, lmr);
|
||||
|
||||
// Amplify by 2x
|
||||
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
|
||||
@ -193,24 +195,11 @@ namespace dsp::demod {
|
||||
// Convert to complex
|
||||
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Filter out pilot and run through PLL
|
||||
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
|
||||
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
|
||||
// Translate to 0Hz
|
||||
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Delay
|
||||
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
||||
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// conjugate PLL output to down convert twice the L-R signal
|
||||
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Since the PLL output is no longer needed after this, use it as the output
|
||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
||||
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
|
||||
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
|
||||
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
|
||||
// Resample to the output samplerate
|
||||
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||
}
|
||||
|
||||
// Filter if needed
|
||||
@ -240,7 +229,7 @@ namespace dsp::demod {
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> rdsOut;
|
||||
stream<complex_t> rdsOut;
|
||||
|
||||
protected:
|
||||
double _deviation;
|
||||
@ -253,13 +242,14 @@ namespace dsp::demod {
|
||||
tap<complex_t> pilotFirTaps;
|
||||
filter::FIR<complex_t, complex_t> pilotFir;
|
||||
convert::RealToComplex rtoc;
|
||||
channel::FrequencyXlator xlator;
|
||||
loop::PLL pilotPLL;
|
||||
math::Delay<float> lprDelay;
|
||||
math::Delay<complex_t> lmrDelay;
|
||||
tap<float> audioFirTaps;
|
||||
filter::FIR<float, float> arFir;
|
||||
filter::FIR<float, float> alFir;
|
||||
multirate::RationalResampler<float> rdsResamp;
|
||||
multirate::RationalResampler<dsp::complex_t> rdsResamp;
|
||||
|
||||
float* lmr;
|
||||
float* l;
|
||||
|
@ -110,7 +110,7 @@ namespace dsp::demod {
|
||||
else if (_mode == Mode::LSB) {
|
||||
return -_bandwidth / 2.0;
|
||||
}
|
||||
else if (_mode == Mode::DSB) {
|
||||
else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
@ -41,10 +41,10 @@ namespace dsp::filter {
|
||||
|
||||
// Move existing data to make transition seemless
|
||||
if (_taps.size < oldTC) {
|
||||
memcpy(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D));
|
||||
memmove(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D));
|
||||
}
|
||||
else if (_taps.size > oldTC) {
|
||||
memcpy(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D));
|
||||
memmove(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D));
|
||||
buffer::clear<D>(buffer, _taps.size - oldTC);
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,11 @@ namespace dsp::loop {
|
||||
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||
}
|
||||
|
||||
inline void advancePhase() {
|
||||
phase += freq;
|
||||
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||
}
|
||||
|
||||
T freq;
|
||||
T phase;
|
||||
|
||||
|
@ -1,183 +0,0 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../window/nuttall.h"
|
||||
#include <fftw3.h>
|
||||
#include "../convert/stereo_to_mono.h"
|
||||
|
||||
namespace dsp::noise_reduction {
|
||||
class Audio : public Processor<stereo_t, stereo_t> {
|
||||
using base_type = Processor<stereo_t, stereo_t>;
|
||||
public:
|
||||
Audio() {}
|
||||
|
||||
Audio(stream<stereo_t>* in, int bins) { init(in, bins); }
|
||||
|
||||
~Audio() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
destroyBuffers();
|
||||
}
|
||||
|
||||
void init(stream<stereo_t>* in, int bins) {
|
||||
_bins = bins;
|
||||
complexBins = (bins / 2) + 1;
|
||||
normFactor = 1.0f / (float)_bins;
|
||||
initBuffers();
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setBins(int bins) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_bins = bins;
|
||||
complexBins = (bins / 2) + 1;
|
||||
normFactor = 1.0f / (float)_bins;
|
||||
destroyBuffers();
|
||||
initBuffers();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setLevel(float level) {
|
||||
_level = powf(10.0f, level * 0.1f);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
buffer::clear(buffer, _bins - 1);
|
||||
buffer::clear(backFFTIn, _bins);
|
||||
buffer::clear(noisePrint, _bins);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int process(int count, const stereo_t* in, stereo_t* out) {
|
||||
// Write new input data to buffer
|
||||
convert::StereoToMono::process(count, in, bufferStart);
|
||||
|
||||
// Iterate the FFT
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Apply windows
|
||||
volk_32f_x2_multiply_32f(forwFFTIn, &buffer[i], fftWin, _bins);
|
||||
|
||||
// Do forward FFT
|
||||
fftwf_execute(forwardPlan);
|
||||
|
||||
// Get bin amplitude and square to get power
|
||||
volk_32fc_magnitude_32f(ampBuf, (lv_32fc_t*)forwFFTOut, complexBins);
|
||||
|
||||
// Update noise print using a running average
|
||||
volk_32f_s32f_multiply_32f(scaledAmps, ampBuf, alpha, complexBins);
|
||||
volk_32f_s32f_multiply_32f(noisePrint, noisePrint, beta, complexBins);
|
||||
volk_32f_x2_add_32f(noisePrint, noisePrint, scaledAmps, complexBins);
|
||||
|
||||
// Clamp amplitudes
|
||||
volk_32f_x2_max_32f(ampBuf, ampBuf, noisePrint, complexBins);
|
||||
|
||||
// Compute Wiener (funny) filter
|
||||
volk_32f_x2_subtract_32f(scaledAmps, ampBuf, noisePrint, complexBins);
|
||||
volk_32f_x2_divide_32f(scaledAmps, scaledAmps, ampBuf, complexBins);
|
||||
|
||||
// Apply wiener filter to bins
|
||||
volk_32fc_32f_multiply_32fc((lv_32fc_t*)backFFTIn, (lv_32fc_t*)forwFFTOut, scaledAmps, complexBins);
|
||||
|
||||
// Do reverse FFT and get first element
|
||||
fftwf_execute(backwardPlan);
|
||||
out[i].l = backFFTOut[_bins / 2];
|
||||
out[i].r = backFFTOut[_bins / 2];
|
||||
}
|
||||
|
||||
// Correct amplitude
|
||||
volk_32f_s32f_multiply_32f((float*)out, (float*)out, normFactor, count*2);
|
||||
|
||||
// Move buffer buffer
|
||||
memmove(buffer, &buffer[count], (_bins - 1) * sizeof(float));
|
||||
|
||||
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);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (!base_type::out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
void initBuffers() {
|
||||
// Allocate FFT buffers
|
||||
forwFFTIn = (float*)fftwf_malloc(_bins * sizeof(float));
|
||||
forwFFTOut = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
|
||||
backFFTIn = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
|
||||
backFFTOut = (float*)fftwf_malloc(_bins * sizeof(float));
|
||||
|
||||
// Allocate and clear delay buffer
|
||||
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + 64000);
|
||||
bufferStart = &buffer[_bins - 1];
|
||||
buffer::clear(buffer, _bins - 1);
|
||||
|
||||
// Clear backward FFT input
|
||||
buffer::clear(backFFTIn, _bins);
|
||||
|
||||
// Allocate amplitude buffer
|
||||
ampBuf = buffer::alloc<float>(_bins);
|
||||
scaledAmps = buffer::alloc<float>(_bins);
|
||||
noisePrint = buffer::alloc<float>(_bins);
|
||||
buffer::clear(noisePrint, _bins);
|
||||
|
||||
// Allocate and generate Window
|
||||
fftWin = buffer::alloc<float>(_bins);
|
||||
for (int i = 0; i < _bins; i++) { fftWin[i] = window::nuttall(i, _bins - 1); }
|
||||
|
||||
// Plan FFTs
|
||||
forwardPlan = fftwf_plan_dft_r2c_1d(_bins, forwFFTIn, (fftwf_complex*)forwFFTOut, FFTW_ESTIMATE);
|
||||
backwardPlan = fftwf_plan_dft_c2r_1d(_bins, (fftwf_complex*)backFFTIn, backFFTOut, FFTW_ESTIMATE);
|
||||
}
|
||||
|
||||
void destroyBuffers() {
|
||||
fftwf_destroy_plan(forwardPlan);
|
||||
fftwf_destroy_plan(backwardPlan);
|
||||
fftwf_free(forwFFTIn);
|
||||
fftwf_free(forwFFTOut);
|
||||
fftwf_free(backFFTIn);
|
||||
fftwf_free(backFFTOut);
|
||||
buffer::free(buffer);
|
||||
buffer::free(ampBuf);
|
||||
buffer::free(scaledAmps);
|
||||
buffer::free(noisePrint);
|
||||
buffer::free(fftWin);
|
||||
}
|
||||
|
||||
float _level = 0.0f;
|
||||
|
||||
float* forwFFTIn;
|
||||
complex_t* forwFFTOut;
|
||||
complex_t* backFFTIn;
|
||||
float* backFFTOut;
|
||||
|
||||
fftwf_plan forwardPlan;
|
||||
fftwf_plan backwardPlan;
|
||||
|
||||
float* buffer;
|
||||
float* bufferStart;
|
||||
|
||||
float* fftWin;
|
||||
|
||||
float* ampBuf;
|
||||
float* scaledAmps;
|
||||
float* noisePrint;
|
||||
|
||||
int _bins;
|
||||
int complexBins;
|
||||
float normFactor = 1.0f;
|
||||
|
||||
float alpha = 0.0001f;
|
||||
float beta = 0.9999f;
|
||||
};
|
||||
}
|
@ -37,17 +37,21 @@ namespace dsp::noise_reduction {
|
||||
|
||||
inline int process(int count, complex_t* in, complex_t* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Get signal amplitude and pass value if null
|
||||
// Get signal amplitude
|
||||
float inAmp = in[i].amplitude();
|
||||
if (!inAmp) {
|
||||
out[i] = in[i];
|
||||
|
||||
// Update average amplitude
|
||||
float gain = 1.0f;
|
||||
if (inAmp != 0.0f) {
|
||||
amp = (amp * _invRate) + (inAmp * _rate);
|
||||
float excess = inAmp / amp;
|
||||
if (excess > _level) {
|
||||
gain = 1.0f / excess;
|
||||
}
|
||||
}
|
||||
|
||||
// Update running average of amplitude
|
||||
amp = (_rate*inAmp) + (_invRate*amp);
|
||||
|
||||
// Null out if spike (Note: ideally, it should try to guess the real data)
|
||||
out[i] = (inAmp > _level*amp) ? complex_t{0.0f,0.0f} : in[i];
|
||||
|
||||
// Scale output by gain
|
||||
out[i] = in[i] * gain;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -82,7 +82,7 @@ namespace dsp {
|
||||
|
||||
inline float fastAmplitude() {
|
||||
float re_abs = fabsf(re);
|
||||
float im_abs = fabsf(re);
|
||||
float im_abs = fabsf(im);
|
||||
if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; }
|
||||
return im_abs + 0.4f * re_abs;
|
||||
}
|
||||
@ -125,4 +125,4 @@ namespace dsp {
|
||||
float l;
|
||||
float r;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -433,6 +433,9 @@ void MainWindow::draw() {
|
||||
showCredits = false;
|
||||
}
|
||||
|
||||
// Reset waterfall lock
|
||||
lockWaterfallControls = showCredits;
|
||||
|
||||
// Handle menu resize
|
||||
ImVec2 winSize = ImGui::GetWindowSize();
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
@ -463,9 +466,10 @@ void MainWindow::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
// Process menu keybinds
|
||||
displaymenu::checkKeybinds();
|
||||
|
||||
// Left Column
|
||||
lockWaterfallControls = false;
|
||||
if (showMenu) {
|
||||
ImGui::Columns(3, "WindowColumns", false);
|
||||
ImGui::SetColumnWidth(0, menuWidth);
|
||||
@ -576,8 +580,20 @@ void MainWindow::draw() {
|
||||
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
|
||||
double nfreq;
|
||||
if (vfo != NULL) {
|
||||
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (vfo->snapInterval * wheel);
|
||||
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
|
||||
// Select factor depending on modifier keys
|
||||
double interval;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) {
|
||||
interval = vfo->snapInterval * 10.0;
|
||||
}
|
||||
else if (ImGui::IsKeyDown(ImGuiKey_LeftAlt)) {
|
||||
interval = vfo->snapInterval * 0.1;
|
||||
}
|
||||
else {
|
||||
interval = vfo->snapInterval;
|
||||
}
|
||||
|
||||
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (interval * wheel);
|
||||
nfreq = roundl(nfreq / interval) * interval;
|
||||
}
|
||||
else {
|
||||
nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0);
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <gui/style.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace displaymenu {
|
||||
bool showWaterfall;
|
||||
@ -18,50 +19,44 @@ namespace displaymenu {
|
||||
std::string colorMapAuthor = "";
|
||||
int selectedWindow = 0;
|
||||
int fftRate = 20;
|
||||
int fftSizeId = 0;
|
||||
int uiScaleId = 0;
|
||||
bool restartRequired = false;
|
||||
bool fftHold = false;
|
||||
int fftHoldSpeed = 60;
|
||||
bool fftSmoothing = false;
|
||||
int fftSmoothingSpeed = 100;
|
||||
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,
|
||||
IQFrontEnd::FFTWindow::NUTTALL
|
||||
};
|
||||
|
||||
void updateFFTHoldSpeed() {
|
||||
gui::waterfall.setFFTHoldSpeed(fftHoldSpeed / (fftRate * 10.0f));
|
||||
void updateFFTSpeeds() {
|
||||
gui::waterfall.setFFTHoldSpeed((float)fftHoldSpeed / ((float)fftRate * 10.0f));
|
||||
gui::waterfall.setFFTSmoothingSpeed(std::min<float>((float)fftSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f));
|
||||
gui::waterfall.setSNRSmoothingSpeed(std::min<float>((float)snrSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f));
|
||||
}
|
||||
|
||||
void init() {
|
||||
// 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"];
|
||||
@ -83,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);
|
||||
@ -104,7 +96,13 @@ namespace displaymenu {
|
||||
fftHold = core::configManager.conf["fftHold"];
|
||||
fftHoldSpeed = core::configManager.conf["fftHoldSpeed"];
|
||||
gui::waterfall.setFFTHold(fftHold);
|
||||
updateFFTHoldSpeed();
|
||||
fftSmoothing = core::configManager.conf["fftSmoothing"];
|
||||
fftSmoothingSpeed = core::configManager.conf["fftSmoothingSpeed"];
|
||||
gui::waterfall.setFFTSmoothing(fftSmoothing);
|
||||
snrSmoothing = core::configManager.conf["snrSmoothing"];
|
||||
snrSmoothingSpeed = core::configManager.conf["snrSmoothingSpeed"];
|
||||
gui::waterfall.setSNRSmoothing(snrSmoothing);
|
||||
updateFFTSpeeds();
|
||||
|
||||
// Define and load UI scales
|
||||
uiScales.define(1.0f, "100%", 1.0f);
|
||||
@ -114,15 +112,24 @@ namespace displaymenu {
|
||||
uiScaleId = uiScales.valueId(style::uiScale);
|
||||
}
|
||||
|
||||
void setWaterfallShown(bool shown) {
|
||||
showWaterfall = shown;
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["showWaterfall"] = showWaterfall;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
void checkKeybinds() {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Home, false)) {
|
||||
setWaterfallShown(!showWaterfall);
|
||||
}
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
bool homePressed = ImGui::IsKeyPressed(ImGuiKey_Home, false);
|
||||
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall) || homePressed) {
|
||||
if (homePressed) { showWaterfall = !showWaterfall; }
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["showWaterfall"] = showWaterfall;
|
||||
core::configManager.release(true);
|
||||
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall)) {
|
||||
setWaterfallShown(showWaterfall);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
|
||||
@ -144,16 +151,47 @@ namespace displaymenu {
|
||||
core::configManager.conf["fftHold"] = fftHold;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::LeftLabel("FFT Hold Speed");
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) {
|
||||
updateFFTHoldSpeed();
|
||||
updateFFTSpeeds();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("FFT Smoothing##_sdrpp", &fftSmoothing)) {
|
||||
gui::waterfall.setFFTSmoothing(fftSmoothing);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftSmoothing"] = fftSmoothing;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::InputInt("##sdrpp_fft_smoothing_speed", &fftSmoothingSpeed)) {
|
||||
fftSmoothingSpeed = std::max<int>(fftSmoothingSpeed, 1);
|
||||
updateFFTSpeeds();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftSmoothingSpeed"] = fftSmoothingSpeed;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("SNR Smoothing##_sdrpp", &snrSmoothing)) {
|
||||
gui::waterfall.setSNRSmoothing(snrSmoothing);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["snrSmoothing"] = snrSmoothing;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::InputInt("##sdrpp_snr_smoothing_speed", &snrSmoothingSpeed)) {
|
||||
snrSmoothingSpeed = std::max<int>(snrSmoothingSpeed, 1);
|
||||
updateFFTSpeeds();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["snrSmoothingSpeed"] = snrSmoothingSpeed;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::LeftLabel("High-DPI Scaling");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) {
|
||||
@ -168,7 +206,7 @@ namespace displaymenu {
|
||||
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
|
||||
fftRate = std::max<int>(1, fftRate);
|
||||
sigpath::iqFrontEnd.setFFTRate(fftRate);
|
||||
updateFFTHoldSpeed();
|
||||
updateFFTSpeeds();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftRate"] = fftRate;
|
||||
core::configManager.release(true);
|
||||
@ -176,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);
|
||||
}
|
||||
|
||||
@ -210,4 +248,4 @@ namespace displaymenu {
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Restart required.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,5 +2,6 @@
|
||||
|
||||
namespace displaymenu {
|
||||
void init();
|
||||
void checkKeybinds();
|
||||
void draw(void* ctx);
|
||||
}
|
@ -39,7 +39,7 @@ namespace module_manager_menu {
|
||||
ImVec2 btnSize = ImVec2(lheight, lheight - 1);
|
||||
ImVec2 textOff = ImVec2(3.0f * style::uiScale, -5.0f * style::uiScale);
|
||||
|
||||
if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
|
||||
if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200.0f * style::uiScale))) {
|
||||
ImGui::TableSetupColumn("Name");
|
||||
ImGui::TableSetupColumn("Type");
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -144,4 +145,4 @@ namespace SmGui {
|
||||
// Config configs
|
||||
void ForceSyncForNext();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,33 @@ inline void printAndScale(double freq, char* buf) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void doZoom(int offset, int width, int inSize, int outSize, float* in, float* out) {
|
||||
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
if (width > 524288) {
|
||||
width = 524288;
|
||||
}
|
||||
|
||||
float factor = (float)width / (float)outSize;
|
||||
float sFactor = ceilf(factor);
|
||||
float uFactor;
|
||||
float id = offset;
|
||||
float maxVal;
|
||||
int sId;
|
||||
for (int i = 0; i < outSize; i++) {
|
||||
maxVal = -INFINITY;
|
||||
sId = (int)id;
|
||||
uFactor = (sId + sFactor > inSize) ? sFactor - ((sId + sFactor) - inSize) : sFactor;
|
||||
for (int j = 0; j < uFactor; j++) {
|
||||
if (in[sId + j] > maxVal) { maxVal = in[sId + j]; }
|
||||
}
|
||||
out[i] = maxVal;
|
||||
id += factor;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ImGui {
|
||||
WaterFall::WaterFall() {
|
||||
fftMin = -70.0;
|
||||
@ -586,7 +613,7 @@ namespace ImGui {
|
||||
for (int i = 0; i < count; i++) {
|
||||
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
|
||||
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
|
||||
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
|
||||
for (int j = 0; j < dataWidth; j++) {
|
||||
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
|
||||
@ -689,6 +716,7 @@ namespace ImGui {
|
||||
|
||||
void WaterFall::onResize() {
|
||||
std::lock_guard<std::recursive_mutex> lck(latestFFTMtx);
|
||||
std::lock_guard<std::mutex> lck2(smoothingBufMtx);
|
||||
// return if widget is too small
|
||||
if (widgetSize.x < 100 || widgetSize.y < 100) {
|
||||
return;
|
||||
@ -740,14 +768,23 @@ namespace ImGui {
|
||||
}
|
||||
latestFFTHold = new float[dataWidth];
|
||||
|
||||
// Reallocate smoothing buffer
|
||||
if (fftSmoothing) {
|
||||
if (smoothingBuf) { delete[] smoothingBuf; }
|
||||
smoothingBuf = new float[dataWidth];
|
||||
for (int i = 0; i < dataWidth; i++) {
|
||||
smoothingBuf[i] = -1000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (waterfallVisible) {
|
||||
delete[] waterfallFb;
|
||||
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
|
||||
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
|
||||
}
|
||||
for (int i = 0; i < dataWidth; i++) {
|
||||
latestFFT[i] = -1000.0; // Hide everything
|
||||
latestFFTHold[i] = -1000.0;
|
||||
latestFFT[i] = -1000.0f; // Hide everything
|
||||
latestFFTHold[i] = -1000.0f;
|
||||
}
|
||||
|
||||
fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale));
|
||||
@ -857,7 +894,7 @@ namespace ImGui {
|
||||
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||
|
||||
if (waterfallVisible) {
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
|
||||
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
|
||||
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
|
||||
float pixel;
|
||||
float dataRange = waterfallMax - waterfallMin;
|
||||
@ -869,13 +906,29 @@ namespace ImGui {
|
||||
waterfallUpdate = true;
|
||||
}
|
||||
else {
|
||||
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
|
||||
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, rawFFTs, latestFFT);
|
||||
fftLines = 1;
|
||||
}
|
||||
|
||||
// Apply smoothing if enabled
|
||||
if (fftSmoothing && latestFFT != NULL && smoothingBuf != NULL && fftLines != 0) {
|
||||
std::lock_guard<std::mutex> lck2(smoothingBufMtx);
|
||||
volk_32f_s32f_multiply_32f(latestFFT, latestFFT, fftSmoothingAlpha, dataWidth);
|
||||
volk_32f_s32f_multiply_32f(smoothingBuf, smoothingBuf, fftSmoothingBeta, dataWidth);
|
||||
volk_32f_x2_add_32f(smoothingBuf, latestFFT, smoothingBuf, dataWidth);
|
||||
memcpy(latestFFT, smoothingBuf, dataWidth * sizeof(float));
|
||||
}
|
||||
|
||||
if (selectedVFO != "" && vfos.size() > 0) {
|
||||
float dummy;
|
||||
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR);
|
||||
if (snrSmoothing) {
|
||||
float newSNR = 0.0f;
|
||||
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, newSNR);
|
||||
selectedVFOSNR = (snrSmoothingBeta*selectedVFOSNR) + (snrSmoothingAlpha*newSNR);
|
||||
}
|
||||
else {
|
||||
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR);
|
||||
}
|
||||
}
|
||||
|
||||
// If FFT hold is enabled, update it
|
||||
@ -1110,6 +1163,45 @@ namespace ImGui {
|
||||
fftHoldSpeed = speed;
|
||||
}
|
||||
|
||||
void WaterFall::setFFTSmoothing(bool enabled) {
|
||||
std::lock_guard<std::mutex> lck(smoothingBufMtx);
|
||||
fftSmoothing = enabled;
|
||||
|
||||
// Free buffer if not null
|
||||
if (smoothingBuf) {delete[] smoothingBuf; }
|
||||
|
||||
// If disabled, stop here
|
||||
if (!enabled) {
|
||||
smoothingBuf = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate and copy existing FFT into it
|
||||
smoothingBuf = new float[dataWidth];
|
||||
if (latestFFT) {
|
||||
std::lock_guard<std::recursive_mutex> lck2(latestFFTMtx);
|
||||
memcpy(smoothingBuf, latestFFT, dataWidth * sizeof(float));
|
||||
}
|
||||
else {
|
||||
memset(smoothingBuf, 0, dataWidth * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
void WaterFall::setFFTSmoothingSpeed(float speed) {
|
||||
std::lock_guard<std::mutex> lck(smoothingBufMtx);
|
||||
fftSmoothingAlpha = speed;
|
||||
fftSmoothingBeta = 1.0f - speed;
|
||||
}
|
||||
|
||||
void WaterFall::setSNRSmoothing(bool enabled) {
|
||||
snrSmoothing = enabled;
|
||||
}
|
||||
|
||||
void WaterFall::setSNRSmoothingSpeed(float speed) {
|
||||
snrSmoothingAlpha = speed;
|
||||
snrSmoothingBeta = 1.0f - speed;
|
||||
}
|
||||
|
||||
float* WaterFall::acquireLatestFFT(int& width) {
|
||||
latestFFTMtx.lock();
|
||||
if (!latestFFT) {
|
||||
|
@ -90,33 +90,6 @@ namespace ImGui {
|
||||
float* getFFTBuffer();
|
||||
void pushFFT();
|
||||
|
||||
inline void doZoom(int offset, int width, int outWidth, float* data, float* out) {
|
||||
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
if (width > 524288) {
|
||||
width = 524288;
|
||||
}
|
||||
|
||||
float factor = (float)width / (float)outWidth;
|
||||
float sFactor = ceilf(factor);
|
||||
float uFactor;
|
||||
float id = offset;
|
||||
float maxVal;
|
||||
int sId;
|
||||
for (int i = 0; i < outWidth; i++) {
|
||||
maxVal = -INFINITY;
|
||||
sId = (int)id;
|
||||
uFactor = (sId + sFactor > rawFFTSize) ? sFactor - ((sId + sFactor) - rawFFTSize) : sFactor;
|
||||
for (int j = 0; j < uFactor; j++) {
|
||||
if (data[sId + j] > maxVal) { maxVal = data[sId + j]; }
|
||||
}
|
||||
out[i] = maxVal;
|
||||
id += factor;
|
||||
}
|
||||
}
|
||||
|
||||
void updatePallette(float colors[][3], int colorCount);
|
||||
void updatePalletteFromArray(float* colors, int colorCount);
|
||||
|
||||
@ -169,6 +142,12 @@ namespace ImGui {
|
||||
void setFFTHold(bool hold);
|
||||
void setFFTHoldSpeed(float speed);
|
||||
|
||||
void setFFTSmoothing(bool enabled);
|
||||
void setFFTSmoothingSpeed(float speed);
|
||||
|
||||
void setSNRSmoothing(bool enabled);
|
||||
void setSNRSmoothingSpeed(float speed);
|
||||
|
||||
float* acquireLatestFFT(int& width);
|
||||
void releaseLatestFFT();
|
||||
|
||||
@ -182,7 +161,7 @@ namespace ImGui {
|
||||
bool mouseInFFT = false;
|
||||
bool mouseInWaterfall = false;
|
||||
|
||||
float selectedVFOSNR = NAN;
|
||||
float selectedVFOSNR = 0.0f;
|
||||
|
||||
bool centerFrequencyLocked = false;
|
||||
|
||||
@ -270,6 +249,7 @@ namespace ImGui {
|
||||
std::recursive_mutex buf_mtx;
|
||||
std::recursive_mutex latestFFTMtx;
|
||||
std::mutex texMtx;
|
||||
std::mutex smoothingBufMtx;
|
||||
|
||||
float vRange;
|
||||
|
||||
@ -304,8 +284,9 @@ namespace ImGui {
|
||||
//std::vector<std::vector<float>> rawFFTs;
|
||||
int rawFFTSize;
|
||||
float* rawFFTs = NULL;
|
||||
float* latestFFT;
|
||||
float* latestFFTHold;
|
||||
float* latestFFT = NULL;
|
||||
float* latestFFTHold = NULL;
|
||||
float* smoothingBuf = NULL;
|
||||
int currentFFTLine = 0;
|
||||
int fftLines = 0;
|
||||
|
||||
@ -325,6 +306,14 @@ namespace ImGui {
|
||||
bool fftHold = false;
|
||||
float fftHoldSpeed = 0.3f;
|
||||
|
||||
bool fftSmoothing = false;
|
||||
float fftSmoothingAlpha = 0.5;
|
||||
float fftSmoothingBeta = 0.5;
|
||||
|
||||
bool snrSmoothing = false;
|
||||
float snrSmoothingAlpha = 0.5;
|
||||
float snrSmoothingBeta = 0.5;
|
||||
|
||||
// UI Select elements
|
||||
bool fftResizeSelect = false;
|
||||
bool freqScaleSelect = false;
|
||||
|
@ -33,7 +33,7 @@ ModuleManager::Module_t ModuleManager::loadModule(std::string path) {
|
||||
#else
|
||||
mod.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (mod.handle == NULL) {
|
||||
flog::error("Couldn't load {0}.", path);
|
||||
flog::error("Couldn't load {0}: {1}", path, dlerror());
|
||||
mod.handle = NULL;
|
||||
return mod;
|
||||
}
|
||||
@ -183,4 +183,4 @@ void ModuleManager::doPostInitAll() {
|
||||
flog::info("Running post-init for {0}", name);
|
||||
inst.instance->postInit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
|
||||
class Instance {
|
||||
public:
|
||||
virtual ~Instance() {}
|
||||
virtual void postInit() = 0;
|
||||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include <utils/flog.h>
|
||||
|
||||
bool ModuleComManager::registerInterface(std::string moduleName, std::string name, void (*handler)(int code, void* in, void* out, void* ctx), void* ctx) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
if (interfaces.find(name) != interfaces.end()) {
|
||||
flog::error("Tried creating module interface with an existing name: {0}", name);
|
||||
return false;
|
||||
@ -16,7 +16,7 @@ bool ModuleComManager::registerInterface(std::string moduleName, std::string nam
|
||||
}
|
||||
|
||||
bool ModuleComManager::unregisterInterface(std::string name) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
if (interfaces.find(name) == interfaces.end()) {
|
||||
flog::error("Tried to erase module interface with unknown name: {0}", name);
|
||||
return false;
|
||||
@ -26,13 +26,13 @@ bool ModuleComManager::unregisterInterface(std::string name) {
|
||||
}
|
||||
|
||||
bool ModuleComManager::interfaceExists(std::string name) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
if (interfaces.find(name) == interfaces.end()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ModuleComManager::getModuleName(std::string name) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
if (interfaces.find(name) == interfaces.end()) {
|
||||
flog::error("Tried to call unknown module interface: {0}", name);
|
||||
return "";
|
||||
@ -41,7 +41,7 @@ std::string ModuleComManager::getModuleName(std::string name) {
|
||||
}
|
||||
|
||||
bool ModuleComManager::callInterface(std::string name, int code, void* in, void* out) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
if (interfaces.find(name) == interfaces.end()) {
|
||||
flog::error("Tried to call unknown module interface: {0}", name);
|
||||
return false;
|
||||
|
@ -18,6 +18,6 @@ public:
|
||||
bool callInterface(std::string name, int code, void* in, void* out);
|
||||
|
||||
private:
|
||||
std::mutex mtx;
|
||||
std::recursive_mutex mtx;
|
||||
std::map<std::string, ModuleComInterface> interfaces;
|
||||
};
|
@ -230,7 +230,7 @@ namespace server {
|
||||
// Compress data if needed and fill out header fields
|
||||
if (compression) {
|
||||
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND_COMPRESSED;
|
||||
bb_pkt_hdr->size = sizeof(PacketHeader) + (uint32_t)ZSTD_compressCCtx(cctx, &bbuf[sizeof(PacketHeader)], SERVER_MAX_PACKET_SIZE, data, count, 1);
|
||||
bb_pkt_hdr->size = sizeof(PacketHeader) + (uint32_t)ZSTD_compressCCtx(cctx, &bbuf[sizeof(PacketHeader)], SERVER_MAX_PACKET_SIZE-sizeof(PacketHeader), data, count, 1);
|
||||
}
|
||||
else {
|
||||
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND;
|
||||
|
@ -84,7 +84,7 @@ void SourceManager::tune(double freq) {
|
||||
if (selectedHandler == NULL) {
|
||||
return;
|
||||
}
|
||||
// TODO: No need to always retune the hardware in panadpter mode
|
||||
// TODO: No need to always retune the hardware in Panadapter mode
|
||||
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
|
||||
onRetune.emit(freq);
|
||||
currentFreq = freq;
|
||||
@ -100,7 +100,7 @@ void SourceManager::setTuningMode(TuningMode mode) {
|
||||
tune(currentFreq);
|
||||
}
|
||||
|
||||
void SourceManager::setPanadpterIF(double freq) {
|
||||
void SourceManager::setPanadapterIF(double freq) {
|
||||
ifFreq = freq;
|
||||
tune(currentFreq);
|
||||
}
|
@ -35,7 +35,7 @@ public:
|
||||
void tune(double freq);
|
||||
void setTuningOffset(double offset);
|
||||
void setTuningMode(TuningMode mode);
|
||||
void setPanadpterIF(double freq);
|
||||
void setPanadapterIF(double freq);
|
||||
|
||||
std::vector<std::string> getSourceNames();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "net.h"
|
||||
#include <string.h>
|
||||
#include <codecvt>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK)
|
||||
@ -85,14 +86,14 @@ namespace net {
|
||||
addr.sin_port = htons(port);
|
||||
}
|
||||
|
||||
std::string Address::getIPStr() {
|
||||
std::string Address::getIPStr() const {
|
||||
char buf[128];
|
||||
IP_t ip = getIP();
|
||||
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
|
||||
return buf;
|
||||
}
|
||||
|
||||
IP_t Address::getIP() {
|
||||
IP_t Address::getIP() const {
|
||||
return htonl(addr.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
@ -100,7 +101,7 @@ namespace net {
|
||||
addr.sin_addr.s_addr = htonl(ip);
|
||||
}
|
||||
|
||||
int Address::getPort() {
|
||||
int Address::getPort() const {
|
||||
return htons(addr.sin_port);
|
||||
}
|
||||
|
||||
@ -137,7 +138,16 @@ namespace net {
|
||||
}
|
||||
|
||||
int Socket::send(const uint8_t* data, size_t len, const Address* dest) {
|
||||
return sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
|
||||
// Send data
|
||||
int err = sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
|
||||
|
||||
// On error, close socket
|
||||
if (err <= 0 && !WOULD_BLOCK) {
|
||||
close();
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int Socket::sendstr(const std::string& str, const Address* dest) {
|
||||
@ -159,8 +169,8 @@ namespace net {
|
||||
|
||||
// Set timeout
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = timeout * 1000;
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
|
||||
|
||||
// Wait for data
|
||||
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
|
||||
@ -224,8 +234,8 @@ namespace net {
|
||||
|
||||
// Define timeout
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = timeout * 1000;
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
|
||||
|
||||
// Wait for data or error
|
||||
if (timeout != NONBLOCKING) {
|
||||
@ -374,13 +384,25 @@ namespace net {
|
||||
return connect(Address(host, port));
|
||||
}
|
||||
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr) {
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast) {
|
||||
// Init library if needed
|
||||
init();
|
||||
|
||||
// Create socket
|
||||
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
// If the remote address is multicast, allow multicast connections
|
||||
#ifdef _WIN32
|
||||
const char enable = allowBroadcast;
|
||||
#else
|
||||
int enable = allowBroadcast;
|
||||
#endif
|
||||
if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)) < 0) {
|
||||
closeSocket(s);
|
||||
throw std::runtime_error("Could not enable broadcast on socket");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Bind socket to local port
|
||||
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
|
||||
closeSocket(s);
|
||||
@ -392,15 +414,15 @@ namespace net {
|
||||
return std::make_shared<Socket>(s, &raddr);
|
||||
}
|
||||
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr) {
|
||||
return openudp(Address(rhost, rport), laddr);
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast) {
|
||||
return openudp(Address(rhost, rport), laddr, allowBroadcast);
|
||||
}
|
||||
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) {
|
||||
return openudp(raddr, Address(lhost, lport));
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) {
|
||||
return openudp(raddr, Address(lhost, lport), allowBroadcast);
|
||||
}
|
||||
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport) {
|
||||
return openudp(Address(rhost, rport), Address(lhost, lport));
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport, bool allowBroadcast) {
|
||||
return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
@ -66,13 +67,13 @@ namespace net {
|
||||
* Get the IP address.
|
||||
* @return IP address in standard string format.
|
||||
*/
|
||||
std::string getIPStr();
|
||||
std::string getIPStr() const;
|
||||
|
||||
/**
|
||||
* Get the IP address.
|
||||
* @return IP address in host byte order.
|
||||
*/
|
||||
IP_t getIP();
|
||||
IP_t getIP() const;
|
||||
|
||||
/**
|
||||
* Set the IP address.
|
||||
@ -84,7 +85,7 @@ namespace net {
|
||||
* Get the TCP/UDP port.
|
||||
* @return TCP/UDP port number.
|
||||
*/
|
||||
int getPort();
|
||||
int getPort() const;
|
||||
|
||||
/**
|
||||
* Set the TCP/UDP port.
|
||||
@ -245,37 +246,37 @@ namespace net {
|
||||
|
||||
/**
|
||||
* Create UDP socket.
|
||||
* @param raddr Remote address.
|
||||
* @param raddr Remote address. Set to a multicast address to allow multicast.
|
||||
* @param laddr Local address to bind the socket to.
|
||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
||||
*/
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr);
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast = false);
|
||||
|
||||
/**
|
||||
* Create UDP socket.
|
||||
* @param rhost Remote hostname or IP address.
|
||||
* @param rhost Remote hostname or IP address. Set to a multicast address to allow multicast.
|
||||
* @param rport Remote port.
|
||||
* @param laddr Local address to bind the socket to.
|
||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
||||
*/
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr);
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast = false);
|
||||
|
||||
/**
|
||||
* Create UDP socket.
|
||||
* @param raddr Remote address.
|
||||
* @param raddr Remote address. Set to a multicast or broadcast address to allow multicast.
|
||||
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
|
||||
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
|
||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
||||
*/
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0);
|
||||
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false);
|
||||
|
||||
/**
|
||||
* Create UDP socket.
|
||||
* @param rhost Remote hostname or IP address.
|
||||
* @param rhost Remote hostname or IP address. Set to a multicast or broadcast address to allow multicast.
|
||||
* @param rport Remote port.
|
||||
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
|
||||
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
|
||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
||||
*/
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0);
|
||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false);
|
||||
}
|
@ -320,7 +320,7 @@ namespace net {
|
||||
}
|
||||
entry.handler(std::move(client), entry.ctx);
|
||||
}
|
||||
catch (std::exception e) {
|
||||
catch (const std::exception& e) {
|
||||
listening = false;
|
||||
return;
|
||||
}
|
||||
|
52
core/src/utils/new_event.h
Normal file
52
core/src/utils/new_event.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
|
||||
typedef int HandlerID;
|
||||
|
||||
template <typename... Args>
|
||||
class NewEvent {
|
||||
public:
|
||||
using Handler = std::function<void(Args...)>;
|
||||
|
||||
HandlerID bind(const Handler& handler) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
HandlerID id = genID();
|
||||
handlers[id] = handler;
|
||||
return id;
|
||||
}
|
||||
|
||||
template<typename MHandler, class T>
|
||||
HandlerID bind(MHandler handler, T* ctx) {
|
||||
return bind([=](Args... args){
|
||||
(ctx->*handler)(args...);
|
||||
});
|
||||
}
|
||||
|
||||
void unbind(HandlerID id) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
if (handlers.find(id) == handlers.end()) {
|
||||
throw std::runtime_error("Could not unbind handler, unknown ID");
|
||||
}
|
||||
handlers.erase(id);
|
||||
}
|
||||
|
||||
void operator()(Args... args) {
|
||||
std::lock_guard<std::mutex> lck(mtx);
|
||||
for (const auto& [desc, handler] : handlers) {
|
||||
handler(args...);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
HandlerID genID() {
|
||||
int id;
|
||||
for (id = 1; handlers.find(id) != handlers.end(); id++);
|
||||
return id;
|
||||
}
|
||||
|
||||
std::map<HandlerID, Handler> handlers;
|
||||
std::mutex mtx;
|
||||
};
|
@ -257,6 +257,7 @@ namespace net::http {
|
||||
|
||||
// Deserialize
|
||||
req.deserialize(respData);
|
||||
return 0; // Might wanna return size instead
|
||||
}
|
||||
|
||||
int Client::sendResponseHeader(ResponseHeader& resp) {
|
||||
@ -274,6 +275,7 @@ namespace net::http {
|
||||
|
||||
// Deserialize
|
||||
resp.deserialize(respData);
|
||||
return 0; // Might wanna return size instead
|
||||
}
|
||||
|
||||
int Client::sendChunkHeader(ChunkHeader& chdr) {
|
||||
|
@ -7,6 +7,14 @@ namespace riff {
|
||||
const char* LIST_SIGNATURE = "LIST";
|
||||
const size_t RIFF_LABEL_SIZE = 4;
|
||||
|
||||
// Writer::Writer(const Writer&& b) {
|
||||
// //file = std::move(b.file);
|
||||
// }
|
||||
|
||||
Writer::~Writer() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool Writer::open(std::string path, const char form[4]) {
|
||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||
|
||||
@ -91,9 +99,9 @@ namespace riff {
|
||||
file.write((char*)&desc.hdr.size, sizeof(desc.hdr.size));
|
||||
file.seekp(pos);
|
||||
|
||||
// If parent chunk, increment its size
|
||||
// If parent chunk, increment its size by the size of the sub-chunk plus the size of its header)
|
||||
if (!chunks.empty()) {
|
||||
chunks.top().hdr.size += desc.hdr.size;
|
||||
chunks.top().hdr.size += desc.hdr.size + sizeof(ChunkHeader);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,10 @@ namespace riff {
|
||||
|
||||
class Writer {
|
||||
public:
|
||||
Writer() {}
|
||||
// Writer(const Writer&& b);
|
||||
~Writer();
|
||||
|
||||
bool open(std::string path, const char form[4]);
|
||||
bool isOpen();
|
||||
void close();
|
||||
@ -40,4 +44,23 @@ namespace riff {
|
||||
std::ofstream file;
|
||||
std::stack<ChunkDesc> chunks;
|
||||
};
|
||||
|
||||
// class Reader {
|
||||
// public:
|
||||
// Reader();
|
||||
// Reader(const Reader&& b);
|
||||
// ~Reader();
|
||||
|
||||
// bool open(std::string path);
|
||||
// bool isOpen();
|
||||
// void close();
|
||||
|
||||
// const std::string& form();
|
||||
|
||||
// private:
|
||||
|
||||
// std::string _form;
|
||||
// std::recursive_mutex mtx;
|
||||
// std::ofstream file;
|
||||
// };
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_STR "1.1.0"
|
||||
#define VERSION_STR "1.2.0"
|
63
decoder_modules/atv_decoder/src/chroma_pll.h
Normal file
63
decoder_modules/atv_decoder/src/chroma_pll.h
Normal file
@ -0,0 +1,63 @@
|
||||
#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;
|
||||
}
|
||||
};
|
||||
}
|
239
decoder_modules/atv_decoder/src/chrominance_filter.h
Normal file
239
decoder_modules/atv_decoder/src/chrominance_filter.h
Normal file
@ -0,0 +1,239 @@
|
||||
#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)
|
193
decoder_modules/atv_decoder/src/linesync.h
Normal file
193
decoder_modules/atv_decoder/src/linesync.h
Normal file
@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
#include <dsp/processor.h>
|
||||
#include <dsp/loop/phase_control_loop.h>
|
||||
#include <dsp/taps/windowed_sinc.h>
|
||||
#include <dsp/multirate/polyphase_bank.h>
|
||||
#include <dsp/math/step.h>
|
||||
|
||||
class LineSync : public dsp::Processor<float, float> {
|
||||
using base_type = dsp::Processor<float, float>;
|
||||
public:
|
||||
LineSync() {}
|
||||
|
||||
LineSync(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
|
||||
|
||||
~LineSync() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
dsp::buffer::free(buffer);
|
||||
}
|
||||
|
||||
void init(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
|
||||
_omega = omega;
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
_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];
|
||||
|
||||
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);
|
||||
base_type::tempStop();
|
||||
_interpPhaseCount = interpPhaseCount;
|
||||
_interpTapCount = interpTapCount;
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
dsp::buffer::free(buffer);
|
||||
generateInterpTaps();
|
||||
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
|
||||
bufStart = &buffer[_interpTapCount - 1];
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
offset = 0;
|
||||
pcl.phase = 0.0f;
|
||||
pcl.freq = _omega;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Copy data to work buffer
|
||||
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(float));
|
||||
|
||||
if (test2) {
|
||||
test2 = false;
|
||||
offset += 5;
|
||||
}
|
||||
|
||||
// Process all samples
|
||||
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;
|
||||
|
||||
// If the end of the line is reached, process it and determin error
|
||||
float error = 0;
|
||||
if (outCount >= 720) {
|
||||
// Compute averages.
|
||||
float left = 0.0f, right = 0.0f;
|
||||
for (int i = (720-17); i < 720; i++) {
|
||||
left += base_type::out.writeBuf[i];
|
||||
}
|
||||
for (int i = 0; i < 27; i++) {
|
||||
left += base_type::out.writeBuf[i];
|
||||
}
|
||||
for (int i = 27; i < (54+17); i++) {
|
||||
right += base_type::out.writeBuf[i];
|
||||
}
|
||||
left *= (1.0f/44.0f);
|
||||
right *= (1.0f/44.0f);
|
||||
|
||||
// If the sync is present, compute error
|
||||
if ((left < syncLevel && right < syncLevel) && !forceLock) {
|
||||
error = (left + syncBias - right);
|
||||
locked = true;
|
||||
}
|
||||
else {
|
||||
locked = false;
|
||||
}
|
||||
|
||||
if (++counter >= 100) {
|
||||
counter = 0;
|
||||
//flog::warn("Left: {}, Right: {}, Error: {}, Freq: {}, Phase: {}", left, right, error, pcl.freq, pcl.phase);
|
||||
}
|
||||
|
||||
// Output line
|
||||
if (!base_type::out.swap(outCount)) { break; }
|
||||
outCount = 0;
|
||||
}
|
||||
|
||||
// Advance symbol offset and phase
|
||||
pcl.advance(error);
|
||||
float delta = floorf(pcl.phase);
|
||||
offset += delta;
|
||||
pcl.phase -= delta;
|
||||
}
|
||||
offset -= count;
|
||||
|
||||
// Update delay buffer
|
||||
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
return outCount;
|
||||
}
|
||||
|
||||
bool locked = false;
|
||||
bool test2 = false;
|
||||
|
||||
float syncBias = 0.0f;
|
||||
bool forceLock = false;
|
||||
|
||||
int counter = 0;
|
||||
|
||||
protected:
|
||||
void generateInterpTaps() {
|
||||
double bw = 0.5 / (double)_interpPhaseCount;
|
||||
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
|
||||
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
|
||||
dsp::taps::free(lp);
|
||||
}
|
||||
|
||||
dsp::multirate::PolyphaseBank<float> interpBank;
|
||||
dsp::loop::PhaseControlLoop<double, false> pcl;
|
||||
|
||||
double _omega;
|
||||
double _omegaGain;
|
||||
double _muGain;
|
||||
double _omegaRelLimit;
|
||||
int _interpPhaseCount;
|
||||
int _interpTapCount;
|
||||
|
||||
int offset = 0;
|
||||
int outCount = 0;
|
||||
float* buffer;
|
||||
float* bufStart;
|
||||
|
||||
float syncLevel = -0.03f;
|
||||
};
|
@ -10,6 +10,15 @@
|
||||
|
||||
#include <dsp/demod/quadrature.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include "linesync.h"
|
||||
#include <dsp/loop/pll.h>
|
||||
#include <dsp/convert/real_to_complex.h>
|
||||
#include <dsp/filter/fir.h>
|
||||
#include <dsp/taps/from_array.h>
|
||||
|
||||
#include "chrominance_filter.h"
|
||||
|
||||
#include "chroma_pll.h"
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
@ -17,7 +26,8 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
|
||||
/* Description: */ "ATV decoder for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1};
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
|
||||
|
||||
@ -29,9 +39,16 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
|
||||
|
||||
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
|
||||
sink.init(&demod.out, handler, this);
|
||||
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));
|
||||
|
||||
demod.start();
|
||||
sync.start();
|
||||
sink.start();
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
@ -47,9 +64,13 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() { enabled = true; }
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() { enabled = false; }
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() { return enabled; }
|
||||
|
||||
@ -61,6 +82,8 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
style::beginDisabled();
|
||||
}
|
||||
|
||||
// Ideal width for testing: 750pixels
|
||||
|
||||
ImGui::FillWidth();
|
||||
_this->img.draw();
|
||||
|
||||
@ -76,6 +99,28 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
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::TextColored(ImVec4(0, 1, 0, 1), "Locked");
|
||||
}
|
||||
else {
|
||||
ImGui::TextUnformatted("Not locked");
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Force Lock", &_this->sync.forceLock);
|
||||
|
||||
if (!_this->enabled) {
|
||||
style::endDisabled();
|
||||
}
|
||||
@ -84,70 +129,67 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
static void handler(float *data, int count, void *ctx) {
|
||||
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
|
||||
|
||||
uint8_t *buf = (uint8_t *)_this->img.buffer;
|
||||
float val;
|
||||
float imval;
|
||||
int pos = 0;
|
||||
// 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];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
val = data[i];
|
||||
// Sync
|
||||
if (val < _this->sync_level) {
|
||||
_this->sync_count++;
|
||||
}
|
||||
else {
|
||||
if (_this->sync_count >= 300) {
|
||||
_this->short_sync = 0;
|
||||
}
|
||||
else if (_this->sync_count >= 33) {
|
||||
if (_this->short_sync == 5) {
|
||||
_this->even_field = false;
|
||||
_this->ypos = 0;
|
||||
_this->img.swap();
|
||||
buf = (uint8_t *)_this->img.buffer;
|
||||
}
|
||||
else if (_this->short_sync == 4) {
|
||||
_this->even_field = true;
|
||||
_this->ypos = 0;
|
||||
}
|
||||
_this->xpos = 0;
|
||||
_this->short_sync = 0;
|
||||
}
|
||||
else if (_this->sync_count >= 15) {
|
||||
_this->short_sync++;
|
||||
}
|
||||
_this->sync_count = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
// Vertical scan logic
|
||||
_this->ypos++;
|
||||
bool rollover = _this->ypos >= 625;
|
||||
if (rollover) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
|
||||
_this->evenFrame = !_this->evenFrame;
|
||||
}
|
||||
_this->ypos = 0;
|
||||
_this->img.swap();
|
||||
}
|
||||
|
||||
// Measure vsync levels
|
||||
float sync0 = 0.0f, sync1 = 0.0f;
|
||||
for (int i = 0; i < 306; i++) {
|
||||
sync0 += data[i];
|
||||
}
|
||||
for (int i = (720/2); i < ((720/2)+306); i++) {
|
||||
sync1 += data[i];
|
||||
}
|
||||
sync0 *= (1.0f/305.0f);
|
||||
sync1 *= (1.0f/305.0f);
|
||||
|
||||
// Draw
|
||||
imval = std::clamp<float>((val - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
||||
if (_this->even_field) {
|
||||
pos = ((720 * _this->ypos * 2) + _this->xpos) * 4;
|
||||
}
|
||||
else {
|
||||
pos = ((720 * (_this->ypos * 2 + 1)) + _this->xpos) * 4;
|
||||
}
|
||||
// Save sync detection to history
|
||||
_this->syncHistory >>= 2;
|
||||
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8);
|
||||
|
||||
buf[pos] = imval;
|
||||
buf[pos + 1] = imval;
|
||||
buf[pos + 2] = imval;
|
||||
buf[pos + 3] = imval;
|
||||
|
||||
// Image logic
|
||||
_this->xpos++;
|
||||
if (_this->xpos >= 720) {
|
||||
_this->ypos++;
|
||||
_this->xpos = 0;
|
||||
}
|
||||
if (_this->ypos >= 312) {
|
||||
_this->ypos = 0;
|
||||
_this->xpos = 0;
|
||||
_this->even_field = !_this->even_field;
|
||||
if (_this->even_field) {
|
||||
_this->img.swap();
|
||||
buf = (uint8_t *)_this->img.buffer;
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,19 +198,27 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
||||
|
||||
VFOManager::VFO *vfo = NULL;
|
||||
dsp::demod::Quadrature demod;
|
||||
LineSync sync;
|
||||
dsp::sink::Handler<float> sink;
|
||||
|
||||
int xpos = 0;
|
||||
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 even_field = false;
|
||||
|
||||
float sync_level = -0.3f;
|
||||
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;
|
||||
|
||||
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
|
@ -57,10 +57,13 @@ public:
|
||||
if (config.conf[name].contains("brokenModulation")) {
|
||||
brokenModulation = config.conf[name]["brokenModulation"];
|
||||
}
|
||||
if (config.conf[name].contains("oqpsk")) {
|
||||
oqpsk = config.conf[name]["oqpsk"];
|
||||
}
|
||||
config.release();
|
||||
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
|
||||
demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, 1e-6, 0.01);
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
|
||||
demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, oqpsk, 1e-6, 0.01);
|
||||
split.init(&demod.out);
|
||||
split.bindStream(&symSinkStream);
|
||||
split.bindStream(&sinkStream);
|
||||
@ -99,6 +102,7 @@ public:
|
||||
double bw = gui::waterfall.getBandwidth();
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
|
||||
|
||||
demod.setBrokenModulation(brokenModulation);
|
||||
demod.setInput(vfo->output);
|
||||
|
||||
demod.start();
|
||||
@ -151,6 +155,13 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox(CONCAT("OQPSK##oqpsk", _this->name), &_this->oqpsk)) {
|
||||
_this->demod.setOQPSK(_this->oqpsk);
|
||||
config.acquire();
|
||||
config.conf[_this->name]["oqpsk"] = _this->oqpsk;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); }
|
||||
|
||||
if (_this->recording) {
|
||||
@ -245,7 +256,7 @@ private:
|
||||
uint64_t dataWritten = 0;
|
||||
std::ofstream recFile;
|
||||
bool brokenModulation = false;
|
||||
|
||||
bool oqpsk = false;
|
||||
int8_t* writeBuffer;
|
||||
};
|
||||
|
||||
|
@ -11,8 +11,8 @@ namespace dsp::demod {
|
||||
public:
|
||||
Meteor() {}
|
||||
|
||||
Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, omegaGain, muGain);
|
||||
Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, oqpsk, omegaGain, muGain);
|
||||
}
|
||||
|
||||
~Meteor() {
|
||||
@ -21,11 +21,12 @@ namespace dsp::demod {
|
||||
taps::free(rrcTaps);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
_symbolrate = symbolrate;
|
||||
_samplerate = samplerate;
|
||||
_rrcTapCount = rrcTapCount;
|
||||
_rrcBeta = rrcBeta;
|
||||
_oqpsk = oqpsk;
|
||||
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.init(NULL, rrcTaps);
|
||||
@ -129,6 +130,12 @@ namespace dsp::demod {
|
||||
costas.setBrokenModulation(enabled);
|
||||
}
|
||||
|
||||
void setOQPSK(bool enabled) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_oqpsk = enabled;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
@ -144,6 +151,18 @@ namespace dsp::demod {
|
||||
rrc.process(count, in, out);
|
||||
agc.process(count, out, out);
|
||||
costas.process(count, out, out);
|
||||
|
||||
if (_oqpsk) {
|
||||
// Single sample delay + deinterleave
|
||||
for (int i = 0; i < count; i++) {
|
||||
float tmp = out[i].im;
|
||||
out[i].im = lastI;
|
||||
lastI = tmp;
|
||||
}
|
||||
|
||||
// TODO: Additional 1/24th sample delay
|
||||
}
|
||||
|
||||
return recov.process(count, out, out);
|
||||
}
|
||||
|
||||
@ -166,6 +185,8 @@ namespace dsp::demod {
|
||||
double _samplerate;
|
||||
int _rrcTapCount;
|
||||
double _rrcBeta;
|
||||
float lastI = 0.0f;
|
||||
bool _oqpsk = false;
|
||||
|
||||
tap<float> rrcTaps;
|
||||
filter::FIR<complex_t, float> rrc;
|
||||
|
8
decoder_modules/pager_decoder/CMakeLists.txt
Normal file
8
decoder_modules/pager_decoder/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(pager_decoder)
|
||||
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(pager_decoder PRIVATE "src/")
|
11
decoder_modules/pager_decoder/src/decoder.h
Normal file
11
decoder_modules/pager_decoder/src/decoder.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <signal_path/vfo_manager.h>
|
||||
|
||||
class Decoder {
|
||||
public:
|
||||
virtual ~Decoder() {}
|
||||
virtual void showMenu() {};
|
||||
virtual void setVFO(VFOManager::VFO* vfo) = 0;
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
};
|
96
decoder_modules/pager_decoder/src/flex/decoder.h
Normal file
96
decoder_modules/pager_decoder/src/flex/decoder.h
Normal file
@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
#include "../decoder.h"
|
||||
#include <signal_path/vfo_manager.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <gui/widgets/symbol_diagram.h>
|
||||
#include <gui/style.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include "flex.h"
|
||||
|
||||
class FLEXDecoder : public Decoder {
|
||||
dsp::stream<float> dummy1;
|
||||
dsp::stream<uint8_t> dummy2;
|
||||
public:
|
||||
FLEXDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, 1600) {
|
||||
this->name = name;
|
||||
this->vfo = vfo;
|
||||
|
||||
// Define baudrate options
|
||||
baudrates.define(1600, "1600 Baud", 1600);
|
||||
baudrates.define(3200, "3200 Baud", 3200);
|
||||
baudrates.define(6400, "6400 Baud", 6400);
|
||||
|
||||
// Init DSP
|
||||
vfo->setBandwidthLimits(12500, 12500, true);
|
||||
vfo->setSampleRate(16000, 12500);
|
||||
reshape.init(&dummy1, 1600.0, (1600 / 30.0) - 1600.0);
|
||||
dataHandler.init(&dummy2, _dataHandler, this);
|
||||
diagHandler.init(&reshape.out, _diagHandler, this);
|
||||
}
|
||||
|
||||
~FLEXDecoder() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
ImGui::LeftLabel("Baudrate");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##pager_decoder_flex_br_" + name).c_str(), &brId, baudrates.txt)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ImGui::FillWidth();
|
||||
diag.draw();
|
||||
}
|
||||
|
||||
void setVFO(VFOManager::VFO* vfo) {
|
||||
this->vfo = vfo;
|
||||
vfo->setBandwidthLimits(12500, 12500, true);
|
||||
vfo->setSampleRate(24000, 12500);
|
||||
// dsp.setInput(vfo->output);
|
||||
}
|
||||
|
||||
void start() {
|
||||
flog::debug("FLEX start");
|
||||
// dsp.start();
|
||||
reshape.start();
|
||||
dataHandler.start();
|
||||
diagHandler.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
flog::debug("FLEX stop");
|
||||
// dsp.stop();
|
||||
reshape.stop();
|
||||
dataHandler.stop();
|
||||
diagHandler.stop();
|
||||
}
|
||||
|
||||
private:
|
||||
static void _dataHandler(uint8_t* data, int count, void* ctx) {
|
||||
FLEXDecoder* _this = (FLEXDecoder*)ctx;
|
||||
// _this->decoder.process(data, count);
|
||||
}
|
||||
|
||||
static void _diagHandler(float* data, int count, void* ctx) {
|
||||
FLEXDecoder* _this = (FLEXDecoder*)ctx;
|
||||
float* buf = _this->diag.acquireBuffer();
|
||||
memcpy(buf, data, count * sizeof(float));
|
||||
_this->diag.releaseBuffer();
|
||||
}
|
||||
|
||||
std::string name;
|
||||
|
||||
VFOManager::VFO* vfo;
|
||||
dsp::buffer::Reshaper<float> reshape;
|
||||
dsp::sink::Handler<uint8_t> dataHandler;
|
||||
dsp::sink::Handler<float> diagHandler;
|
||||
|
||||
flex::Decoder decoder;
|
||||
|
||||
ImGui::SymbolDiagram diag;
|
||||
|
||||
int brId = 0;
|
||||
|
||||
OptionList<int, int> baudrates;
|
||||
};
|
5
decoder_modules/pager_decoder/src/flex/flex.cpp
Normal file
5
decoder_modules/pager_decoder/src/flex/flex.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
#include "flex.h"
|
||||
|
||||
namespace flex {
|
||||
// TODO
|
||||
}
|
11
decoder_modules/pager_decoder/src/flex/flex.h
Normal file
11
decoder_modules/pager_decoder/src/flex/flex.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace flex {
|
||||
class Decoder {
|
||||
public:
|
||||
// TODO
|
||||
|
||||
private:
|
||||
// TODO
|
||||
};
|
||||
}
|
172
decoder_modules/pager_decoder/src/main.cpp
Normal file
172
decoder_modules/pager_decoder/src/main.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
#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 <gui/widgets/folder_select.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include "decoder.h"
|
||||
#include "pocsag/decoder.h"
|
||||
#include "flex/decoder.h"
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "pager_decoder",
|
||||
/* Description: */ "POCSAG and Flex Pager Decoder"
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
enum Protocol {
|
||||
PROTOCOL_INVALID = -1,
|
||||
PROTOCOL_POCSAG,
|
||||
PROTOCOL_FLEX
|
||||
};
|
||||
|
||||
class PagerDecoderModule : public ModuleManager::Instance {
|
||||
public:
|
||||
PagerDecoderModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
// Define protocols
|
||||
protocols.define("POCSAG", PROTOCOL_POCSAG);
|
||||
//protocols.define("FLEX", PROTOCOL_FLEX);
|
||||
|
||||
// Initialize VFO with default values
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 12500, 24000, 12500, 12500, true);
|
||||
vfo->setSnapInterval(1);
|
||||
|
||||
// Select the protocol
|
||||
selectProtocol(PROTOCOL_POCSAG);
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
}
|
||||
|
||||
~PagerDecoderModule() {
|
||||
gui::menu.removeEntry(name);
|
||||
// Stop DSP
|
||||
if (enabled) {
|
||||
decoder->stop();
|
||||
decoder.reset();
|
||||
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), 12500, 24000, 12500, 12500, true);
|
||||
vfo->setSnapInterval(1);
|
||||
|
||||
decoder->setVFO(vfo);
|
||||
decoder->start();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
decoder->stop();
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void selectProtocol(Protocol newProto) {
|
||||
// Cannot change while disabled
|
||||
if (!enabled) { return; }
|
||||
|
||||
// If the protocol hasn't changed, no need to do anything
|
||||
if (newProto == proto) { return; }
|
||||
|
||||
// Delete current decoder
|
||||
decoder.reset();
|
||||
|
||||
// Create a new decoder
|
||||
switch (newProto) {
|
||||
case PROTOCOL_POCSAG:
|
||||
decoder = std::make_unique<POCSAGDecoder>(name, vfo);
|
||||
break;
|
||||
case PROTOCOL_FLEX:
|
||||
decoder = std::make_unique<FLEXDecoder>(name, vfo);
|
||||
break;
|
||||
default:
|
||||
flog::error("Tried to select unknown pager protocol");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the new decoder
|
||||
decoder->start();
|
||||
|
||||
// Save selected protocol
|
||||
proto = newProto;
|
||||
}
|
||||
|
||||
private:
|
||||
static void menuHandler(void* ctx) {
|
||||
PagerDecoderModule* _this = (PagerDecoderModule*)ctx;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (!_this->enabled) { style::beginDisabled(); }
|
||||
|
||||
ImGui::LeftLabel("Protocol");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##pager_decoder_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
|
||||
_this->selectProtocol(_this->protocols.value(_this->protoId));
|
||||
}
|
||||
|
||||
if (_this->decoder) { _this->decoder->showMenu(); }
|
||||
|
||||
ImGui::Button(("Record##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
|
||||
ImGui::Button(("Show Messages##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
Protocol proto = PROTOCOL_INVALID;
|
||||
int protoId = 0;
|
||||
|
||||
OptionList<std::string, Protocol> protocols;
|
||||
|
||||
// DSP Chain
|
||||
VFOManager::VFO* vfo;
|
||||
std::unique_ptr<Decoder> decoder;
|
||||
|
||||
bool showLines = false;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Create default recording directory
|
||||
json def = json({});
|
||||
config.setPath(core::args["root"].s() + "/pager_decoder_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new PagerDecoderModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (PagerDecoderModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
105
decoder_modules/pager_decoder/src/pocsag/decoder.h
Normal file
105
decoder_modules/pager_decoder/src/pocsag/decoder.h
Normal file
@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
#include "../decoder.h"
|
||||
#include <signal_path/vfo_manager.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <gui/widgets/symbol_diagram.h>
|
||||
#include <gui/style.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include "dsp.h"
|
||||
#include "pocsag.h"
|
||||
|
||||
#define BAUDRATE 2400
|
||||
#define SAMPLERATE (BAUDRATE*10)
|
||||
|
||||
class POCSAGDecoder : public Decoder {
|
||||
public:
|
||||
POCSAGDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, BAUDRATE) {
|
||||
this->name = name;
|
||||
this->vfo = vfo;
|
||||
|
||||
// Define baudrate options
|
||||
baudrates.define(512, "512 Baud", 512);
|
||||
baudrates.define(1200, "1200 Baud", 1200);
|
||||
baudrates.define(2400, "2400 Baud", 2400);
|
||||
|
||||
// Init DSP
|
||||
vfo->setBandwidthLimits(12500, 12500, true);
|
||||
vfo->setSampleRate(SAMPLERATE, 12500);
|
||||
dsp.init(vfo->output, SAMPLERATE, BAUDRATE);
|
||||
reshape.init(&dsp.soft, BAUDRATE, (BAUDRATE / 30.0) - BAUDRATE);
|
||||
dataHandler.init(&dsp.out, _dataHandler, this);
|
||||
diagHandler.init(&reshape.out, _diagHandler, this);
|
||||
|
||||
// Init decoder
|
||||
decoder.onMessage.bind(&POCSAGDecoder::messageHandler, this);
|
||||
}
|
||||
|
||||
~POCSAGDecoder() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
ImGui::LeftLabel("Baudrate");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##pager_decoder_pocsag_br_" + name).c_str(), &brId, baudrates.txt)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ImGui::FillWidth();
|
||||
diag.draw();
|
||||
}
|
||||
|
||||
void setVFO(VFOManager::VFO* vfo) {
|
||||
this->vfo = vfo;
|
||||
vfo->setBandwidthLimits(12500, 12500, true);
|
||||
vfo->setSampleRate(24000, 12500);
|
||||
dsp.setInput(vfo->output);
|
||||
}
|
||||
|
||||
void start() {
|
||||
dsp.start();
|
||||
reshape.start();
|
||||
dataHandler.start();
|
||||
diagHandler.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
dsp.stop();
|
||||
reshape.stop();
|
||||
dataHandler.stop();
|
||||
diagHandler.stop();
|
||||
}
|
||||
|
||||
private:
|
||||
static void _dataHandler(uint8_t* data, int count, void* ctx) {
|
||||
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
|
||||
_this->decoder.process(data, count);
|
||||
}
|
||||
|
||||
static void _diagHandler(float* data, int count, void* ctx) {
|
||||
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
|
||||
float* buf = _this->diag.acquireBuffer();
|
||||
memcpy(buf, data, count * sizeof(float));
|
||||
_this->diag.releaseBuffer();
|
||||
}
|
||||
|
||||
void messageHandler(pocsag::Address addr, pocsag::MessageType type, const std::string& msg) {
|
||||
flog::debug("[{}]: '{}'", (uint32_t)addr, msg);
|
||||
}
|
||||
|
||||
std::string name;
|
||||
VFOManager::VFO* vfo;
|
||||
|
||||
POCSAGDSP dsp;
|
||||
dsp::buffer::Reshaper<float> reshape;
|
||||
dsp::sink::Handler<uint8_t> dataHandler;
|
||||
dsp::sink::Handler<float> diagHandler;
|
||||
|
||||
pocsag::Decoder decoder;
|
||||
|
||||
ImGui::SymbolDiagram diag;
|
||||
|
||||
int brId = 2;
|
||||
|
||||
OptionList<int, int> baudrates;
|
||||
};
|
76
decoder_modules/pager_decoder/src/pocsag/dsp.h
Normal file
76
decoder_modules/pager_decoder/src/pocsag/dsp.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <dsp/demod/quadrature.h>
|
||||
#include <dsp/clock_recovery/mm.h>
|
||||
#include <dsp/taps/root_raised_cosine.h>
|
||||
#include <dsp/correction/dc_blocker.h>
|
||||
#include <dsp/loop/fast_agc.h>
|
||||
#include <dsp/digital/binary_slicer.h>
|
||||
#include <dsp/routing/doubler.h>
|
||||
|
||||
class POCSAGDSP : public dsp::Processor<dsp::complex_t, uint8_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
|
||||
public:
|
||||
POCSAGDSP() {}
|
||||
POCSAGDSP(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) { init(in, samplerate, baudrate); }
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) {
|
||||
// Save settings
|
||||
_samplerate = samplerate;
|
||||
|
||||
// Configure blocks
|
||||
demod.init(NULL, -4500.0, samplerate);
|
||||
float taps[] = { 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f };
|
||||
shape = dsp::taps::fromArray<float>(10, taps);
|
||||
fir.init(NULL, shape);
|
||||
recov.init(NULL, samplerate/baudrate, 1e-4, 1.0, 0.05);
|
||||
|
||||
// Free useless buffers
|
||||
fir.out.free();
|
||||
recov.out.free();
|
||||
|
||||
// Init base
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
int process(int count, dsp::complex_t* in, float* softOut, uint8_t* out) {
|
||||
count = demod.process(count, in, demod.out.readBuf);
|
||||
count = fir.process(count, demod.out.readBuf, demod.out.readBuf);
|
||||
count = recov.process(count, demod.out.readBuf, softOut);
|
||||
dsp::digital::BinarySlicer::process(count, softOut, out);
|
||||
return count;
|
||||
}
|
||||
|
||||
void setBaudrate(double baudrate) {
|
||||
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; }
|
||||
|
||||
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!base_type::out.swap(count)) { return -1; }
|
||||
if (count) { if (!soft.swap(count)) { return -1; } }
|
||||
return count;
|
||||
}
|
||||
|
||||
dsp::stream<float> soft;
|
||||
|
||||
private:
|
||||
dsp::demod::Quadrature demod;
|
||||
dsp::tap<float> shape;
|
||||
dsp::filter::FIR<float, float> fir;
|
||||
dsp::clock_recovery::MM<float> recov;
|
||||
|
||||
double _samplerate;
|
||||
};
|
173
decoder_modules/pager_decoder/src/pocsag/pocsag.cpp
Normal file
173
decoder_modules/pager_decoder/src/pocsag/pocsag.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
#include "pocsag.h"
|
||||
#include <string.h>
|
||||
#include <utils/flog.h>
|
||||
|
||||
#define POCSAG_FRAME_SYNC_CODEWORD ((uint32_t)(0b01111100110100100001010111011000))
|
||||
#define POCSAG_IDLE_CODEWORD_DATA ((uint32_t)(0b011110101100100111000))
|
||||
#define POCSAG_BATCH_BIT_COUNT (POCSAG_BATCH_CODEWORD_COUNT*32)
|
||||
#define POCSAG_DATA_BITS_PER_CW 20
|
||||
|
||||
#define POCSAG_GEN_POLY ((uint32_t)(0b11101101001))
|
||||
|
||||
namespace pocsag {
|
||||
const char NUMERIC_CHARSET[] = {
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'*',
|
||||
'U',
|
||||
' ',
|
||||
'-',
|
||||
']',
|
||||
'['
|
||||
};
|
||||
|
||||
Decoder::Decoder() {
|
||||
// Zero out batch
|
||||
memset(batch, 0, sizeof(batch));
|
||||
}
|
||||
|
||||
void Decoder::process(uint8_t* symbols, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Get symbol
|
||||
uint32_t s = symbols[i];
|
||||
|
||||
// If not sync, try to acquire sync (TODO: sync confidence)
|
||||
if (!synced) {
|
||||
// Append new symbol to sync shift register
|
||||
syncSR = (syncSR << 1) | s;
|
||||
|
||||
// Test for sync
|
||||
synced = (distance(syncSR, POCSAG_FRAME_SYNC_CODEWORD) <= POCSAG_SYNC_DIST);
|
||||
|
||||
// Go to next symbol
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Flush message on desync
|
||||
|
||||
// Append bit to batch
|
||||
batch[batchOffset >> 5] |= (s << (31 - (batchOffset & 0b11111)));
|
||||
batchOffset++;
|
||||
|
||||
// On end of batch, decode and reset
|
||||
if (batchOffset >= POCSAG_BATCH_BIT_COUNT) {
|
||||
decodeBatch();
|
||||
batchOffset = 0;
|
||||
synced = false;
|
||||
memset(batch, 0, sizeof(batch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Decoder::distance(uint32_t a, uint32_t b) {
|
||||
uint32_t diff = a ^ b;
|
||||
int dist = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
dist += (diff >> i ) & 1;
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
bool Decoder::correctCodeword(Codeword in, Codeword& out) {
|
||||
|
||||
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
void Decoder::flushMessage() {
|
||||
if (!msg.empty()) {
|
||||
// Send out message
|
||||
onMessage(addr, msgType, msg);
|
||||
|
||||
// Reset state
|
||||
msg.clear();
|
||||
currChar = 0;
|
||||
currOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void printbin(uint32_t cw) {
|
||||
for (int i = 31; i >= 0; i--) {
|
||||
printf("%c", ((cw >> i) & 1) ? '1':'0');
|
||||
}
|
||||
}
|
||||
|
||||
void bitswapChar(char in, char& out) {
|
||||
out = 0;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
out |= ((in >> (6-i)) & 1) << i;
|
||||
}
|
||||
}
|
||||
|
||||
void Decoder::decodeBatch() {
|
||||
for (int i = 0; i < POCSAG_BATCH_CODEWORD_COUNT; i++) {
|
||||
// Get codeword
|
||||
Codeword cw = batch[i];
|
||||
|
||||
// Correct errors. If corrupted, skip
|
||||
if (!correctCodeword(cw, cw)) { continue; }
|
||||
// TODO: End message if two consecutive are corrupt
|
||||
|
||||
// Get codeword type
|
||||
CodewordType type = (CodewordType)((cw >> 31) & 1);
|
||||
if (type == CODEWORD_TYPE_ADDRESS && (cw >> 11) == POCSAG_IDLE_CODEWORD_DATA) {
|
||||
type = CODEWORD_TYPE_IDLE;
|
||||
}
|
||||
|
||||
// Decode codeword
|
||||
if (type == CODEWORD_TYPE_IDLE) {
|
||||
// If a non-empty message is available, send it out and clear
|
||||
flushMessage();
|
||||
}
|
||||
else if (type == CODEWORD_TYPE_ADDRESS) {
|
||||
// If a non-empty message is available, send it out and clear
|
||||
flushMessage();
|
||||
|
||||
// Decode message type
|
||||
msgType = MESSAGE_TYPE_ALPHANUMERIC;
|
||||
// msgType = (MessageType)((cw >> 11) & 0b11);
|
||||
|
||||
// Decode address and append lower 8 bits from position
|
||||
addr = ((cw >> 13) & 0b111111111111111111) << 3;
|
||||
addr |= (i >> 1);
|
||||
}
|
||||
else if (type == CODEWORD_TYPE_MESSAGE) {
|
||||
// Extract the 20 data bits
|
||||
uint32_t data = (cw >> 11) & 0b11111111111111111111;
|
||||
|
||||
// Decode data depending on message type
|
||||
if (msgType == MESSAGE_TYPE_NUMERIC) {
|
||||
// Numeric messages pack 5 characters per message codeword
|
||||
msg += NUMERIC_CHARSET[(data >> 16) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[(data >> 12) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[(data >> 8) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[(data >> 4) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[data & 0b1111];
|
||||
}
|
||||
else if (msgType == MESSAGE_TYPE_ALPHANUMERIC) {
|
||||
// Unpack ascii bits 7 at a time (TODO: could be more efficient)
|
||||
for (int i = 19; i >= 0; i--) {
|
||||
// Append bit to char
|
||||
currChar |= ((data >> i) & 1) << (currOffset++);
|
||||
|
||||
// When the char is full, append to message
|
||||
if (currOffset >= 7) {
|
||||
// TODO: maybe replace with std::isprint
|
||||
if (currChar) { msg += currChar; }
|
||||
currChar = 0;
|
||||
currOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
decoder_modules/pager_decoder/src/pocsag/pocsag.h
Normal file
51
decoder_modules/pager_decoder/src/pocsag/pocsag.h
Normal file
@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <utils/new_event.h>
|
||||
|
||||
#define POCSAG_SYNC_DIST 4
|
||||
#define POCSAG_BATCH_CODEWORD_COUNT 16
|
||||
|
||||
namespace pocsag {
|
||||
enum CodewordType {
|
||||
CODEWORD_TYPE_IDLE = -1,
|
||||
CODEWORD_TYPE_ADDRESS = 0,
|
||||
CODEWORD_TYPE_MESSAGE = 1
|
||||
};
|
||||
|
||||
enum MessageType {
|
||||
MESSAGE_TYPE_NUMERIC = 0b00,
|
||||
MESSAGE_TYPE_ALPHANUMERIC = 0b11
|
||||
};
|
||||
|
||||
using Codeword = uint32_t;
|
||||
using Address = uint32_t;
|
||||
|
||||
class Decoder {
|
||||
public:
|
||||
Decoder();
|
||||
|
||||
void process(uint8_t* symbols, int count);
|
||||
|
||||
NewEvent<Address, MessageType, const std::string&> onMessage;
|
||||
|
||||
private:
|
||||
static int distance(uint32_t a, uint32_t b);
|
||||
bool correctCodeword(Codeword in, Codeword& out);
|
||||
void flushMessage();
|
||||
void decodeBatch();
|
||||
|
||||
uint32_t syncSR = 0;
|
||||
bool synced = false;
|
||||
int batchOffset = 0;
|
||||
|
||||
Codeword batch[POCSAG_BATCH_CODEWORD_COUNT];
|
||||
|
||||
Address addr;
|
||||
MessageType msgType;
|
||||
std::string msg;
|
||||
|
||||
char currChar = 0;
|
||||
int currOffset = 0;
|
||||
};
|
||||
}
|
@ -45,7 +45,6 @@ namespace demod {
|
||||
virtual int getDefaultDeemphasisMode() = 0;
|
||||
virtual bool getFMIFNRAllowed() = 0;
|
||||
virtual bool getNBAllowed() = 0;
|
||||
virtual bool getAFNRAllowed() = 0;
|
||||
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
|
||||
};
|
||||
}
|
||||
|
@ -86,7 +86,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getAFNRAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
@ -92,7 +92,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getAFNRAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
@ -79,7 +79,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getAFNRAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
@ -79,7 +79,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getAFNRAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
@ -75,7 +75,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return true; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getAFNRAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
@ -59,7 +59,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getAFNRAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
|
||||
|
||||
private:
|
||||
|
@ -80,7 +80,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||
bool getFMIFNRAllowed() { return false; }
|
||||
bool getNBAllowed() { return true; }
|
||||
bool getAFNRAllowed() { return true; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
private:
|
||||
|
@ -1,22 +1,22 @@
|
||||
#pragma once
|
||||
#include "../demod.h"
|
||||
#include <dsp/demod/broadcast_fm.h>
|
||||
#include <dsp/clock_recovery/mm.h>
|
||||
#include <dsp/clock_recovery/fd.h>
|
||||
#include <dsp/taps/root_raised_cosine.h>
|
||||
#include <dsp/digital/binary_slicer.h>
|
||||
#include <dsp/digital/manchester_decoder.h>
|
||||
#include <dsp/digital/differential_decoder.h>
|
||||
#include "../rds_demod.h"
|
||||
#include <gui/widgets/symbol_diagram.h>
|
||||
#include <fstream>
|
||||
#include <rds.h>
|
||||
|
||||
namespace demod {
|
||||
enum RDSRegion {
|
||||
RDS_REGION_EUROPE,
|
||||
RDS_REGION_NORTH_AMERICA
|
||||
};
|
||||
|
||||
class WFM : public Demodulator {
|
||||
public:
|
||||
WFM() {}
|
||||
WFM() : diag(0.5, 4096) {}
|
||||
|
||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -29,10 +29,18 @@ namespace demod {
|
||||
this->name = name;
|
||||
_config = config;
|
||||
|
||||
// Define RDS regions
|
||||
rdsRegions.define("eu", "Europe", RDS_REGION_EUROPE);
|
||||
rdsRegions.define("na", "North America", RDS_REGION_NORTH_AMERICA);
|
||||
|
||||
// Register FFT draw handler
|
||||
fftRedrawHandler.handler = fftRedraw;
|
||||
fftRedrawHandler.ctx = this;
|
||||
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
|
||||
|
||||
// Default
|
||||
std::string rdsRegionStr = "eu";
|
||||
|
||||
// Load config
|
||||
_config->acquire();
|
||||
bool modified = false;
|
||||
@ -45,33 +53,50 @@ namespace demod {
|
||||
if (config->conf[name][getName()].contains("rds")) {
|
||||
_rds = config->conf[name][getName()]["rds"];
|
||||
}
|
||||
if (config->conf[name][getName()].contains("rdsInfo")) {
|
||||
_rdsInfo = config->conf[name][getName()]["rdsInfo"];
|
||||
}
|
||||
if (config->conf[name][getName()].contains("rdsRegion")) {
|
||||
rdsRegionStr = config->conf[name][getName()]["rdsRegion"];
|
||||
}
|
||||
_config->release(modified);
|
||||
|
||||
// Define structure
|
||||
// Load RDS region
|
||||
if (rdsRegions.keyExists(rdsRegionStr)) {
|
||||
rdsRegionId = rdsRegions.keyId(rdsRegionStr);
|
||||
rdsRegion = rdsRegions.value(rdsRegionId);
|
||||
}
|
||||
else {
|
||||
rdsRegion = RDS_REGION_EUROPE;
|
||||
rdsRegionId = rdsRegions.valueId(rdsRegion);
|
||||
}
|
||||
|
||||
// Init DSP
|
||||
demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
|
||||
recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01);
|
||||
slice.init(&recov.out);
|
||||
manch.init(&slice.out);
|
||||
diff.init(&manch.out, 2);
|
||||
hs.init(&diff.out, rdsHandler, this);
|
||||
rdsDemod.init(&demod.rdsOut, _rdsInfo);
|
||||
hs.init(&rdsDemod.out, rdsHandler, this);
|
||||
reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096);
|
||||
diagHandler.init(&reshape.out, _diagHandler, this);
|
||||
|
||||
// Init RDS display
|
||||
diag.lines.push_back(-0.8);
|
||||
diag.lines.push_back(0.8);
|
||||
}
|
||||
|
||||
void start() {
|
||||
demod.start();
|
||||
recov.start();
|
||||
slice.start();
|
||||
manch.start();
|
||||
diff.start();
|
||||
rdsDemod.start();
|
||||
hs.start();
|
||||
reshape.start();
|
||||
diagHandler.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
demod.stop();
|
||||
recov.stop();
|
||||
slice.stop();
|
||||
manch.stop();
|
||||
diff.stop();
|
||||
rdsDemod.stop();
|
||||
hs.stop();
|
||||
reshape.stop();
|
||||
diagHandler.stop();
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
@ -94,14 +119,129 @@ namespace demod {
|
||||
_config->release(true);
|
||||
}
|
||||
|
||||
// if (_rds) {
|
||||
// if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); }
|
||||
// if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); }
|
||||
// if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); }
|
||||
// if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); }
|
||||
// if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); }
|
||||
// if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); }
|
||||
// }
|
||||
// TODO: This might break when the entire radio module is disabled
|
||||
if (!_rds) { ImGui::BeginDisabled(); }
|
||||
if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) {
|
||||
setAdvancedRds(_rdsInfo);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["rdsInfo"] = _rdsInfo;
|
||||
_config->release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##_radio_wfm_rds_region_" + name).c_str(), &rdsRegionId, rdsRegions.txt)) {
|
||||
rdsRegion = rdsRegions.value(rdsRegionId);
|
||||
_config->acquire();
|
||||
_config->conf[name][getName()]["rdsRegion"] = rdsRegions.key(rdsRegionId);
|
||||
_config->release(true);
|
||||
}
|
||||
if (!_rds) { ImGui::EndDisabled(); }
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (_rds && _rdsInfo) {
|
||||
ImGui::BeginTable(("##radio_wfm_rds_info_tbl_" + name).c_str(), 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
|
||||
if (rdsDecode.piCodeValid()) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("PI Code");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
|
||||
ImGui::Text("0x%04X (%s)", rdsDecode.getPICode(), rdsDecode.getCallsign().c_str());
|
||||
}
|
||||
else {
|
||||
ImGui::Text("0x%04X", rdsDecode.getPICode());
|
||||
}
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Country Code");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("%d", rdsDecode.getCountryCode());
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Program Coverage");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("%s (%d)", rds::AREA_COVERAGE_TO_STR[rdsDecode.getProgramCoverage()], rdsDecode.getProgramCoverage());
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Reference Number");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("%d", rdsDecode.getProgramRefNumber());
|
||||
}
|
||||
else {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("PI Code");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
|
||||
ImGui::TextUnformatted("0x---- (----)");
|
||||
}
|
||||
else {
|
||||
ImGui::TextUnformatted("0x----");
|
||||
}
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Country Code");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted("--"); // TODO: String
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Program Coverage");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted("------- (--)");
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Reference Number");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted("--");
|
||||
}
|
||||
|
||||
if (rdsDecode.programTypeValid()) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Program Type");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
|
||||
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_US_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
|
||||
}
|
||||
else {
|
||||
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_EU_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Program Type");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted("------- (--)"); // TODO: String
|
||||
}
|
||||
|
||||
if (rdsDecode.musicValid()) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Music");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("%s", rdsDecode.getMusic() ? "Yes":"No");
|
||||
}
|
||||
else {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Music");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted("---");
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
diag.draw();
|
||||
}
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) {
|
||||
@ -130,7 +270,6 @@ namespace demod {
|
||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
|
||||
bool getFMIFNRAllowed() { return true; }
|
||||
bool getNBAllowed() { return false; }
|
||||
bool getAFNRAllowed() { return false; }
|
||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||
|
||||
// ============= DEDICATED FUNCTIONS =============
|
||||
@ -140,12 +279,24 @@ namespace demod {
|
||||
demod.setStereo(_stereo);
|
||||
}
|
||||
|
||||
void setAdvancedRds(bool enabled) {
|
||||
rdsDemod.setSoftEnabled(enabled);
|
||||
_rdsInfo = enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static void rdsHandler(uint8_t* data, int count, void* ctx) {
|
||||
WFM* _this = (WFM*)ctx;
|
||||
_this->rdsDecode.process(data, count);
|
||||
}
|
||||
|
||||
static void _diagHandler(float* data, int count, void* ctx) {
|
||||
WFM* _this = (WFM*)ctx;
|
||||
float* buf = _this->diag.acquireBuffer();
|
||||
memcpy(buf, data, count * sizeof(float));
|
||||
_this->diag.releaseBuffer();
|
||||
}
|
||||
|
||||
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
|
||||
WFM* _this = (WFM*)ctx;
|
||||
if (!_this->_rds) { return; }
|
||||
@ -187,23 +338,31 @@ namespace demod {
|
||||
}
|
||||
|
||||
dsp::demod::BroadcastFM demod;
|
||||
dsp::clock_recovery::FD recov;
|
||||
dsp::digital::BinarySlicer slice;
|
||||
dsp::digital::ManchesterDecoder manch;
|
||||
dsp::digital::DifferentialDecoder diff;
|
||||
RDSDemod rdsDemod;
|
||||
dsp::sink::Handler<uint8_t> hs;
|
||||
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
|
||||
|
||||
rds::RDSDecoder rdsDecode;
|
||||
dsp::buffer::Reshaper<float> reshape;
|
||||
dsp::sink::Handler<float> diagHandler;
|
||||
ImGui::SymbolDiagram diag;
|
||||
|
||||
rds::Decoder rdsDecode;
|
||||
|
||||
ConfigManager* _config = NULL;
|
||||
|
||||
bool _stereo = false;
|
||||
bool _lowPass = true;
|
||||
bool _rds = false;
|
||||
bool _rdsInfo = false;
|
||||
float muGain = 0.01;
|
||||
float omegaGain = (0.01*0.01)/4.0;
|
||||
|
||||
int rdsRegionId = 0;
|
||||
RDSRegion rdsRegion = RDS_REGION_EUROPE;
|
||||
|
||||
OptionList<std::string, RDSRegion> rdsRegions;
|
||||
|
||||
|
||||
std::string name;
|
||||
};
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
#include <dsp/noise_reduction/noise_blanker.h>
|
||||
#include <dsp/noise_reduction/fm_if.h>
|
||||
#include <dsp/noise_reduction/squelch.h>
|
||||
#include <dsp/noise_reduction/audio.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/filter/deephasis.h>
|
||||
#include <core.h>
|
||||
@ -84,11 +83,9 @@ public:
|
||||
|
||||
resamp.init(NULL, 250000.0, 48000.0);
|
||||
deemp.init(NULL, 50e-6, 48000.0);
|
||||
afNR.init(NULL, 1024);
|
||||
|
||||
afChain.addBlock(&resamp, true);
|
||||
afChain.addBlock(&deemp, false);
|
||||
afChain.addBlock(&afNR, false);
|
||||
|
||||
// Initialize the sink
|
||||
srChangeHandler.ctx = this;
|
||||
@ -250,12 +247,6 @@ private:
|
||||
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
// Noise reduction
|
||||
if (_this->afNRAllowed) {
|
||||
if (ImGui::Checkbox(("Audio Noise Reduction##_radio_afnr_ena_" + _this->name).c_str(), &_this->afNREnabled)) {
|
||||
_this->setAFNREnabled(_this->afNREnabled);
|
||||
}
|
||||
}
|
||||
|
||||
// Squelch
|
||||
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
|
||||
@ -379,8 +370,6 @@ private:
|
||||
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
|
||||
nbAllowed = selectedDemod->getNBAllowed();
|
||||
nbEnabled = false;
|
||||
afNRAllowed = selectedDemod->getAFNRAllowed();
|
||||
afNREnabled = false;
|
||||
nbLevel = 0.0f;
|
||||
double ifSamplerate = selectedDemod->getIFSampleRate();
|
||||
config.acquire();
|
||||
@ -422,9 +411,6 @@ private:
|
||||
if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) {
|
||||
nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"];
|
||||
}
|
||||
if (config.conf[name][selectedDemod->getName()].contains("audioNoiseReductionEnabled")) {
|
||||
nbEnabled = config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"];
|
||||
}
|
||||
config.release();
|
||||
|
||||
// Configure VFO
|
||||
@ -460,10 +446,7 @@ private:
|
||||
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Configure deemphasis
|
||||
setDeemphasisMode(deempAllowed ? deempModes[deempId] : DEEMP_MODE_NONE);
|
||||
|
||||
// Configure AF NR
|
||||
setAFNREnabled(afNRAllowed && afNREnabled);
|
||||
setDeemphasisMode(deempModes[deempId]);
|
||||
}
|
||||
else {
|
||||
// Disable everything if post processing is disabled
|
||||
@ -525,17 +508,6 @@ private:
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setAFNREnabled(bool enable) {
|
||||
afNREnabled = enable;
|
||||
if (!postProcEnabled || !selectedDemod) { return; }
|
||||
afChain.setBlockEnabled(&afNR, afNREnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
|
||||
|
||||
// Save config
|
||||
config.acquire();
|
||||
config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"] = nbEnabled;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
void setNBEnabled(bool enable) {
|
||||
nbEnabled = enable;
|
||||
if (!selectedDemod) { return; }
|
||||
@ -625,14 +597,16 @@ private:
|
||||
|
||||
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
|
||||
RadioModule* _this = (RadioModule*)ctx;
|
||||
if (!_this->enabled || !_this->selectedDemod) { return; }
|
||||
|
||||
// If no demod is selected, reject the command
|
||||
if (!_this->selectedDemod) { return; }
|
||||
|
||||
// Execute commands
|
||||
if (code == RADIO_IFACE_CMD_GET_MODE && out) {
|
||||
int* _out = (int*)out;
|
||||
*_out = _this->selectedDemodID;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_MODE && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_MODE && in && _this->enabled) {
|
||||
int* _in = (int*)in;
|
||||
_this->selectDemodByID((DemodID)*_in);
|
||||
}
|
||||
@ -640,7 +614,7 @@ private:
|
||||
float* _out = (float*)out;
|
||||
*_out = _this->bandwidth;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in && _this->enabled) {
|
||||
float* _in = (float*)in;
|
||||
if (_this->bandwidthLocked) { return; }
|
||||
_this->setBandwidth(*_in);
|
||||
@ -649,7 +623,7 @@ private:
|
||||
bool* _out = (bool*)out;
|
||||
*_out = _this->squelchEnabled;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in && _this->enabled) {
|
||||
bool* _in = (bool*)in;
|
||||
_this->setSquelchEnabled(*_in);
|
||||
}
|
||||
@ -657,7 +631,7 @@ private:
|
||||
float* _out = (float*)out;
|
||||
*_out = _this->squelchLevel;
|
||||
}
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in) {
|
||||
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in && _this->enabled) {
|
||||
float* _in = (float*)in;
|
||||
_this->setSquelchLevel(*_in);
|
||||
}
|
||||
@ -688,7 +662,6 @@ private:
|
||||
dsp::chain<dsp::stereo_t> afChain;
|
||||
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
|
||||
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
|
||||
dsp::noise_reduction::Audio afNR;
|
||||
|
||||
SinkManager::Stream stream;
|
||||
|
||||
@ -712,9 +685,6 @@ private:
|
||||
int deempId = 0;
|
||||
bool deempAllowed;
|
||||
|
||||
bool afNREnabled = false;
|
||||
bool afNRAllowed;
|
||||
|
||||
bool FMIFNRAllowed;
|
||||
bool FMIFNREnabled = false;
|
||||
int fmIFPresetId;
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
#include <utils/flog.h>
|
||||
|
||||
namespace rds {
|
||||
std::map<uint16_t, BlockType> SYNDROMES = {
|
||||
{ 0b1111011000, BLOCK_TYPE_A },
|
||||
@ -20,6 +22,98 @@ namespace rds {
|
||||
{ BLOCK_TYPE_D, 0b0110110100 }
|
||||
};
|
||||
|
||||
std::map<uint16_t, const char*> THREE_LETTER_CALLS = {
|
||||
{ 0x99A5, "KBW" },
|
||||
{ 0x99A6, "KCY" },
|
||||
{ 0x9990, "KDB" },
|
||||
{ 0x99A7, "KDF" },
|
||||
{ 0x9950, "KEX" },
|
||||
{ 0x9951, "KFH" },
|
||||
{ 0x9952, "KFI" },
|
||||
{ 0x9953, "KGA" },
|
||||
{ 0x9991, "KGB" },
|
||||
{ 0x9954, "KGO" },
|
||||
{ 0x9955, "KGU" },
|
||||
{ 0x9956, "KGW" },
|
||||
{ 0x9957, "KGY" },
|
||||
{ 0x99AA, "KHQ" },
|
||||
{ 0x9958, "KID" },
|
||||
{ 0x9959, "KIT" },
|
||||
{ 0x995A, "KJR" },
|
||||
{ 0x995B, "KLO" },
|
||||
{ 0x995C, "KLZ" },
|
||||
{ 0x995D, "KMA" },
|
||||
{ 0x995E, "KMJ" },
|
||||
{ 0x995F, "KNX" },
|
||||
{ 0x9960, "KOA" },
|
||||
{ 0x99AB, "KOB" },
|
||||
{ 0x9992, "KOY" },
|
||||
{ 0x9993, "KPQ" },
|
||||
{ 0x9964, "KQV" },
|
||||
{ 0x9994, "KSD" },
|
||||
{ 0x9965, "KSL" },
|
||||
{ 0x9966, "KUJ" },
|
||||
{ 0x9995, "KUT" },
|
||||
{ 0x9967, "KVI" },
|
||||
{ 0x9968, "KWG" },
|
||||
{ 0x9996, "KXL" },
|
||||
{ 0x9997, "KXO" },
|
||||
{ 0x996B, "KYW" },
|
||||
{ 0x9999, "WBT" },
|
||||
{ 0x996D, "WBZ" },
|
||||
{ 0x996E, "WDZ" },
|
||||
{ 0x996F, "WEW" },
|
||||
{ 0x999A, "WGH" },
|
||||
{ 0x9971, "WGL" },
|
||||
{ 0x9972, "WGN" },
|
||||
{ 0x9973, "WGR" },
|
||||
{ 0x999B, "WGY" },
|
||||
{ 0x9975, "WHA" },
|
||||
{ 0x9976, "WHB" },
|
||||
{ 0x9977, "WHK" },
|
||||
{ 0x9978, "WHO" },
|
||||
{ 0x999C, "WHP" },
|
||||
{ 0x999D, "WIL" },
|
||||
{ 0x997A, "WIP" },
|
||||
{ 0x99B3, "WIS" },
|
||||
{ 0x997B, "WJR" },
|
||||
{ 0x99B4, "WJW" },
|
||||
{ 0x99B5, "WJZ" },
|
||||
{ 0x997C, "WKY" },
|
||||
{ 0x997D, "WLS" },
|
||||
{ 0x997E, "WLW" },
|
||||
{ 0x999E, "WMC" },
|
||||
{ 0x999F, "WMT" },
|
||||
{ 0x9981, "WOC" },
|
||||
{ 0x99A0, "WOI" },
|
||||
{ 0x9983, "WOL" },
|
||||
{ 0x9984, "WOR" },
|
||||
{ 0x99A1, "WOW" },
|
||||
{ 0x99B9, "WRC" },
|
||||
{ 0x99A2, "WRR" },
|
||||
{ 0x99A3, "WSB" },
|
||||
{ 0x99A4, "WSM" },
|
||||
{ 0x9988, "WWJ" },
|
||||
{ 0x9989, "WWL" }
|
||||
};
|
||||
|
||||
std::map<uint16_t, const char*> NAT_LOC_LINKED_STATIONS = {
|
||||
{ 0xB01, "NPR-1" },
|
||||
{ 0xB02, "CBC - Radio One" },
|
||||
{ 0xB03, "CBC - Radio Two" },
|
||||
{ 0xB04, "Radio-Canada - Première Chaîne" },
|
||||
{ 0xB05, "Radio-Canada - Espace Musique" },
|
||||
{ 0xB06, "CBC" },
|
||||
{ 0xB07, "CBC" },
|
||||
{ 0xB08, "CBC" },
|
||||
{ 0xB09, "CBC" },
|
||||
{ 0xB0A, "NPR-2" },
|
||||
{ 0xB0B, "NPR-3" },
|
||||
{ 0xB0C, "NPR-4" },
|
||||
{ 0xB0D, "NPR-5" },
|
||||
{ 0xB0E, "NPR-6" }
|
||||
};
|
||||
|
||||
// 9876543210
|
||||
const uint16_t LFSR_POLY = 0b0110111001;
|
||||
const uint16_t IN_POLY = 0b1100011011;
|
||||
@ -28,7 +122,7 @@ namespace rds {
|
||||
const int DATA_LEN = 16;
|
||||
const int POLY_LEN = 10;
|
||||
|
||||
void RDSDecoder::process(uint8_t* symbols, int count) {
|
||||
void Decoder::process(uint8_t* symbols, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Shift in the bit
|
||||
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
|
||||
@ -54,18 +148,26 @@ namespace rds {
|
||||
type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT);
|
||||
}
|
||||
|
||||
// Save block while correcting errors (NOT YET)
|
||||
// Save block while correcting errors (NOT YET) <- idk why the "not yet is here", TODO: find why
|
||||
blocks[type] = correctErrors(shiftReg, type, blockAvail[type]);
|
||||
|
||||
// Update continous group count
|
||||
if (type == BLOCK_TYPE_A) { contGroup = 1; }
|
||||
else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; }
|
||||
// If block type is A, decode it directly, otherwise, update continous count
|
||||
if (type == BLOCK_TYPE_A) {
|
||||
decodeBlockA();
|
||||
}
|
||||
else if (type == BLOCK_TYPE_B) { contGroup = 1; }
|
||||
else if ((type == BLOCK_TYPE_C || type == BLOCK_TYPE_CP) && lastType == BLOCK_TYPE_B) { contGroup++; }
|
||||
else if (type == BLOCK_TYPE_D && (lastType == BLOCK_TYPE_C || lastType == BLOCK_TYPE_CP)) { contGroup++; }
|
||||
else { contGroup = 0; }
|
||||
else {
|
||||
// If block B is available, decode it alone.
|
||||
if (contGroup == 1) {
|
||||
decodeBlockB();
|
||||
}
|
||||
contGroup = 0;
|
||||
}
|
||||
|
||||
// If we've got an entire group, process it
|
||||
if (contGroup >= 4) {
|
||||
if (contGroup >= 3) {
|
||||
contGroup = 0;
|
||||
decodeGroup();
|
||||
}
|
||||
@ -76,7 +178,7 @@ namespace rds {
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t RDSDecoder::calcSyndrome(uint32_t block) {
|
||||
uint16_t Decoder::calcSyndrome(uint32_t block) {
|
||||
uint16_t syn = 0;
|
||||
|
||||
// Calculate the syndrome using a LFSR
|
||||
@ -95,7 +197,7 @@ namespace rds {
|
||||
return syn;
|
||||
}
|
||||
|
||||
uint32_t RDSDecoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
|
||||
uint32_t Decoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
|
||||
// Subtract the offset from block
|
||||
block ^= (uint32_t)OFFSETS[type];
|
||||
uint32_t out = block;
|
||||
@ -124,96 +226,264 @@ namespace rds {
|
||||
return out;
|
||||
}
|
||||
|
||||
void RDSDecoder::decodeGroup() {
|
||||
std::lock_guard<std::mutex> lck(groupMtx);
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
anyGroupLastUpdate = now;
|
||||
void Decoder::decodeBlockA() {
|
||||
// Acquire lock
|
||||
std::lock_guard<std::mutex> lck(blockAMtx);
|
||||
|
||||
// Make sure blocks A and B are available
|
||||
if (!blockAvail[BLOCK_TYPE_A] || !blockAvail[BLOCK_TYPE_B]) { return; }
|
||||
// If it didn't decode properly return
|
||||
if (!blockAvail[BLOCK_TYPE_A]) { return; }
|
||||
|
||||
// Decode PI code
|
||||
piCode = (blocks[BLOCK_TYPE_A] >> 10) & 0xFFFF;
|
||||
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
|
||||
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
|
||||
programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF;
|
||||
callsign = decodeCallsign(piCode);
|
||||
|
||||
// Update timeout
|
||||
blockALastUpdate = std::chrono::high_resolution_clock::now();;
|
||||
}
|
||||
|
||||
void Decoder::decodeBlockB() {
|
||||
// Acquire lock
|
||||
std::lock_guard<std::mutex> lck(blockBMtx);
|
||||
|
||||
// If it didn't decode properly return (TODO: Make sure this is not needed)
|
||||
if (!blockAvail[BLOCK_TYPE_B]) { return; }
|
||||
|
||||
// Decode group type and version
|
||||
uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
|
||||
GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
|
||||
groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
|
||||
groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
|
||||
|
||||
// Decode traffic program and program type
|
||||
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
|
||||
programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F);
|
||||
|
||||
if (groupType == 0) {
|
||||
group0LastUpdate = now;
|
||||
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
|
||||
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
|
||||
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
|
||||
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
|
||||
uint8_t diOffset = 3 - offset;
|
||||
uint8_t psOffset = offset * 2;
|
||||
|
||||
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
|
||||
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
|
||||
// Update timeout
|
||||
blockBLastUpdate = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Decoder::decodeGroup0() {
|
||||
// Acquire lock
|
||||
std::lock_guard<std::mutex> lck(group0Mtx);
|
||||
|
||||
// Decode Block B data
|
||||
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
|
||||
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
|
||||
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
|
||||
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
|
||||
uint8_t diOffset = 3 - offset;
|
||||
uint8_t psOffset = offset * 2;
|
||||
|
||||
// Decode Block C data
|
||||
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
|
||||
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
|
||||
}
|
||||
|
||||
// Write DI bit to the decoder identification
|
||||
decoderIdent &= ~(1 << diOffset);
|
||||
decoderIdent |= (diBit << diOffset);
|
||||
|
||||
// Write chars at offset the PSName
|
||||
if (blockAvail[BLOCK_TYPE_D]) {
|
||||
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
}
|
||||
|
||||
// Update timeout
|
||||
group0LastUpdate = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Decoder::decodeGroup2() {
|
||||
// Acquire lock
|
||||
std::lock_guard<std::mutex> lck(group2Mtx);
|
||||
|
||||
// Get char offset and write chars in the Radiotext
|
||||
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
|
||||
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
|
||||
|
||||
// Clear text field if the A/B flag changed
|
||||
if (nAB != rtAB) {
|
||||
radioText = " ";
|
||||
}
|
||||
rtAB = nAB;
|
||||
|
||||
// Write char at offset in Radiotext
|
||||
if (groupVer == GROUP_VER_A) {
|
||||
uint8_t rtOffset = offset * 4;
|
||||
if (blockAvail[BLOCK_TYPE_C]) {
|
||||
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
|
||||
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
|
||||
}
|
||||
|
||||
// Write DI bit to the decoder identification
|
||||
decoderIdent &= ~(1 << diOffset);
|
||||
decoderIdent |= (diBit << diOffset);
|
||||
|
||||
// Write chars at offset the PSName
|
||||
if (blockAvail[BLOCK_TYPE_D]) {
|
||||
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
}
|
||||
}
|
||||
else if (groupType == 2) {
|
||||
group2LastUpdate = now;
|
||||
// Get char offset and write chars in the Radiotext
|
||||
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
|
||||
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
|
||||
|
||||
// Clear text field if the A/B flag changed
|
||||
if (nAB != rtAB) {
|
||||
radioText = " ";
|
||||
else {
|
||||
uint8_t rtOffset = offset * 2;
|
||||
if (blockAvail[BLOCK_TYPE_D]) {
|
||||
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
}
|
||||
rtAB = nAB;
|
||||
}
|
||||
|
||||
// Write char at offset in Radiotext
|
||||
if (groupVer == GROUP_VER_A) {
|
||||
uint8_t rtOffset = offset * 4;
|
||||
if (blockAvail[BLOCK_TYPE_C]) {
|
||||
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
|
||||
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
|
||||
}
|
||||
if (blockAvail[BLOCK_TYPE_D]) {
|
||||
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
}
|
||||
// Update timeout
|
||||
group2LastUpdate = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Decoder::decodeGroup10() {
|
||||
// Acquire lock
|
||||
std::lock_guard<std::mutex> lck(group10Mtx);
|
||||
|
||||
// Check if the text needs to be cleared
|
||||
bool ab = (blocks[BLOCK_TYPE_B] >> 14) & 1;
|
||||
if (ab != ptnAB) {
|
||||
programTypeName = " ";
|
||||
}
|
||||
ptnAB = ab;
|
||||
|
||||
// Decode segment address
|
||||
bool addr = (blocks[BLOCK_TYPE_B] >> 10) & 1;
|
||||
|
||||
// Save text depending on address
|
||||
if (addr) {
|
||||
if (blockAvail[BLOCK_TYPE_C]) {
|
||||
programTypeName[4] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
|
||||
programTypeName[5] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
|
||||
}
|
||||
if (blockAvail[BLOCK_TYPE_D]) {
|
||||
programTypeName[6] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
programTypeName[7] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (blockAvail[BLOCK_TYPE_C]) {
|
||||
programTypeName[0] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
|
||||
programTypeName[1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
|
||||
}
|
||||
if (blockAvail[BLOCK_TYPE_D]) {
|
||||
programTypeName[2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
programTypeName[3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
// Update timeout
|
||||
group10LastUpdate = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Decoder::decodeGroup() {
|
||||
// Make sure blocks B is available
|
||||
if (!blockAvail[BLOCK_TYPE_B]) { return; }
|
||||
|
||||
// Decode block B
|
||||
decodeBlockB();
|
||||
|
||||
// Decode depending on group type
|
||||
switch (groupType) {
|
||||
case 0:
|
||||
decodeGroup0();
|
||||
break;
|
||||
case 2:
|
||||
decodeGroup2();
|
||||
break;
|
||||
case 10:
|
||||
decodeGroup10();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Decoder::base26ToCall(uint16_t pi) {
|
||||
// Determin first better based on offset
|
||||
bool w = (pi >= 21672);
|
||||
std::string callsign(w ? "W" : "K");
|
||||
|
||||
// Base25 decode the rest
|
||||
std::string restStr;
|
||||
int rest = pi - (w ? 21672 : 4096);
|
||||
while (rest) {
|
||||
restStr += 'A' + (rest % 26);
|
||||
rest /= 26;
|
||||
}
|
||||
|
||||
// Pad with As
|
||||
while (restStr.size() < 3) {
|
||||
restStr += 'A';
|
||||
}
|
||||
|
||||
// Reorder chars
|
||||
for (int i = restStr.size() - 1; i >= 0; i--) {
|
||||
callsign += restStr[i];
|
||||
}
|
||||
|
||||
return callsign;
|
||||
}
|
||||
|
||||
std::string Decoder::decodeCallsign(uint16_t pi) {
|
||||
if ((pi >> 8) == 0xAF) {
|
||||
// AFXY -> XY00
|
||||
return base26ToCall((pi & 0xFF) << 8);
|
||||
}
|
||||
else if ((pi >> 12) == 0xA) {
|
||||
// AXYZ -> X0YZ
|
||||
return base26ToCall((((pi >> 8) & 0xF) << 12) | (pi & 0xFF));
|
||||
}
|
||||
else if (pi >= 0x9950 && pi <= 0x9EFF) {
|
||||
// 3 letter callsigns
|
||||
if (THREE_LETTER_CALLS.find(pi) != THREE_LETTER_CALLS.end()) {
|
||||
return THREE_LETTER_CALLS[pi];
|
||||
}
|
||||
else {
|
||||
uint8_t rtOffset = offset * 2;
|
||||
if (blockAvail[BLOCK_TYPE_D]) {
|
||||
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||
}
|
||||
return "Not Assigned";
|
||||
}
|
||||
}
|
||||
else if (pi >= 0x1000 && pi <= 0x994F) {
|
||||
// Normal encoding
|
||||
if ((pi & 0xFF) == 0 || ((pi >> 8) & 0xF) == 0) {
|
||||
return "Not Assigned";
|
||||
}
|
||||
else {
|
||||
return base26ToCall(pi);
|
||||
}
|
||||
}
|
||||
else if (pi >= 0xB000 && pi <= 0xEFFF) {
|
||||
uint16_t _pi = ((pi >> 12) << 8) | (pi & 0xFF);
|
||||
if (NAT_LOC_LINKED_STATIONS.find(_pi) != NAT_LOC_LINKED_STATIONS.end()) {
|
||||
return NAT_LOC_LINKED_STATIONS[_pi];
|
||||
}
|
||||
else {
|
||||
return "Not Assigned";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "Not Assigned";
|
||||
}
|
||||
}
|
||||
|
||||
bool RDSDecoder::anyGroupValid() {
|
||||
bool Decoder::blockAValid() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - anyGroupLastUpdate)).count() < 5000.0;
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockALastUpdate)).count() < RDS_BLOCK_A_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
bool RDSDecoder::group0Valid() {
|
||||
bool Decoder::blockBValid() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < 5000.0;
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockBLastUpdate)).count() < RDS_BLOCK_B_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
bool RDSDecoder::group2Valid() {
|
||||
bool Decoder::group0Valid() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < 5000.0;
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < RDS_GROUP_0_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
bool Decoder::group2Valid() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < RDS_GROUP_2_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
bool Decoder::group10Valid() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group10LastUpdate)).count() < RDS_GROUP_10_TIMEOUT_MS;
|
||||
}
|
||||
}
|
@ -4,6 +4,12 @@
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
#define RDS_BLOCK_A_TIMEOUT_MS 5000.0
|
||||
#define RDS_BLOCK_B_TIMEOUT_MS 5000.0
|
||||
#define RDS_GROUP_0_TIMEOUT_MS 5000.0
|
||||
#define RDS_GROUP_2_TIMEOUT_MS 5000.0
|
||||
#define RDS_GROUP_10_TIMEOUT_MS 5000.0
|
||||
|
||||
namespace rds {
|
||||
enum BlockType {
|
||||
BLOCK_TYPE_A,
|
||||
@ -20,22 +26,42 @@ namespace rds {
|
||||
};
|
||||
|
||||
enum AreaCoverage {
|
||||
AREA_COVERAGE_LOCAL,
|
||||
AREA_COVERAGE_INTERNATIONAL,
|
||||
AREA_COVERAGE_NATIONAL,
|
||||
AREA_COVERAGE_SUPRA_NATIONAL,
|
||||
AREA_COVERAGE_REGIONAL1,
|
||||
AREA_COVERAGE_REGIONAL2,
|
||||
AREA_COVERAGE_REGIONAL3,
|
||||
AREA_COVERAGE_REGIONAL4,
|
||||
AREA_COVERAGE_REGIONAL5,
|
||||
AREA_COVERAGE_REGIONAL6,
|
||||
AREA_COVERAGE_REGIONAL7,
|
||||
AREA_COVERAGE_REGIONAL8,
|
||||
AREA_COVERAGE_REGIONAL9,
|
||||
AREA_COVERAGE_REGIONAL10,
|
||||
AREA_COVERAGE_REGIONAL11,
|
||||
AREA_COVERAGE_REGIONAL12
|
||||
AREA_COVERAGE_INVALID = -1,
|
||||
AREA_COVERAGE_LOCAL = 0,
|
||||
AREA_COVERAGE_INTERNATIONAL = 1,
|
||||
AREA_COVERAGE_NATIONAL = 2,
|
||||
AREA_COVERAGE_SUPRA_NATIONAL = 3,
|
||||
AREA_COVERAGE_REGIONAL1 = 4,
|
||||
AREA_COVERAGE_REGIONAL2 = 5,
|
||||
AREA_COVERAGE_REGIONAL3 = 6,
|
||||
AREA_COVERAGE_REGIONAL4 = 7,
|
||||
AREA_COVERAGE_REGIONAL5 = 8,
|
||||
AREA_COVERAGE_REGIONAL6 = 9,
|
||||
AREA_COVERAGE_REGIONAL7 = 10,
|
||||
AREA_COVERAGE_REGIONAL8 = 11,
|
||||
AREA_COVERAGE_REGIONAL9 = 12,
|
||||
AREA_COVERAGE_REGIONAL10 = 13,
|
||||
AREA_COVERAGE_REGIONAL11 = 14,
|
||||
AREA_COVERAGE_REGIONAL12 = 15
|
||||
};
|
||||
|
||||
inline const char* AREA_COVERAGE_TO_STR[] = {
|
||||
"Local",
|
||||
"International",
|
||||
"National",
|
||||
"Supra-National",
|
||||
"Regional 1",
|
||||
"Regional 2",
|
||||
"Regional 3",
|
||||
"Regional 4",
|
||||
"Regional 5",
|
||||
"Regional 6",
|
||||
"Regional 7",
|
||||
"Regional 8",
|
||||
"Regional 9",
|
||||
"Regional 10",
|
||||
"Regional 11",
|
||||
"Regional 12",
|
||||
};
|
||||
|
||||
enum ProgramType {
|
||||
@ -108,6 +134,76 @@ namespace rds {
|
||||
PROGRAM_TYPE_EU_ALARM = 31
|
||||
};
|
||||
|
||||
inline const char* PROGRAM_TYPE_EU_TO_STR[] = {
|
||||
"None",
|
||||
"News",
|
||||
"Current Affairs",
|
||||
"Information",
|
||||
"Sports",
|
||||
"Education",
|
||||
"Drama",
|
||||
"Culture",
|
||||
"Science",
|
||||
"Varied",
|
||||
"Pop Music",
|
||||
"Rock Music",
|
||||
"Easy Listening Music",
|
||||
"Light Classical",
|
||||
"Serious Classical",
|
||||
"Other Music",
|
||||
"Weather",
|
||||
"Finance",
|
||||
"Children Program",
|
||||
"Social Affairs",
|
||||
"Religion",
|
||||
"Phone-in",
|
||||
"Travel",
|
||||
"Leisure",
|
||||
"Jazz Music",
|
||||
"Country Music",
|
||||
"National Music",
|
||||
"Oldies Music",
|
||||
"Folk Music",
|
||||
"Documentary",
|
||||
"Alarm Test",
|
||||
"Alarm",
|
||||
};
|
||||
|
||||
inline const char* PROGRAM_TYPE_US_TO_STR[] = {
|
||||
"None",
|
||||
"News",
|
||||
"Information",
|
||||
"Sports",
|
||||
"Talk",
|
||||
"Rock",
|
||||
"Classic Rock",
|
||||
"Adult Hits",
|
||||
"Soft Rock",
|
||||
"Top 40",
|
||||
"Country",
|
||||
"Oldies",
|
||||
"Soft",
|
||||
"Nostalgia",
|
||||
"Jazz",
|
||||
"Classical",
|
||||
"Rythm and Blues",
|
||||
"Soft Rythm and Blues",
|
||||
"Foreign Language",
|
||||
"Religious Music",
|
||||
"Religious Talk",
|
||||
"Personality",
|
||||
"Public",
|
||||
"College",
|
||||
"Unassigned",
|
||||
"Unassigned",
|
||||
"Unassigned",
|
||||
"Unassigned",
|
||||
"Unassigned",
|
||||
"Weather",
|
||||
"Emergency Test",
|
||||
"Emergency",
|
||||
};
|
||||
|
||||
enum DecoderIdentification {
|
||||
DECODER_IDENT_STEREO = (1 << 0),
|
||||
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
|
||||
@ -115,35 +211,49 @@ namespace rds {
|
||||
DECODER_IDENT_VARIABLE_PTY = (1 << 0)
|
||||
};
|
||||
|
||||
class RDSDecoder {
|
||||
class Decoder {
|
||||
public:
|
||||
void process(uint8_t* symbols, int count);
|
||||
|
||||
bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
||||
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; }
|
||||
bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
||||
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; }
|
||||
bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
||||
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; }
|
||||
bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
||||
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; }
|
||||
bool piCodeValid() { std::lock_guard<std::mutex> lck(blockAMtx); return blockAValid(); }
|
||||
uint16_t getPICode() { std::lock_guard<std::mutex> lck(blockAMtx); return piCode; }
|
||||
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(blockAMtx); return countryCode; }
|
||||
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(blockAMtx); return programCoverage; }
|
||||
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(blockAMtx); return programRefNumber; }
|
||||
std::string getCallsign() { std::lock_guard<std::mutex> lck(blockAMtx); return callsign; }
|
||||
|
||||
bool programTypeValid() { std::lock_guard<std::mutex> lck(blockBMtx); return blockBValid(); }
|
||||
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(blockBMtx); return programType; }
|
||||
|
||||
bool musicValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
|
||||
bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; }
|
||||
bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
|
||||
std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; }
|
||||
bool musicValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
|
||||
bool getMusic() { std::lock_guard<std::mutex> lck(group0Mtx); return music; }
|
||||
bool PSNameValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
|
||||
std::string getPSName() { std::lock_guard<std::mutex> lck(group0Mtx); return programServiceName; }
|
||||
|
||||
bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); }
|
||||
std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; }
|
||||
bool radioTextValid() { std::lock_guard<std::mutex> lck(group2Mtx); return group2Valid(); }
|
||||
std::string getRadioText() { std::lock_guard<std::mutex> lck(group2Mtx); return radioText; }
|
||||
|
||||
bool programTypeNameValid() { std::lock_guard<std::mutex> lck(group10Mtx); return group10Valid(); }
|
||||
std::string getProgramTypeName() { std::lock_guard<std::mutex> lck(group10Mtx); return programTypeName; }
|
||||
|
||||
private:
|
||||
static uint16_t calcSyndrome(uint32_t block);
|
||||
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
|
||||
void decodeBlockA();
|
||||
void decodeBlockB();
|
||||
void decodeGroup0();
|
||||
void decodeGroup2();
|
||||
void decodeGroup10();
|
||||
void decodeGroup();
|
||||
|
||||
bool anyGroupValid();
|
||||
static std::string base26ToCall(uint16_t pi);
|
||||
static std::string decodeCallsign(uint16_t pi);
|
||||
|
||||
bool blockAValid();
|
||||
bool blockBValid();
|
||||
bool group0Valid();
|
||||
bool group2Valid();
|
||||
bool group10Valid();
|
||||
|
||||
// State machine
|
||||
uint32_t shiftReg = 0;
|
||||
@ -154,17 +264,26 @@ namespace rds {
|
||||
uint32_t blocks[_BLOCK_TYPE_COUNT];
|
||||
bool blockAvail[_BLOCK_TYPE_COUNT];
|
||||
|
||||
// All groups
|
||||
std::mutex groupMtx;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> anyGroupLastUpdate;
|
||||
// Block A (All groups)
|
||||
std::mutex blockAMtx;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> blockALastUpdate{}; // 1970-01-01
|
||||
uint16_t piCode;
|
||||
uint8_t countryCode;
|
||||
AreaCoverage programCoverage;
|
||||
uint8_t programRefNumber;
|
||||
std::string callsign;
|
||||
|
||||
// Block B (All groups)
|
||||
std::mutex blockBMtx;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> blockBLastUpdate{}; // 1970-01-01
|
||||
uint8_t groupType;
|
||||
GroupVersion groupVer;
|
||||
bool trafficProgram;
|
||||
ProgramType programType;
|
||||
|
||||
// Group type 0
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate;
|
||||
std::mutex group0Mtx;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate{}; // 1970-01-01
|
||||
bool trafficAnnouncement;
|
||||
bool music;
|
||||
uint8_t decoderIdent;
|
||||
@ -172,9 +291,16 @@ namespace rds {
|
||||
std::string programServiceName = " ";
|
||||
|
||||
// Group type 2
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate;
|
||||
std::mutex group2Mtx;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate{}; // 1970-01-01
|
||||
bool rtAB = false;
|
||||
std::string radioText = " ";
|
||||
|
||||
// Group type 10
|
||||
std::mutex group10Mtx;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> group10LastUpdate{}; // 1970-01-01
|
||||
bool ptnAB = false;
|
||||
std::string programTypeName = " ";
|
||||
|
||||
};
|
||||
}
|
102
decoder_modules/radio/src/rds_demod.h
Normal file
102
decoder_modules/radio/src/rds_demod.h
Normal file
@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include <dsp/processor.h>
|
||||
#include <dsp/loop/fast_agc.h>
|
||||
#include <dsp/loop/costas.h>
|
||||
#include <dsp/taps/band_pass.h>
|
||||
#include <dsp/filter/fir.h>
|
||||
#include <dsp/convert/complex_to_real.h>
|
||||
#include <dsp/clock_recovery/mm.h>
|
||||
#include <dsp/digital/binary_slicer.h>
|
||||
#include <dsp/digital/differential_decoder.h>
|
||||
|
||||
class RDSDemod : public dsp::Processor<dsp::complex_t, uint8_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
|
||||
public:
|
||||
RDSDemod() {}
|
||||
RDSDemod(dsp::stream<dsp::complex_t>* in, bool enableSoft) { init(in, enableSoft); }
|
||||
~RDSDemod() {}
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, bool enableSoft) {
|
||||
// Save config
|
||||
this->enableSoft = enableSoft;
|
||||
|
||||
// Initialize the DSP
|
||||
agc.init(NULL, 1.0, 1e6, 0.1);
|
||||
costas.init(NULL, 0.005f);
|
||||
taps = dsp::taps::bandPass<dsp::complex_t>(0, 2375, 100, 5000);
|
||||
fir.init(NULL, taps);
|
||||
double baudfreq = dsp::math::hzToRads(2375.0/2.0, 5000);
|
||||
costas2.init(NULL, 0.01, 0.0, baudfreq, baudfreq - (baudfreq*0.1), baudfreq + (baudfreq*0.1));
|
||||
recov.init(NULL, 5000.0 / (2375.0 / 2.0), 1e-6, 0.01, 0.01);
|
||||
diff.init(NULL, 2);
|
||||
|
||||
// Free useless buffers
|
||||
agc.out.free();
|
||||
fir.out.free();
|
||||
costas2.out.free();
|
||||
recov.out.free();
|
||||
|
||||
// Init the rest
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setSoftEnabled(bool enable) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
enableSoft = enable;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
agc.reset();
|
||||
costas.reset();
|
||||
fir.reset();
|
||||
costas2.reset();
|
||||
recov.reset();
|
||||
diff.reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, dsp::complex_t* in, float* softOut, uint8_t* hardOut) {
|
||||
count = agc.process(count, in, costas.out.readBuf);
|
||||
count = costas.process(count, costas.out.readBuf, costas.out.writeBuf);
|
||||
count = fir.process(count, costas.out.writeBuf, costas.out.writeBuf);
|
||||
count = costas2.process(count, costas.out.writeBuf, costas.out.readBuf);
|
||||
count = dsp::convert::ComplexToReal::process(count, costas.out.readBuf, softOut);
|
||||
count = recov.process(count, softOut, softOut);
|
||||
count = dsp::digital::BinarySlicer::process(count, softOut, diff.out.readBuf);
|
||||
count = diff.process(count, diff.out.readBuf, hardOut);
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!base_type::out.swap(count)) { return -1; }
|
||||
if (enableSoft) {
|
||||
if (!soft.swap(count)) { return -1; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
dsp::stream<float> soft;
|
||||
|
||||
private:
|
||||
bool enableSoft = false;
|
||||
|
||||
dsp::loop::FastAGC<dsp::complex_t> agc;
|
||||
dsp::loop::Costas<2> costas;
|
||||
dsp::tap<dsp::complex_t> taps;
|
||||
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
|
||||
dsp::loop::Costas<2> costas2;
|
||||
dsp::clock_recovery::MM<float> recov;
|
||||
dsp::digital::DifferentialDecoder diff;
|
||||
};
|
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;
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user