mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2026-04-20 15:22:42 +00:00
Compare commits
441 Commits
new_source
...
a6df4d58e5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6df4d58e5 | ||
|
|
f817f7365b | ||
|
|
8eebbacb96 | ||
|
|
7be83ffe65 | ||
|
|
6fa945333c | ||
|
|
a3168f0365 | ||
|
|
acb96dd70a | ||
|
|
e8c73f4dab | ||
|
|
8c247c5679 | ||
|
|
ce34af01ee | ||
|
|
1f1315f015 | ||
|
|
11ffdcfd53 | ||
|
|
2330af01e5 | ||
|
|
621c031364 | ||
|
|
2bf3faebae | ||
|
|
65a0e11d3d | ||
|
|
4a48a0c09f | ||
|
|
9c32a68892 | ||
|
|
4658a1ade6 | ||
|
|
b1d94775fe | ||
|
|
2a3fb12f61 | ||
|
|
e82202ea74 | ||
|
|
981f53aa4e | ||
|
|
c216b88366 | ||
|
|
f65e4afb4a | ||
|
|
51ae5628b3 | ||
|
|
9feaf02673 | ||
|
|
5d9f6dc341 | ||
|
|
cf7dd85f92 | ||
|
|
8b8eda301b | ||
|
|
4558e73be5 | ||
|
|
40808c60e4 | ||
|
|
b5d227fecf | ||
|
|
6c00834ddd | ||
|
|
f67fa0c66c | ||
|
|
a94e2d6712 | ||
|
|
981bd1695a | ||
|
|
dd9b8db6c9 | ||
|
|
543c60ccbc | ||
|
|
2dd8c6cea4 | ||
|
|
9457ad9369 | ||
|
|
fccd72b5f8 | ||
|
|
e75cc7be6f | ||
|
|
aa2b4b1c58 | ||
|
|
64315ebc61 | ||
|
|
553204b801 | ||
|
|
2a84ed202c | ||
|
|
f90e2d53a7 | ||
|
|
993bf9128c | ||
|
|
d9ff7eaa12 | ||
|
|
32b289953d | ||
|
|
5178de0849 | ||
|
|
5c355e21f5 | ||
|
|
d020640b7c | ||
|
|
e0f9053229 | ||
|
|
395186ffb0 | ||
|
|
0e77a9f4ab | ||
|
|
4799d0e3a8 | ||
|
|
6c6f4264b2 | ||
|
|
ea3675da47 | ||
|
|
afef9f57ab | ||
|
|
e1de2daca8 | ||
|
|
69bd6b0f3a | ||
|
|
fea3fc2563 | ||
|
|
7bccc67311 | ||
|
|
4310bbb1ea | ||
|
|
d811a839ff | ||
|
|
46bcba7594 | ||
|
|
45e4286f38 | ||
|
|
c266a37a6b | ||
|
|
895199ae94 | ||
|
|
d62426364a | ||
|
|
5ded73ce71 | ||
|
|
cf3e15d285 | ||
|
|
59ca2cf1c0 | ||
|
|
9bb4aeda14 | ||
|
|
304d5c42cc | ||
|
|
b914587228 | ||
|
|
3c1d0c7422 | ||
|
|
11f87e0fe2 | ||
|
|
e192cb963b | ||
|
|
fe407a2f27 | ||
|
|
6891d0bb0f | ||
|
|
b835d07573 | ||
|
|
f205d97b52 | ||
|
|
628dcfcce0 | ||
|
|
d1e7cc56b4 | ||
|
|
334860c963 | ||
|
|
69161253e8 | ||
|
|
5ab3428b90 | ||
|
|
7f002f6276 | ||
|
|
a728403a3f | ||
|
|
0f1d2da3b7 | ||
|
|
6d0b65c27f | ||
|
|
f640cdcb6a | ||
|
|
80a90e13d9 | ||
|
|
3982db73d3 | ||
|
|
bd64f07a20 | ||
|
|
c9950d9331 | ||
|
|
9bc609f4e4 | ||
|
|
bcc8e20e66 | ||
|
|
b07e828fed | ||
|
|
bd24a4a5eb | ||
|
|
fe4a7b32a7 | ||
|
|
0e1ab29b5d | ||
|
|
fbbafddd3d | ||
|
|
1cbc8ec6f5 | ||
|
|
9f65e3ec71 | ||
|
|
08f3a7d201 | ||
|
|
9ce62f8885 | ||
|
|
caeaa2d46c | ||
|
|
7ae030a3a6 | ||
|
|
1b27379a3d | ||
|
|
e52123038e | ||
|
|
ec8c60111d | ||
|
|
f61799cf5f | ||
|
|
17eccf5156 | ||
|
|
e835c8dd9a | ||
|
|
acb1be121c | ||
|
|
0fa89614bb | ||
|
|
256affd918 | ||
|
|
e80cdbf248 | ||
|
|
75e66226c3 | ||
|
|
c2f0e756a5 | ||
|
|
79dd5bdcbb | ||
|
|
6dce28345c | ||
|
|
fe9ac6c9a1 | ||
|
|
bfdfa2b30b | ||
|
|
46e98b9b03 | ||
|
|
e674a73771 | ||
|
|
118e1fbff0 | ||
|
|
bcadb36232 | ||
|
|
554ba2f596 | ||
|
|
949fde022d | ||
|
|
123e34d250 | ||
|
|
f1c7010437 | ||
|
|
33a7795de1 | ||
|
|
fe7299c18a | ||
|
|
8a9e0abcc2 | ||
|
|
13abe4860b | ||
|
|
981592fa19 | ||
|
|
582750f79b | ||
|
|
36f2a083ce | ||
|
|
d753135a61 | ||
|
|
07744e5bae | ||
|
|
9ec78da7ac | ||
|
|
0066994899 | ||
|
|
93ab51bf2f | ||
|
|
f9d7d20073 | ||
|
|
e81db5d85c | ||
|
|
5c3a66642b | ||
|
|
36492e799a | ||
|
|
0110dfbef6 | ||
|
|
0b5a2ff786 | ||
|
|
ce0f1f05ae | ||
|
|
46a5ff8ac5 | ||
|
|
03559b928b | ||
|
|
206ce6e8c3 | ||
|
|
d7a1f46af0 | ||
|
|
89e6e4f7ad | ||
|
|
6ced9b15c3 | ||
|
|
0de189a7b7 | ||
|
|
bb9024fadd | ||
|
|
d1dc20f4e2 | ||
|
|
309717b5f8 | ||
|
|
762444d340 | ||
|
|
18300e8916 | ||
|
|
a93bb9d468 | ||
|
|
ea0362b927 | ||
|
|
ffc642f270 | ||
|
|
1b5975f563 | ||
|
|
733dc55723 | ||
|
|
b841180f84 | ||
|
|
e99e84e809 | ||
|
|
7a4281dd76 | ||
|
|
c89763a989 | ||
|
|
27edc260c9 | ||
|
|
2ea7ac496f | ||
|
|
314d78d9d2 | ||
|
|
4e455e6661 | ||
|
|
58b86fcee5 | ||
|
|
27072e9fe7 | ||
|
|
da1417b5ab | ||
|
|
e60eca5d6d | ||
|
|
ccb10bfb9a | ||
|
|
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 | ||
|
|
a9f882e5b1 | ||
|
|
3420808f3a | ||
|
|
d3d245992d | ||
|
|
9a3414b847 | ||
|
|
90c26f8c1b | ||
|
|
ec4dc6cc9e | ||
|
|
93b28d1495 | ||
|
|
84291deaf6 | ||
|
|
21e0696917 | ||
|
|
19247ef4f2 | ||
|
|
1e5601e773 | ||
|
|
ae1fd87f02 | ||
|
|
ab2aee316c | ||
|
|
109374277e | ||
|
|
692436f6e4 | ||
|
|
eccb715d0c | ||
|
|
f6f074e0c7 | ||
|
|
50a77a7e60 | ||
|
|
a4f3c92a03 | ||
|
|
37920b6476 | ||
|
|
314b8bf72d | ||
|
|
5f0858bab2 | ||
|
|
007761a027 | ||
|
|
801f1be6b2 | ||
|
|
9cc793e328 | ||
|
|
1e01313612 | ||
|
|
4283cacae6 | ||
|
|
6f9dacdd53 | ||
|
|
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.
|
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**
|
**Hardware**
|
||||||
|
|||||||
4
.github/pull_request_template.md
vendored
Normal file
4
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Important
|
||||||
|
|
||||||
|
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.
|
||||||
463
.github/workflows/build_all.yml
vendored
463
.github/workflows/build_all.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
@@ -34,16 +34,23 @@ jobs:
|
|||||||
|
|
||||||
- name: Patch Pothos with earlier libusb version
|
- name: Patch Pothos with earlier libusb version
|
||||||
working-directory: ${{runner.workspace}}
|
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
|
- 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
|
- 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
|
- name: Download codec2
|
||||||
run: git clone https://github.com/AlexandreRouma/codec2
|
run: git clone https://github.com/drowe67/codec2
|
||||||
|
|
||||||
- name: Prepare MinGW
|
- name: Prepare MinGW
|
||||||
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
|
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
|
||||||
@@ -58,14 +65,26 @@ jobs:
|
|||||||
run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2"
|
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
|
- 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
|
- 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 .
|
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: Install libhydrasdr
|
||||||
|
run: git clone https://github.com/hydrasdr/rfone_host; cd rfone_host/ ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:\Program Files\PothosSDR\lib\libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIR=C:\Program Files\PothosSDR\include\libusb-1.0" "-DTHREADS_PTHREADS_WIN32_LIBRARY=C:\Program Files\PothosSDR\lib\pthreadVC2.lib" "-DTHREADS_PTHREADS_INCLUDE_DIR=C:\Program Files\PothosSDR\include" ; cmake --build . --config Release ; cmake --install .
|
||||||
|
|
||||||
- name: Prepare CMake
|
- name: Prepare CMake
|
||||||
working-directory: ${{runner.workspace}}/build
|
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 -DOPT_BUILD_HYDRASDR_SOURCE=ON
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
@@ -76,47 +95,59 @@ jobs:
|
|||||||
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
|
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
|
||||||
|
|
||||||
- name: Save Archive
|
- name: Save Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_windows_x64
|
name: sdrpp_windows_x64
|
||||||
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
||||||
|
|
||||||
build_macos:
|
build_macos_intel:
|
||||||
runs-on: macos-11
|
runs-on: macos-15-intel
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
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
|
- 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 --break-system-packages
|
||||||
|
|
||||||
- name: Install volk
|
- 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
|
- 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
|
- 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
|
- 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 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. && make -j3 && sudo make install && cd ../../
|
||||||
|
|
||||||
- name: Install LimeSuite
|
- 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: Install libhydrasdr
|
||||||
|
run: git clone https://github.com/hydrasdr/rfone_host && cd rfone_host && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||||
|
|
||||||
|
- name: Install libdlcr
|
||||||
|
run: wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip && mkdir dlcr_host && cd dlcr_host && 7z x ../dlcr_host_v0.3.0.zip && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 .. -DCMAKE_BUILD_TYPE=Release && make -j2 && sudo make install && cd ../../
|
||||||
|
|
||||||
- name: Prepare CMake
|
- name: Prepare CMake
|
||||||
working-directory: ${{runner.workspace}}/build
|
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_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
@@ -127,38 +158,79 @@ 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
|
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
|
- name: Save Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_macos_intel
|
name: sdrpp_macos_intel
|
||||||
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
|
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
|
||||||
|
|
||||||
build_debian_buster:
|
build_macos_arm:
|
||||||
runs-on: ubuntu-latest
|
runs-on: macos-15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Build Environment
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
- name: Run Container
|
- name: Install dependencies
|
||||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
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: Recover Deb Archive
|
- 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 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. && make -j3 && sudo make install && cd ../../
|
||||||
|
|
||||||
|
- name: Install LimeSuite
|
||||||
|
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||||
|
|
||||||
|
# - name: Install libperseus
|
||||||
|
# run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
|
||||||
|
|
||||||
|
- name: Install librfnm
|
||||||
|
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||||
|
|
||||||
|
- name: Install libfobos
|
||||||
|
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||||
|
|
||||||
|
- name: Install more recent librtlsdr
|
||||||
|
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
|
||||||
|
|
||||||
|
- name: Install libhydrasdr
|
||||||
|
run: git clone https://github.com/hydrasdr/rfone_host && cd rfone_host && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||||
|
|
||||||
|
- name: Install libdlcr
|
||||||
|
run: wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip && mkdir dlcr_host && cd dlcr_host && 7z x ../dlcr_host_v0.3.0.zip && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 .. -DCMAKE_BUILD_TYPE=Release && make -j2 && sudo make install && cd ../../
|
||||||
|
|
||||||
|
- name: Prepare CMake
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: make VERBOSE=1 -j3
|
||||||
|
|
||||||
|
- name: Create Archive
|
||||||
working-directory: ${{runner.workspace}}
|
working-directory: ${{runner.workspace}}
|
||||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
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 Deb Archive
|
- name: Save Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_debian_buster_amd64
|
name: sdrpp_macos_arm
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
|
||||||
|
|
||||||
build_debian_bullseye:
|
build_debian_bullseye_amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
|
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
|
||||||
@@ -171,16 +243,126 @@ jobs:
|
|||||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_debian_bullseye_amd64
|
name: sdrpp_debian_bullseye_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
build_debian_sid:
|
build_debian_bullseye_aarch64:
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create Docker Image
|
||||||
|
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
|
||||||
|
|
||||||
|
- name: Run Container
|
||||||
|
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||||
|
|
||||||
|
- name: Recover Deb Archive
|
||||||
|
working-directory: ${{runner.workspace}}
|
||||||
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
|
- name: Save Deb Archive
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdrpp_debian_bullseye_aarch64
|
||||||
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
|
build_debian_bookworm_amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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_bookworm_aarch64:
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create Docker Image
|
||||||
|
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
|
||||||
|
|
||||||
|
- name: Run Container
|
||||||
|
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||||
|
|
||||||
|
- name: Recover Deb Archive
|
||||||
|
working-directory: ${{runner.workspace}}
|
||||||
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
|
- name: Save Deb Archive
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdrpp_debian_bookworm_aarch64
|
||||||
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
|
build_debian_trixie_amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create Docker Image
|
||||||
|
run: cd $GITHUB_WORKSPACE/docker_builds/debian_trixie && docker build . --tag sdrpp_build
|
||||||
|
|
||||||
|
- name: Run Container
|
||||||
|
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||||
|
|
||||||
|
- name: Recover Deb Archive
|
||||||
|
working-directory: ${{runner.workspace}}
|
||||||
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
|
- name: Save Deb Archive
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdrpp_debian_trixie_amd64
|
||||||
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
|
build_debian_trixie_aarch64:
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create Docker Image
|
||||||
|
run: cd $GITHUB_WORKSPACE/docker_builds/debian_trixie && docker build . --tag sdrpp_build
|
||||||
|
|
||||||
|
- name: Run Container
|
||||||
|
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||||
|
|
||||||
|
- name: Recover Deb Archive
|
||||||
|
working-directory: ${{runner.workspace}}
|
||||||
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
|
- name: Save Deb Archive
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdrpp_debian_trixie_aarch64
|
||||||
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
|
build_debian_sid_amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
|
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
|
||||||
@@ -193,38 +375,38 @@ jobs:
|
|||||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_debian_sid_amd64
|
name: sdrpp_debian_sid_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
# build_ubuntu_bionic:
|
build_debian_sid_aarch64:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
# steps:
|
steps:
|
||||||
# - uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
# - name: Create Docker Image
|
- name: Create Docker Image
|
||||||
# run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && docker build . --tag sdrpp_build
|
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
|
||||||
|
|
||||||
# - name: Run Container
|
- 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
|
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
|
- name: Recover Deb Archive
|
||||||
# working-directory: ${{runner.workspace}}
|
working-directory: ${{runner.workspace}}
|
||||||
# run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
# - name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
# uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
# with:
|
with:
|
||||||
# name: sdrpp_ubuntu_bionic_amd64
|
name: sdrpp_debian_sid_aarch64
|
||||||
# path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
build_ubuntu_focal:
|
build_ubuntu_focal_amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
|
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
|
||||||
@@ -237,16 +419,38 @@ jobs:
|
|||||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_ubuntu_focal_amd64
|
name: sdrpp_ubuntu_focal_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
build_ubuntu_jammy:
|
build_ubuntu_focal_aarch64:
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create Docker Image
|
||||||
|
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
|
||||||
|
|
||||||
|
- name: Run Container
|
||||||
|
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||||
|
|
||||||
|
- name: Recover Deb Archive
|
||||||
|
working-directory: ${{runner.workspace}}
|
||||||
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
|
- name: Save Deb Archive
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdrpp_ubuntu_focal_aarch64
|
||||||
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
|
build_ubuntu_jammy_amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
|
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
|
||||||
@@ -259,43 +463,82 @@ jobs:
|
|||||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_ubuntu_jammy_amd64
|
name: sdrpp_ubuntu_jammy_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
build_raspios_bullseye_armhf:
|
build_ubuntu_jammy_aarch64:
|
||||||
runs-on: ARM
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
steps:
|
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
|
- name: Create Docker Image
|
||||||
working-directory: ${{runner.workspace}}/build
|
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_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
|
|
||||||
|
|
||||||
- name: Build
|
- name: Run Container
|
||||||
working-directory: ${{runner.workspace}}/build
|
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||||
run: make VERBOSE=1 -j3
|
|
||||||
|
|
||||||
- name: Create Dev Archive
|
- name: Recover Deb Archive
|
||||||
working-directory: ${{runner.workspace}}
|
working-directory: ${{runner.workspace}}
|
||||||
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_raspios_bullseye_armhf
|
name: sdrpp_ubuntu_jammy_aarch64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
|
build_ubuntu_noble_amd64:
|
||||||
|
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_ubuntu_noble_aarch64:
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create Docker Image
|
||||||
|
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
|
||||||
|
|
||||||
|
- name: Run Container
|
||||||
|
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||||
|
|
||||||
|
- name: Recover Deb Archive
|
||||||
|
working-directory: ${{runner.workspace}}
|
||||||
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
|
- name: Save Deb Archive
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdrpp_ubuntu_noble_aarch64
|
||||||
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch container
|
- name: Fetch container
|
||||||
working-directory: ${{runner.workspace}}
|
working-directory: ${{runner.workspace}}
|
||||||
@@ -313,33 +556,61 @@ jobs:
|
|||||||
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
|
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
|
||||||
|
|
||||||
- name: Save APK
|
- name: Save APK
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_android
|
name: sdrpp_android
|
||||||
path: ${{runner.workspace}}/sdrpp.apk
|
path: ${{runner.workspace}}/sdrpp.apk
|
||||||
|
|
||||||
create_full_archive:
|
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_bullseye_amd64',
|
||||||
|
'build_debian_bullseye_aarch64',
|
||||||
|
'build_debian_bookworm_amd64',
|
||||||
|
'build_debian_bookworm_aarch64',
|
||||||
|
'build_debian_trixie_amd64',
|
||||||
|
'build_debian_trixie_aarch64',
|
||||||
|
'build_debian_sid_amd64',
|
||||||
|
'build_debian_sid_aarch64',
|
||||||
|
'build_ubuntu_focal_amd64',
|
||||||
|
'build_ubuntu_focal_aarch64',
|
||||||
|
'build_ubuntu_jammy_amd64',
|
||||||
|
'build_ubuntu_jammy_aarch64',
|
||||||
|
'build_ubuntu_noble_amd64',
|
||||||
|
'build_ubuntu_noble_aarch64',
|
||||||
|
'build_android'
|
||||||
|
]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download All Builds
|
- name: Download All Builds
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Create Archive
|
- name: Create Archive
|
||||||
run: >
|
run: >
|
||||||
mkdir sdrpp_all &&
|
mkdir sdrpp_all &&
|
||||||
mv sdrpp_windows_x64/sdrpp_windows_x64.zip 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_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
|
||||||
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
|
mv sdrpp_macos_arm/sdrpp_macos_arm.zip sdrpp_all/ &&
|
||||||
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
|
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
|
||||||
|
mv sdrpp_debian_bullseye_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_aarch64.deb &&
|
||||||
|
mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
|
||||||
|
mv sdrpp_debian_bookworm_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_aarch64.deb &&
|
||||||
|
mv sdrpp_debian_trixie_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_trixie_amd64.deb &&
|
||||||
|
mv sdrpp_debian_trixie_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_trixie_aarch64.deb &&
|
||||||
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
|
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
|
||||||
|
mv sdrpp_debian_sid_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_aarch64.deb &&
|
||||||
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
||||||
|
mv sdrpp_ubuntu_focal_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_aarch64.deb &&
|
||||||
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
|
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
|
||||||
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
|
mv sdrpp_ubuntu_jammy_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_aarch64.deb &&
|
||||||
|
mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb &&
|
||||||
|
mv sdrpp_ubuntu_noble_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_aarch64.deb &&
|
||||||
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_all
|
name: sdrpp_all
|
||||||
path: sdrpp_all/
|
path: sdrpp_all/
|
||||||
@@ -351,7 +622,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download All Builds
|
- name: Download All Builds
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Update Nightly
|
- name: Update Nightly
|
||||||
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
|
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
|
||||||
@@ -360,7 +631,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install codespell
|
- name: Install codespell
|
||||||
run: sudo apt update -y && sudo apt install -y codespell
|
run: sudo apt update -y && sudo apt install -y codespell
|
||||||
@@ -372,7 +643,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run check_clang_format
|
- name: Run check_clang_format
|
||||||
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true
|
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,4 +16,8 @@ poggers_decoder
|
|||||||
m17_decoder/libcorrect
|
m17_decoder/libcorrect
|
||||||
SDR++.app
|
SDR++.app
|
||||||
android/deps
|
android/deps
|
||||||
android/app/assets
|
android/app/assets
|
||||||
|
source_modules/badgesdr_source
|
||||||
|
decoder_modules/mystery_decoder
|
||||||
|
decoder_modules/tetra_decoder
|
||||||
|
misc_modules/modulation_monitor
|
||||||
127
CMakeLists.txt
127
CMakeLists.txt
@@ -12,42 +12,56 @@ 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_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_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_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_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
|
||||||
|
option(OPT_BUILD_DRAGONLABS_SOURCE "Build Dragon Labs Source Module (Dependencies: libdlcr)" OFF)
|
||||||
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
option(OPT_BUILD_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_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_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
|
||||||
|
option(OPT_BUILD_HYDRASDR_SOURCE "Build HydraSDR Source Module (Dependencies: libhydrasdr)" OFF)
|
||||||
|
option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF)
|
||||||
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
|
option(OPT_BUILD_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_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_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_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_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_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_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_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)
|
option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF)
|
||||||
|
|
||||||
# Sinks
|
# Sinks
|
||||||
option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF)
|
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_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_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_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
|
# Decoders
|
||||||
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
|
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" ON)
|
||||||
|
option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF)
|
||||||
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
|
option(OPT_BUILD_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_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_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_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_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
|
||||||
|
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
|
||||||
|
option(OPT_BUILD_VOR_RECEIVER "VOR beacon receiver" OFF)
|
||||||
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
|
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_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_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_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)
|
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
|
||||||
@@ -57,6 +71,7 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
|
|||||||
# Other options
|
# Other options
|
||||||
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON)
|
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(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
|
# Module cmake path
|
||||||
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
|
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
|
||||||
@@ -121,29 +136,65 @@ if (OPT_BUILD_AUDIO_SOURCE)
|
|||||||
add_subdirectory("source_modules/audio_source")
|
add_subdirectory("source_modules/audio_source")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_BLADERF_SOURCE)
|
||||||
add_subdirectory("source_modules/bladerf_source")
|
add_subdirectory("source_modules/bladerf_source")
|
||||||
endif (OPT_BUILD_BLADERF_SOURCE)
|
endif (OPT_BUILD_BLADERF_SOURCE)
|
||||||
|
|
||||||
|
if (OPT_BUILD_DRAGONLABS_SOURCE)
|
||||||
|
add_subdirectory("source_modules/dragonlabs_source")
|
||||||
|
endif (OPT_BUILD_DRAGONLABS_SOURCE)
|
||||||
|
|
||||||
if (OPT_BUILD_FILE_SOURCE)
|
if (OPT_BUILD_FILE_SOURCE)
|
||||||
add_subdirectory("source_modules/file_source")
|
add_subdirectory("source_modules/file_source")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_HACKRF_SOURCE)
|
||||||
add_subdirectory("source_modules/hackrf_source")
|
add_subdirectory("source_modules/hackrf_source")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_HERMES_SOURCE)
|
||||||
add_subdirectory("source_modules/hermes_source")
|
add_subdirectory("source_modules/hermes_source")
|
||||||
endif (OPT_BUILD_HERMES_SOURCE)
|
endif (OPT_BUILD_HERMES_SOURCE)
|
||||||
|
|
||||||
|
if (OPT_BUILD_HYDRASDR_SOURCE)
|
||||||
|
add_subdirectory("source_modules/hydrasdr_source")
|
||||||
|
endif (OPT_BUILD_HYDRASDR_SOURCE)
|
||||||
|
|
||||||
|
if (OPT_BUILD_KCSDR_SOURCE)
|
||||||
|
add_subdirectory("source_modules/kcsdr_source")
|
||||||
|
endif (OPT_BUILD_KCSDR_SOURCE)
|
||||||
|
|
||||||
if (OPT_BUILD_LIMESDR_SOURCE)
|
if (OPT_BUILD_LIMESDR_SOURCE)
|
||||||
add_subdirectory("source_modules/limesdr_source")
|
add_subdirectory("source_modules/limesdr_source")
|
||||||
endif (OPT_BUILD_LIMESDR_SOURCE)
|
endif (OPT_BUILD_LIMESDR_SOURCE)
|
||||||
|
|
||||||
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
if (OPT_BUILD_NETWORK_SOURCE)
|
||||||
add_subdirectory("source_modules/sdrpp_server_source")
|
add_subdirectory("source_modules/network_source")
|
||||||
endif (OPT_BUILD_SDRPP_SERVER_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)
|
if (OPT_BUILD_RFSPACE_SOURCE)
|
||||||
add_subdirectory("source_modules/rfspace_source")
|
add_subdirectory("source_modules/rfspace_source")
|
||||||
@@ -157,6 +208,14 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
|
|||||||
add_subdirectory("source_modules/rtl_tcp_source")
|
add_subdirectory("source_modules/rtl_tcp_source")
|
||||||
endif (OPT_BUILD_RTL_TCP_SOURCE)
|
endif (OPT_BUILD_RTL_TCP_SOURCE)
|
||||||
|
|
||||||
|
if (OPT_BUILD_SDDC_SOURCE)
|
||||||
|
add_subdirectory("source_modules/sddc_source")
|
||||||
|
endif (OPT_BUILD_SDDC_SOURCE)
|
||||||
|
|
||||||
|
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||||
|
add_subdirectory("source_modules/sdrpp_server_source")
|
||||||
|
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||||
|
|
||||||
if (OPT_BUILD_SDRPLAY_SOURCE)
|
if (OPT_BUILD_SDRPLAY_SOURCE)
|
||||||
add_subdirectory("source_modules/sdrplay_source")
|
add_subdirectory("source_modules/sdrplay_source")
|
||||||
endif (OPT_BUILD_SDRPLAY_SOURCE)
|
endif (OPT_BUILD_SDRPLAY_SOURCE)
|
||||||
@@ -177,10 +236,6 @@ if (OPT_BUILD_SPYSERVER_SOURCE)
|
|||||||
add_subdirectory("source_modules/spyserver_source")
|
add_subdirectory("source_modules/spyserver_source")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_USRP_SOURCE)
|
||||||
add_subdirectory("source_modules/usrp_source")
|
add_subdirectory("source_modules/usrp_source")
|
||||||
endif (OPT_BUILD_USRP_SOURCE)
|
endif (OPT_BUILD_USRP_SOURCE)
|
||||||
@@ -213,6 +268,10 @@ if (OPT_BUILD_ATV_DECODER)
|
|||||||
add_subdirectory("decoder_modules/atv_decoder")
|
add_subdirectory("decoder_modules/atv_decoder")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_FALCON9_DECODER)
|
||||||
add_subdirectory("decoder_modules/falcon9_decoder")
|
add_subdirectory("decoder_modules/falcon9_decoder")
|
||||||
endif (OPT_BUILD_FALCON9_DECODER)
|
endif (OPT_BUILD_FALCON9_DECODER)
|
||||||
@@ -229,10 +288,22 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
|
|||||||
add_subdirectory("decoder_modules/meteor_demodulator")
|
add_subdirectory("decoder_modules/meteor_demodulator")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_RADIO)
|
||||||
add_subdirectory("decoder_modules/radio")
|
add_subdirectory("decoder_modules/radio")
|
||||||
endif (OPT_BUILD_RADIO)
|
endif (OPT_BUILD_RADIO)
|
||||||
|
|
||||||
|
if (OPT_BUILD_RYFI_DECODER)
|
||||||
|
add_subdirectory("decoder_modules/ryfi_decoder")
|
||||||
|
endif (OPT_BUILD_RYFI_DECODER)
|
||||||
|
|
||||||
|
if (OPT_BUILD_VOR_RECEIVER)
|
||||||
|
add_subdirectory("decoder_modules/vor_receiver")
|
||||||
|
endif (OPT_BUILD_VOR_RECEIVER)
|
||||||
|
|
||||||
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||||
@@ -247,6 +318,10 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
|
|||||||
add_subdirectory("misc_modules/frequency_manager")
|
add_subdirectory("misc_modules/frequency_manager")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_RECORDER)
|
||||||
add_subdirectory("misc_modules/recorder")
|
add_subdirectory("misc_modules/recorder")
|
||||||
endif (OPT_BUILD_RECORDER)
|
endif (OPT_BUILD_RECORDER)
|
||||||
@@ -267,7 +342,12 @@ if (OPT_BUILD_SCHEDULER)
|
|||||||
add_subdirectory("misc_modules/scheduler")
|
add_subdirectory("misc_modules/scheduler")
|
||||||
endif (OPT_BUILD_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)
|
target_link_libraries(sdrpp PRIVATE sdrpp_core)
|
||||||
|
|
||||||
# Compiler arguments
|
# Compiler arguments
|
||||||
@@ -277,6 +357,21 @@ target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
|
|||||||
if (MSVC)
|
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 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)
|
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 ()
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
@@ -297,7 +392,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>\")
|
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||||
endif ()
|
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
|
# Create module cmake file
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
|
||||||
@@ -317,4 +412,6 @@ endif ()
|
|||||||
|
|
||||||
# Create uninstall target
|
# Create uninstall target
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
|
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,11 +10,11 @@ android {
|
|||||||
minSdkVersion 28
|
minSdkVersion 28
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.1.0"
|
versionName "1.2.1"
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF"
|
arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF", "-DOPT_BUILD_HYDRASDR_SOURCE=ON"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,12 @@
|
|||||||
|
# AI USE IS FORBIDDEN
|
||||||
|
|
||||||
|
Use of AI for the creation of code, pull requests or issues is forbidden.
|
||||||
|
|
||||||
|
Submitting code, pull requests or issues generated by AI will result in a permanent ban from all of my (Alexandre Rouma) repositories.
|
||||||
|
|
||||||
# Pull Requests
|
# Pull Requests
|
||||||
|
|
||||||
TODO
|
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
|
||||||
|
|
||||||
# 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**
|
|
||||||
|
|
||||||
## Band Frequency Allocation
|
## Band Frequency Allocation
|
||||||
|
|
||||||
@@ -118,8 +66,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.
|
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.
|
||||||
* Use VSCode for development, VS seems to cause issues.
|
|
||||||
* DO NOT use libboost for any code meant for this repository
|
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ endif (USE_BUNDLE_DEFAULTS)
|
|||||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||||
|
|
||||||
add_definitions(-DSDRPP_IS_CORE)
|
add_definitions(-DSDRPP_IS_CORE)
|
||||||
|
add_definitions(-DFLOG_ANDROID_TAG="SDR++")
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
endif ()
|
endif ()
|
||||||
@@ -107,7 +108,6 @@ elseif (ANDROID)
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(sdrpp_core PUBLIC
|
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/libvolk.so
|
||||||
/sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so
|
/sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so
|
||||||
/sdr-kit/${ANDROID_ABI}/lib/libzstd.so
|
/sdr-kit/${ANDROID_ABI}/lib/libzstd.so
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace backend {
|
|||||||
extern const std::vector<DevVIDPID> AIRSPY_VIDPIDS;
|
extern const std::vector<DevVIDPID> AIRSPY_VIDPIDS;
|
||||||
extern const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS;
|
extern const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS;
|
||||||
extern const std::vector<DevVIDPID> HACKRF_VIDPIDS;
|
extern const std::vector<DevVIDPID> HACKRF_VIDPIDS;
|
||||||
|
extern const std::vector<DevVIDPID> HYDRASDR_VIDPIDS;
|
||||||
extern const std::vector<DevVIDPID> RTL_SDR_VIDPIDS;
|
extern const std::vector<DevVIDPID> RTL_SDR_VIDPIDS;
|
||||||
|
|
||||||
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids);
|
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids);
|
||||||
|
|||||||
@@ -408,6 +408,11 @@ namespace backend {
|
|||||||
{ 0x1d50, 0xcc15 }
|
{ 0x1d50, 0xcc15 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const std::vector<DevVIDPID> HYDRASDR_VIDPIDS = {
|
||||||
|
{ 0x1d50, 0x60a1 },
|
||||||
|
{ 0x38af, 0x0001 }
|
||||||
|
};
|
||||||
|
|
||||||
const std::vector<DevVIDPID> RTL_SDR_VIDPIDS = {
|
const std::vector<DevVIDPID> RTL_SDR_VIDPIDS = {
|
||||||
{ 0x0bda, 0x2832 },
|
{ 0x0bda, 0x2832 },
|
||||||
{ 0x0bda, 0x2838 },
|
{ 0x0bda, 0x2838 },
|
||||||
|
|||||||
@@ -99,7 +99,10 @@ namespace backend {
|
|||||||
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
|
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
|
||||||
|
#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4)
|
||||||
|
glfwWindowHintString(GLFW_WAYLAND_APP_ID, "sdrpp");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Create window with graphics context
|
// Create window with graphics context
|
||||||
monitor = glfwGetPrimaryMonitor();
|
monitor = glfwGetPrimaryMonitor();
|
||||||
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 2.8)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
project(Correct C)
|
project(Correct C)
|
||||||
include(CheckLibraryExists)
|
include(CheckLibraryExists)
|
||||||
include(CheckIncludeFiles)
|
include(CheckIncludeFiles)
|
||||||
|
|||||||
@@ -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,
|
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
|
||||||
unsigned int search_every) {
|
unsigned int search_every) {
|
||||||
shift_register_t bestpath;
|
shift_register_t bestpath = 0;
|
||||||
distance_t leasterror = USHRT_MAX;
|
distance_t leasterror = USHRT_MAX;
|
||||||
// search for a state with the least error
|
// search for a state with the least error
|
||||||
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {
|
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 {
|
try {
|
||||||
carg.ival = std::stoi(arg);
|
carg.ival = std::stoi(arg);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
printf("Invalid argument, failed to parse integer\n");
|
printf("Invalid argument, failed to parse integer\n");
|
||||||
showHelp();
|
showHelp();
|
||||||
return -1;
|
return -1;
|
||||||
@@ -98,7 +98,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
|
|||||||
try {
|
try {
|
||||||
carg.fval = std::stod(arg);
|
carg.fval = std::stod(arg);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
printf("Invalid argument, failed to parse float\n");
|
printf("Invalid argument, failed to parse float\n");
|
||||||
showHelp();
|
showHelp();
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ void ConfigManager::load(json def, bool lock) {
|
|||||||
file >> conf;
|
file >> conf;
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Config file '{0}' is corrupted, resetting it", path);
|
flog::error("Config file '{}' is corrupted, resetting it: {}", path, e.what());
|
||||||
conf = def;
|
conf = def;
|
||||||
save(false);
|
save(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
defConfig["colorMap"] = "Classic";
|
defConfig["colorMap"] = "Classic";
|
||||||
defConfig["fftHold"] = false;
|
defConfig["fftHold"] = false;
|
||||||
defConfig["fftHoldSpeed"] = 60;
|
defConfig["fftHoldSpeed"] = 60;
|
||||||
|
defConfig["fftSmoothing"] = false;
|
||||||
|
defConfig["fftSmoothingSpeed"] = 100;
|
||||||
|
defConfig["snrSmoothing"] = false;
|
||||||
|
defConfig["snrSmoothingSpeed"] = 20;
|
||||||
defConfig["fastFFT"] = false;
|
defConfig["fastFFT"] = false;
|
||||||
defConfig["fftHeight"] = 300;
|
defConfig["fftHeight"] = 300;
|
||||||
defConfig["fftRate"] = 20;
|
defConfig["fftRate"] = 20;
|
||||||
@@ -143,12 +147,12 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
defConfig["menuElements"][3]["name"] = "Sinks";
|
defConfig["menuElements"][3]["name"] = "Sinks";
|
||||||
defConfig["menuElements"][3]["open"] = true;
|
defConfig["menuElements"][3]["open"] = true;
|
||||||
|
|
||||||
defConfig["menuElements"][3]["name"] = "Frequency Manager";
|
defConfig["menuElements"][4]["name"] = "Frequency Manager";
|
||||||
defConfig["menuElements"][3]["open"] = true;
|
|
||||||
|
|
||||||
defConfig["menuElements"][4]["name"] = "VFO Color";
|
|
||||||
defConfig["menuElements"][4]["open"] = true;
|
defConfig["menuElements"][4]["open"] = true;
|
||||||
|
|
||||||
|
defConfig["menuElements"][5]["name"] = "VFO Color";
|
||||||
|
defConfig["menuElements"][5]["open"] = true;
|
||||||
|
|
||||||
defConfig["menuElements"][6]["name"] = "Band Plan";
|
defConfig["menuElements"][6]["name"] = "Band Plan";
|
||||||
defConfig["menuElements"][6]["open"] = true;
|
defConfig["menuElements"][6]["open"] = true;
|
||||||
|
|
||||||
@@ -167,16 +171,30 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
defConfig["moduleInstances"]["Audio Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["Audio Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source";
|
defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source";
|
||||||
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
|
||||||
|
defConfig["moduleInstances"]["Dragon Labs Source"]["module"] = "dragonlabs_source";
|
||||||
|
defConfig["moduleInstances"]["Dragon Labs Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
|
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
|
||||||
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
|
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"]["module"] = "hackrf_source";
|
||||||
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
|
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"]["module"] = "hermes_source";
|
||||||
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
|
||||||
|
defConfig["moduleInstances"]["HydraSDR Source"]["module"] = "hydrasdr_source";
|
||||||
|
defConfig["moduleInstances"]["HydraSDR Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
|
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
|
||||||
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
|
||||||
|
defConfig["moduleInstances"]["Network Source"]["module"] = "network_source";
|
||||||
|
defConfig["moduleInstances"]["Network Source"]["enabled"] = true;
|
||||||
|
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
|
||||||
|
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
||||||
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
|
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"]["module"] = "rfspace_source";
|
||||||
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
|
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
|
||||||
@@ -187,10 +205,12 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
|
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
|
||||||
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source";
|
defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source";
|
||||||
defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true;
|
defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true;
|
||||||
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
|
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
|
||||||
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;
|
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"]["Audio Sink"] = "audio_sink";
|
||||||
defConfig["moduleInstances"]["Network Sink"] = "network_sink";
|
defConfig["moduleInstances"]["Network Sink"] = "network_sink";
|
||||||
@@ -216,12 +236,19 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
defConfig["modules"] = json::array();
|
defConfig["modules"] = json::array();
|
||||||
|
|
||||||
defConfig["offsetMode"] = (int)0; // Off
|
defConfig["offsets"]["SpyVerter"] = 120000000.0;
|
||||||
defConfig["offset"] = 0.0;
|
defConfig["offsets"]["Ham-It-Up"] = 125000000.0;
|
||||||
|
defConfig["offsets"]["MMDS S-band (1998MHz)"] = -1998000000.0;
|
||||||
|
defConfig["offsets"]["DK5AV X-Band"] = -6800000000.0;
|
||||||
|
defConfig["offsets"]["Ku LNB (9750MHz)"] = -9750000000.0;
|
||||||
|
defConfig["offsets"]["Ku LNB (10700MHz)"] = -10700000000.0;
|
||||||
|
|
||||||
|
defConfig["selectedOffset"] = "None";
|
||||||
|
defConfig["manualOffset"] = 0.0;
|
||||||
defConfig["showMenu"] = true;
|
defConfig["showMenu"] = true;
|
||||||
defConfig["showWaterfall"] = true;
|
defConfig["showWaterfall"] = true;
|
||||||
defConfig["source"] = "";
|
defConfig["source"] = "";
|
||||||
defConfig["decimationPower"] = 0;
|
defConfig["decimation"] = 1;
|
||||||
defConfig["iqCorrection"] = false;
|
defConfig["iqCorrection"] = false;
|
||||||
defConfig["invertIQ"] = false;
|
defConfig["invertIQ"] = false;
|
||||||
|
|
||||||
@@ -272,6 +299,7 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
core::configManager.conf["modules"][modCount++] = "airspyhf_source.so";
|
core::configManager.conf["modules"][modCount++] = "airspyhf_source.so";
|
||||||
core::configManager.conf["modules"][modCount++] = "hackrf_source.so";
|
core::configManager.conf["modules"][modCount++] = "hackrf_source.so";
|
||||||
core::configManager.conf["modules"][modCount++] = "hermes_source.so";
|
core::configManager.conf["modules"][modCount++] = "hermes_source.so";
|
||||||
|
core::configManager.conf["modules"][modCount++] = "hydrasdr_source.so";
|
||||||
core::configManager.conf["modules"][modCount++] = "plutosdr_source.so";
|
core::configManager.conf["modules"][modCount++] = "plutosdr_source.so";
|
||||||
core::configManager.conf["modules"][modCount++] = "rfspace_source.so";
|
core::configManager.conf["modules"][modCount++] = "rfspace_source.so";
|
||||||
core::configManager.conf["modules"][modCount++] = "rtl_sdr_source.so";
|
core::configManager.conf["modules"][modCount++] = "rtl_sdr_source.so";
|
||||||
@@ -302,12 +330,18 @@ int sdrpp_main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
// Remove unused elements
|
// Remove unused elements
|
||||||
auto items = core::configManager.conf.items();
|
auto items = core::configManager.conf.items();
|
||||||
|
auto newConf = core::configManager.conf;
|
||||||
|
bool configCorrected = false;
|
||||||
for (auto const& item : items) {
|
for (auto const& item : items) {
|
||||||
if (!defConfig.contains(item.key())) {
|
if (!defConfig.contains(item.key())) {
|
||||||
flog::info("Unused key in config {0}, repairing", item.key());
|
flog::info("Unused key in config {0}, repairing", item.key());
|
||||||
core::configManager.conf.erase(item.key());
|
newConf.erase(item.key());
|
||||||
|
configCorrected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (configCorrected) {
|
||||||
|
core::configManager.conf = newConf;
|
||||||
|
}
|
||||||
|
|
||||||
// Update to new module representation in config if needed
|
// Update to new module representation in config if needed
|
||||||
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {
|
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace sdrpp_credits {
|
|||||||
"Howard0su",
|
"Howard0su",
|
||||||
"John Donkersley",
|
"John Donkersley",
|
||||||
"Joshua Kimsey",
|
"Joshua Kimsey",
|
||||||
|
"Manawyrm",
|
||||||
"Martin Hauke",
|
"Martin Hauke",
|
||||||
"Marvin Sinister",
|
"Marvin Sinister",
|
||||||
"Maxime Biette",
|
"Maxime Biette",
|
||||||
@@ -21,7 +22,6 @@ namespace sdrpp_credits {
|
|||||||
"Shuyuan Liu",
|
"Shuyuan Liu",
|
||||||
"Syne Ardwin (WI9SYN)",
|
"Syne Ardwin (WI9SYN)",
|
||||||
"Szymon Zakrent",
|
"Szymon Zakrent",
|
||||||
"Tobias Mädel",
|
|
||||||
"Youssef Touil",
|
"Youssef Touil",
|
||||||
"Zimm"
|
"Zimm"
|
||||||
};
|
};
|
||||||
@@ -37,13 +37,20 @@ namespace sdrpp_credits {
|
|||||||
const char* hardwareDonators[] = {
|
const char* hardwareDonators[] = {
|
||||||
"Aaronia AG",
|
"Aaronia AG",
|
||||||
"Airspy",
|
"Airspy",
|
||||||
|
"Alex 4Z5LV",
|
||||||
"Analog Devices",
|
"Analog Devices",
|
||||||
"CaribouLabs",
|
"CaribouLabs",
|
||||||
|
"Deepace",
|
||||||
"Ettus Research",
|
"Ettus Research",
|
||||||
|
"Harogic",
|
||||||
"Howard Su",
|
"Howard Su",
|
||||||
|
"MicroPhase",
|
||||||
|
"Microtelecom",
|
||||||
"MyriadRF",
|
"MyriadRF",
|
||||||
"Nuand",
|
"Nuand",
|
||||||
|
"RFNM",
|
||||||
"RFspace",
|
"RFspace",
|
||||||
|
"RigExpert",
|
||||||
"RTL-SDRblog",
|
"RTL-SDRblog",
|
||||||
"SDRplay"
|
"SDRplay"
|
||||||
};
|
};
|
||||||
@@ -54,25 +61,39 @@ namespace sdrpp_credits {
|
|||||||
"Croccydile",
|
"Croccydile",
|
||||||
"Dale L Puckett (K0HYD)",
|
"Dale L Puckett (K0HYD)",
|
||||||
"Daniele D'Agnelli",
|
"Daniele D'Agnelli",
|
||||||
|
"David Taylor (GM8ARV)",
|
||||||
"D. Jones",
|
"D. Jones",
|
||||||
|
"Dexruus",
|
||||||
"EB3FRN",
|
"EB3FRN",
|
||||||
"Eric Johnson",
|
"Eric Johnson",
|
||||||
"Ernest Murphy (NH7L)",
|
"Ernest Murphy (NH7L)",
|
||||||
"Flinger Films",
|
"Flinger Films",
|
||||||
|
"Frank Werner (HB9FXQ)",
|
||||||
"gringogrigio",
|
"gringogrigio",
|
||||||
|
"Jandro",
|
||||||
|
"Jeff Moe",
|
||||||
"Joe Cupano",
|
"Joe Cupano",
|
||||||
|
"KD1SQ",
|
||||||
"Kezza",
|
"Kezza",
|
||||||
"Krys Kamieniecki",
|
"Krys Kamieniecki",
|
||||||
"Lee Donaghy",
|
"Lee Donaghy",
|
||||||
"Lee KD1SQ",
|
"Lee (KD1SQ)",
|
||||||
".lozenge. (Hank Hill)",
|
".lozenge. (Hank Hill)",
|
||||||
|
"Martin Herren (HB9FXX)",
|
||||||
|
"NeoVilsonWong",
|
||||||
|
"Nitin (VU2JEK)",
|
||||||
"ON4MU",
|
"ON4MU",
|
||||||
"Passion-Radio.com",
|
"Passion-Radio.com",
|
||||||
"Paul Maine",
|
"Paul Maine",
|
||||||
|
"Peter Betz",
|
||||||
"Scanner School",
|
"Scanner School",
|
||||||
|
"Scott Palmer",
|
||||||
"SignalsEverywhere",
|
"SignalsEverywhere",
|
||||||
"Syne Ardwin (WI9SYN)",
|
"Syne Ardwin (WI9SYN)",
|
||||||
"W4IPA",
|
"W4IPA",
|
||||||
|
"William Arcand (W1WRA)",
|
||||||
|
"William Pitchford",
|
||||||
|
"Yves Rougy",
|
||||||
"Zipper"
|
"Zipper"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
namespace dsp {
|
namespace dsp {
|
||||||
class generic_block {
|
class generic_block {
|
||||||
public:
|
public:
|
||||||
|
virtual ~generic_block() {}
|
||||||
virtual void start() {}
|
virtual void start() {}
|
||||||
virtual void stop() {}
|
virtual void stop() {}
|
||||||
virtual int run() { return -1; }
|
virtual int run() { return -1; }
|
||||||
@@ -16,8 +17,6 @@ namespace dsp {
|
|||||||
|
|
||||||
class block : public generic_block {
|
class block : public generic_block {
|
||||||
public:
|
public:
|
||||||
virtual void init() {}
|
|
||||||
|
|
||||||
virtual ~block() {
|
virtual ~block() {
|
||||||
if (!_block_init) { return; }
|
if (!_block_init) { return; }
|
||||||
stop();
|
stop();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <volk/volk.h>
|
#include <volk/volk.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
namespace dsp::buffer {
|
namespace dsp::buffer {
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|||||||
@@ -67,10 +67,6 @@ namespace dsp::buffer {
|
|||||||
sizes[writeCur] = count;
|
sizes[writeCur] = count;
|
||||||
writeCur++;
|
writeCur++;
|
||||||
writeCur = ((writeCur) % TEST_BUFFER_SIZE);
|
writeCur = ((writeCur) % TEST_BUFFER_SIZE);
|
||||||
|
|
||||||
// if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) {
|
|
||||||
// flog::warn("Overflow");
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
cnd.notify_all();
|
cnd.notify_all();
|
||||||
_in->flush();
|
_in->flush();
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace dsp {
|
|||||||
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
|
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
|
||||||
// Check that the block is part of the chain
|
// Check that the block is part of the chain
|
||||||
if (!blockExists(block)) {
|
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
|
// If already disabled, don't do anything
|
||||||
@@ -163,10 +163,12 @@ namespace dsp {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Processor<T, T>* blockBefore(Processor<T, T>* block) {
|
Processor<T, T>* blockBefore(Processor<T, T>* block) {
|
||||||
|
Processor<T, T>* prev = NULL;
|
||||||
for (auto& ln : links) {
|
for (auto& ln : links) {
|
||||||
if (ln == block) { return NULL; }
|
if (ln == block) { return prev; }
|
||||||
if (states[ln]) { return ln; }
|
if (states[ln]) { prev = ln; }
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Processor<T, T>* blockAfter(Processor<T, T>* block) {
|
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) {
|
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);
|
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count);
|
||||||
|
#endif
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ namespace dsp::compression {
|
|||||||
|
|
||||||
void init(stream<complex_t>* in, PCMType pcmType) {
|
void init(stream<complex_t>* in, PCMType pcmType) {
|
||||||
_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);
|
base_type::init(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ namespace dsp::demod {
|
|||||||
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
|
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
|
||||||
alFir.init(NULL, audioFirTaps);
|
alFir.init(NULL, audioFirTaps);
|
||||||
arFir.init(NULL, audioFirTaps);
|
arFir.init(NULL, audioFirTaps);
|
||||||
|
xlator.init(NULL, -57000.0, samplerate);
|
||||||
rdsResamp.init(NULL, samplerate, 5000.0);
|
rdsResamp.init(NULL, samplerate, 5000.0);
|
||||||
|
|
||||||
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||||
@@ -56,9 +57,9 @@ namespace dsp::demod {
|
|||||||
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||||
|
|
||||||
lprDelay.out.free();
|
lprDelay.out.free();
|
||||||
lmrDelay.out.free();
|
|
||||||
arFir.out.free();
|
arFir.out.free();
|
||||||
alFir.out.free();
|
alFir.out.free();
|
||||||
|
xlator.out.free();
|
||||||
rdsResamp.out.free();
|
rdsResamp.out.free();
|
||||||
|
|
||||||
base_type::init(in);
|
base_type::init(in);
|
||||||
@@ -92,6 +93,7 @@ namespace dsp::demod {
|
|||||||
alFir.setTaps(audioFirTaps);
|
alFir.setTaps(audioFirTaps);
|
||||||
arFir.setTaps(audioFirTaps);
|
arFir.setTaps(audioFirTaps);
|
||||||
|
|
||||||
|
xlator.setOffset(-57000.0, samplerate);
|
||||||
rdsResamp.setInSamplerate(samplerate);
|
rdsResamp.setInSamplerate(samplerate);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
@@ -139,7 +141,7 @@ namespace dsp::demod {
|
|||||||
base_type::tempStart();
|
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
|
// Demodulate
|
||||||
demod.process(count, in, demod.out.writeBuf);
|
demod.process(count, in, demod.out.writeBuf);
|
||||||
if (_stereo) {
|
if (_stereo) {
|
||||||
@@ -152,24 +154,24 @@ namespace dsp::demod {
|
|||||||
|
|
||||||
// Delay
|
// Delay
|
||||||
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
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
|
// conjugate PLL output to down convert twice the L-R signal
|
||||||
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
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, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.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);
|
||||||
|
|
||||||
// Do RDS demod
|
// Do RDS demod
|
||||||
if (_rdsOut) {
|
if (_rdsOut) {
|
||||||
// Since the PLL output is no longer needed after this, use it as the output
|
// Translate to 0Hz
|
||||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||||
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
|
|
||||||
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
|
// Resample to the output samplerate
|
||||||
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
|
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert output back to real for further processing
|
// 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
|
// Amplify by 2x
|
||||||
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
|
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
|
||||||
@@ -193,24 +195,11 @@ namespace dsp::demod {
|
|||||||
// Convert to complex
|
// Convert to complex
|
||||||
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
|
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
|
||||||
|
|
||||||
// Filter out pilot and run through PLL
|
// Translate to 0Hz
|
||||||
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
|
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||||
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
|
|
||||||
|
|
||||||
// Delay
|
// Resample to the output samplerate
|
||||||
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter if needed
|
// Filter if needed
|
||||||
@@ -240,7 +229,7 @@ namespace dsp::demod {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream<float> rdsOut;
|
stream<complex_t> rdsOut;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
double _deviation;
|
double _deviation;
|
||||||
@@ -253,13 +242,14 @@ namespace dsp::demod {
|
|||||||
tap<complex_t> pilotFirTaps;
|
tap<complex_t> pilotFirTaps;
|
||||||
filter::FIR<complex_t, complex_t> pilotFir;
|
filter::FIR<complex_t, complex_t> pilotFir;
|
||||||
convert::RealToComplex rtoc;
|
convert::RealToComplex rtoc;
|
||||||
|
channel::FrequencyXlator xlator;
|
||||||
loop::PLL pilotPLL;
|
loop::PLL pilotPLL;
|
||||||
math::Delay<float> lprDelay;
|
math::Delay<float> lprDelay;
|
||||||
math::Delay<complex_t> lmrDelay;
|
math::Delay<complex_t> lmrDelay;
|
||||||
tap<float> audioFirTaps;
|
tap<float> audioFirTaps;
|
||||||
filter::FIR<float, float> arFir;
|
filter::FIR<float, float> arFir;
|
||||||
filter::FIR<float, float> alFir;
|
filter::FIR<float, float> alFir;
|
||||||
multirate::RationalResampler<float> rdsResamp;
|
multirate::RationalResampler<dsp::complex_t> rdsResamp;
|
||||||
|
|
||||||
float* lmr;
|
float* lmr;
|
||||||
float* l;
|
float* l;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "quadrature.h"
|
#include "quadrature.h"
|
||||||
#include "../filter/fir.h"
|
#include "../filter/fir.h"
|
||||||
#include "../taps/low_pass.h"
|
#include "../taps/low_pass.h"
|
||||||
|
#include "../taps/high_pass.h"
|
||||||
|
#include "../taps/band_pass.h"
|
||||||
#include "../convert/mono_to_stereo.h"
|
#include "../convert/mono_to_stereo.h"
|
||||||
|
|
||||||
namespace dsp::demod {
|
namespace dsp::demod {
|
||||||
@@ -17,7 +19,7 @@ namespace dsp::demod {
|
|||||||
~FM() {
|
~FM() {
|
||||||
if (!base_type::_block_init) { return; }
|
if (!base_type::_block_init) { return; }
|
||||||
base_type::stop();
|
base_type::stop();
|
||||||
dsp::taps::free(lpfTaps);
|
dsp::taps::free(filterTaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) {
|
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) {
|
||||||
@@ -26,13 +28,16 @@ namespace dsp::demod {
|
|||||||
_lowPass = lowPass;
|
_lowPass = lowPass;
|
||||||
|
|
||||||
demod.init(NULL, bandwidth / 2.0, _samplerate);
|
demod.init(NULL, bandwidth / 2.0, _samplerate);
|
||||||
lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
|
loadDummyTaps();
|
||||||
lpf.init(NULL, lpfTaps);
|
fir.init(NULL, filterTaps);
|
||||||
|
|
||||||
|
// Initialize taps
|
||||||
|
updateFilter(lowPass);
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
demod.out.free();
|
demod.out.free();
|
||||||
}
|
}
|
||||||
lpf.out.free();
|
fir.out.free();
|
||||||
|
|
||||||
base_type::init(in);
|
base_type::init(in);
|
||||||
}
|
}
|
||||||
@@ -43,9 +48,7 @@ namespace dsp::demod {
|
|||||||
base_type::tempStop();
|
base_type::tempStop();
|
||||||
_samplerate = samplerate;
|
_samplerate = samplerate;
|
||||||
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
||||||
dsp::taps::free(lpfTaps);
|
updateFilter(_lowPass);
|
||||||
lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
|
|
||||||
lpf.setTaps(lpfTaps);
|
|
||||||
base_type::tempStart();
|
base_type::tempStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,19 +57,14 @@ namespace dsp::demod {
|
|||||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
if (bandwidth == _bandwidth) { return; }
|
if (bandwidth == _bandwidth) { return; }
|
||||||
_bandwidth = bandwidth;
|
_bandwidth = bandwidth;
|
||||||
std::lock_guard<std::mutex> lck2(lpfMtx);
|
|
||||||
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
||||||
dsp::taps::free(lpfTaps);
|
updateFilter(_lowPass);
|
||||||
lpfTaps = dsp::taps::lowPass(_bandwidth / 2, (_bandwidth / 2) * 0.1, _samplerate);
|
|
||||||
lpf.setTaps(lpfTaps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLowPass(bool lowPass) {
|
void setLowPass(bool lowPass) {
|
||||||
assert(base_type::_block_init);
|
assert(base_type::_block_init);
|
||||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
std::lock_guard<std::mutex> lck2(lpfMtx);
|
updateFilter(lowPass);
|
||||||
_lowPass = lowPass;
|
|
||||||
lpf.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
@@ -74,7 +72,7 @@ namespace dsp::demod {
|
|||||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
base_type::tempStop();
|
base_type::tempStop();
|
||||||
demod.reset();
|
demod.reset();
|
||||||
lpf.reset();
|
fir.reset();
|
||||||
base_type::tempStart();
|
base_type::tempStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,15 +80,15 @@ namespace dsp::demod {
|
|||||||
if constexpr (std::is_same_v<T, float>) {
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
demod.process(count, in, out);
|
demod.process(count, in, out);
|
||||||
if (_lowPass) {
|
if (_lowPass) {
|
||||||
std::lock_guard<std::mutex> lck(lpfMtx);
|
std::lock_guard<std::mutex> lck(filterMtx);
|
||||||
lpf.process(count, out, out);
|
fir.process(count, out, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||||
demod.process(count, in, demod.out.writeBuf);
|
demod.process(count, in, demod.out.writeBuf);
|
||||||
if (_lowPass) {
|
if (_lowPass) {
|
||||||
std::lock_guard<std::mutex> lck(lpfMtx);
|
std::lock_guard<std::mutex> lck(filterMtx);
|
||||||
lpf.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
fir.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
||||||
}
|
}
|
||||||
convert::MonoToStereo::process(count, demod.out.writeBuf, out);
|
convert::MonoToStereo::process(count, demod.out.writeBuf, out);
|
||||||
}
|
}
|
||||||
@@ -109,13 +107,41 @@ namespace dsp::demod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updateFilter(bool lowPass) {
|
||||||
|
std::lock_guard<std::mutex> lck(filterMtx);
|
||||||
|
|
||||||
|
// Update values
|
||||||
|
_lowPass = lowPass;
|
||||||
|
|
||||||
|
// Free filter taps
|
||||||
|
dsp::taps::free(filterTaps);
|
||||||
|
|
||||||
|
// Generate filter depending on the low pass settings
|
||||||
|
if (_lowPass) {
|
||||||
|
filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadDummyTaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set filter to use new taps
|
||||||
|
fir.setTaps(filterTaps);
|
||||||
|
fir.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadDummyTaps() {
|
||||||
|
float dummyTap = 1.0f;
|
||||||
|
filterTaps = dsp::taps::fromArray<float>(1, &dummyTap);
|
||||||
|
}
|
||||||
|
|
||||||
double _samplerate;
|
double _samplerate;
|
||||||
double _bandwidth;
|
double _bandwidth;
|
||||||
bool _lowPass;
|
bool _lowPass;
|
||||||
|
bool filtering;
|
||||||
|
|
||||||
Quadrature demod;
|
Quadrature demod;
|
||||||
tap<float> lpfTaps;
|
tap<float> filterTaps;
|
||||||
filter::FIR<float, float> lpf;
|
filter::FIR<float, float> fir;
|
||||||
std::mutex lpfMtx;
|
std::mutex filterMtx;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ namespace dsp::demod {
|
|||||||
else if (_mode == Mode::LSB) {
|
else if (_mode == Mode::LSB) {
|
||||||
return -_bandwidth / 2.0;
|
return -_bandwidth / 2.0;
|
||||||
}
|
}
|
||||||
else if (_mode == Mode::DSB) {
|
else {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ namespace dsp::filter {
|
|||||||
|
|
||||||
// Move existing data to make transition seemless
|
// Move existing data to make transition seemless
|
||||||
if (_taps.size < oldTC) {
|
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) {
|
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);
|
buffer::clear<D>(buffer, _taps.size - oldTC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ namespace dsp::loop {
|
|||||||
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void advancePhase() {
|
||||||
|
phase += freq;
|
||||||
|
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||||
|
}
|
||||||
|
|
||||||
T freq;
|
T freq;
|
||||||
T phase;
|
T phase;
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
#include "../multirate/rrc_interpolator.h"
|
#include "../multirate/rrc_interpolator.h"
|
||||||
|
|
||||||
namespace dsp::mod {
|
namespace dsp::mod {
|
||||||
|
// TODO: Check if resample before RRC is better than using the RRC taps as a filter (bandwidth probably not correct for alias-free resampling)
|
||||||
typedef multirate::RRCInterpolator<complex_t> PSK;
|
typedef multirate::RRCInterpolator<complex_t> PSK;
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "../math/hz_to_rads.h"
|
#include "../math/hz_to_rads.h"
|
||||||
|
|
||||||
namespace dsp::mod {
|
namespace dsp::mod {
|
||||||
class Quadrature : Processor<float, complex_t> {
|
class Quadrature : public Processor<float, complex_t> {
|
||||||
using base_type = Processor<float, complex_t>;
|
using base_type = Processor<float, complex_t>;
|
||||||
public:
|
public:
|
||||||
Quadrature() {}
|
Quadrature() {}
|
||||||
|
|||||||
@@ -83,8 +83,6 @@ namespace dsp::multirate {
|
|||||||
int interp = OutSR / gcd;
|
int interp = OutSR / gcd;
|
||||||
int decim = InSR / gcd;
|
int decim = InSR / gcd;
|
||||||
|
|
||||||
flog::warn("interp: {0}, decim: {1}", interp, decim);
|
|
||||||
|
|
||||||
// Configure resampler
|
// Configure resampler
|
||||||
double tapSamplerate = _symbolrate * (double)interp;
|
double tapSamplerate = _symbolrate * (double)interp;
|
||||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount * interp, _rrcBeta, _symbolrate, tapSamplerate);
|
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount * interp, _rrcBeta, _symbolrate, tapSamplerate);
|
||||||
|
|||||||
303
core/src/dsp/noise_reduction/ctcss_squelch.h
Normal file
303
core/src/dsp/noise_reduction/ctcss_squelch.h
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../channel/rx_vfo.h"
|
||||||
|
#include "../demod/quadrature.h"
|
||||||
|
#include "../filter/fir.h"
|
||||||
|
#include "../taps/high_pass.h"
|
||||||
|
#include <fftw3.h>
|
||||||
|
#include <map>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#define CTCSS_DECODE_SAMPLERATE 500//250.0
|
||||||
|
#define CTCSS_DECODE_BANDWIDTH 200.0
|
||||||
|
#define CTCSS_DECODE_OFFSET 160.55
|
||||||
|
|
||||||
|
namespace dsp::noise_reduction {
|
||||||
|
enum CTCSSTone {
|
||||||
|
/**
|
||||||
|
* Indicates that any valid tone will let audio through.
|
||||||
|
*/
|
||||||
|
CTCSS_TONE_ANY = -2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that no tone is being received, or to act as a decoder only, letting audio through continuously.
|
||||||
|
*/
|
||||||
|
CTCSS_TONE_NONE = -1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CTCSS Tone Frequency.
|
||||||
|
*/
|
||||||
|
CTCSS_TONE_67Hz,
|
||||||
|
CTCSS_TONE_69_3Hz,
|
||||||
|
CTCSS_TONE_71_9Hz,
|
||||||
|
CTCSS_TONE_74_4Hz,
|
||||||
|
CTCSS_TONE_77Hz,
|
||||||
|
CTCSS_TONE_79_7Hz,
|
||||||
|
CTCSS_TONE_82_5Hz,
|
||||||
|
CTCSS_TONE_85_4Hz,
|
||||||
|
CTCSS_TONE_88_5Hz,
|
||||||
|
CTCSS_TONE_91_5Hz,
|
||||||
|
CTCSS_TONE_94_8Hz,
|
||||||
|
CTCSS_TONE_97_4Hz,
|
||||||
|
CTCSS_TONE_100Hz,
|
||||||
|
CTCSS_TONE_103_5Hz,
|
||||||
|
CTCSS_TONE_107_2Hz,
|
||||||
|
CTCSS_TONE_110_9Hz,
|
||||||
|
CTCSS_TONE_114_8Hz,
|
||||||
|
CTCSS_TONE_118_8Hz,
|
||||||
|
CTCSS_TONE_123Hz,
|
||||||
|
CTCSS_TONE_127_3Hz,
|
||||||
|
CTCSS_TONE_131_8Hz,
|
||||||
|
CTCSS_TONE_136_5Hz,
|
||||||
|
CTCSS_TONE_141_3Hz,
|
||||||
|
CTCSS_TONE_146_2Hz,
|
||||||
|
CTCSS_TONE_150Hz,
|
||||||
|
CTCSS_TONE_151_4Hz,
|
||||||
|
CTCSS_TONE_156_7Hz,
|
||||||
|
CTCSS_TONE_159_8Hz,
|
||||||
|
CTCSS_TONE_162_2Hz,
|
||||||
|
CTCSS_TONE_165_5Hz,
|
||||||
|
CTCSS_TONE_167_9Hz,
|
||||||
|
CTCSS_TONE_171_3Hz,
|
||||||
|
CTCSS_TONE_173_8Hz,
|
||||||
|
CTCSS_TONE_177_3Hz,
|
||||||
|
CTCSS_TONE_179_9Hz,
|
||||||
|
CTCSS_TONE_183_5Hz,
|
||||||
|
CTCSS_TONE_186_2Hz,
|
||||||
|
CTCSS_TONE_189_9Hz,
|
||||||
|
CTCSS_TONE_192_8Hz,
|
||||||
|
CTCSS_TONE_196_6Hz,
|
||||||
|
CTCSS_TONE_199_5Hz,
|
||||||
|
CTCSS_TONE_203_5Hz,
|
||||||
|
CTCSS_TONE_206_5Hz,
|
||||||
|
CTCSS_TONE_210_7Hz,
|
||||||
|
CTCSS_TONE_218_1Hz,
|
||||||
|
CTCSS_TONE_225_7Hz,
|
||||||
|
CTCSS_TONE_229_1Hz,
|
||||||
|
CTCSS_TONE_233_6Hz,
|
||||||
|
CTCSS_TONE_241_8Hz,
|
||||||
|
CTCSS_TONE_250_3Hz,
|
||||||
|
CTCSS_TONE_254_1Hz,
|
||||||
|
_CTCSS_TONE_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
const float CTCSS_TONES[_CTCSS_TONE_COUNT] = {
|
||||||
|
67.0f,
|
||||||
|
69.3f,
|
||||||
|
71.9f,
|
||||||
|
74.4f,
|
||||||
|
77.0f,
|
||||||
|
79.7f,
|
||||||
|
82.5f,
|
||||||
|
85.4f,
|
||||||
|
88.5f,
|
||||||
|
91.5f,
|
||||||
|
94.8f,
|
||||||
|
97.4f,
|
||||||
|
100.0f,
|
||||||
|
103.5f,
|
||||||
|
107.2f,
|
||||||
|
110.9f,
|
||||||
|
114.8f,
|
||||||
|
118.8f,
|
||||||
|
123.0f,
|
||||||
|
127.3f,
|
||||||
|
131.8f,
|
||||||
|
136.5f,
|
||||||
|
141.3f,
|
||||||
|
146.2f,
|
||||||
|
150.0f,
|
||||||
|
151.4f,
|
||||||
|
156.7f,
|
||||||
|
159.8f,
|
||||||
|
162.2f,
|
||||||
|
165.5f,
|
||||||
|
167.9f,
|
||||||
|
171.3f,
|
||||||
|
173.8f,
|
||||||
|
177.3f,
|
||||||
|
179.9f,
|
||||||
|
183.5f,
|
||||||
|
186.2f,
|
||||||
|
189.9f,
|
||||||
|
192.8f,
|
||||||
|
196.6f,
|
||||||
|
199.5f,
|
||||||
|
203.5f,
|
||||||
|
206.5f,
|
||||||
|
210.7f,
|
||||||
|
218.1f,
|
||||||
|
225.7f,
|
||||||
|
229.1f,
|
||||||
|
233.6f,
|
||||||
|
241.8f,
|
||||||
|
250.3f,
|
||||||
|
254.1f
|
||||||
|
};
|
||||||
|
|
||||||
|
class CTCSSSquelch : public Processor<stereo_t, stereo_t> {
|
||||||
|
using base_type = Processor<stereo_t, stereo_t>;
|
||||||
|
public:
|
||||||
|
CTCSSSquelch() {}
|
||||||
|
|
||||||
|
CTCSSSquelch(stream<stereo_t>* in, double samplerate) { init(in, samplerate); }
|
||||||
|
|
||||||
|
~CTCSSSquelch() {
|
||||||
|
// If not initialized, do nothing
|
||||||
|
if (!base_type::_block_init) { return; }
|
||||||
|
|
||||||
|
// Stop the DSP thread
|
||||||
|
base_type::stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(stream<stereo_t>* in, double samplerate) {
|
||||||
|
// Save settings
|
||||||
|
_samplerate = samplerate;
|
||||||
|
|
||||||
|
// Create dummy taps just for initialization
|
||||||
|
float dummy[1] = { 1.0f };
|
||||||
|
auto dummyTaps = dsp::taps::fromArray(1, dummy);
|
||||||
|
|
||||||
|
// Initialize the DDC and FM demod
|
||||||
|
ddc.init(NULL, samplerate, CTCSS_DECODE_SAMPLERATE, CTCSS_DECODE_BANDWIDTH, CTCSS_DECODE_OFFSET);
|
||||||
|
fm.init(NULL, 1.0, CTCSS_DECODE_SAMPLERATE);
|
||||||
|
|
||||||
|
// Initilize the base block class
|
||||||
|
base_type::init(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSamplerate(double samplerate) {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
|
base_type::tempStop();
|
||||||
|
_samplerate = samplerate;
|
||||||
|
ddc.setInSamplerate(samplerate);
|
||||||
|
base_type::tempStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRequiredTone(CTCSSTone tone) {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
requiredTone = tone;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTCSSTone getCurrentTone() {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
return currentTone;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int process(int count, const stereo_t* in, stereo_t* out) {
|
||||||
|
// Shift and resample to the correct samplerate
|
||||||
|
int ddcOutCount = ddc.process(count, (complex_t*)in, ddc.out.writeBuf);
|
||||||
|
|
||||||
|
// FM Demod the CTCSS tone
|
||||||
|
fm.process(ddcOutCount, ddc.out.writeBuf, fm.out.writeBuf);
|
||||||
|
|
||||||
|
// Get the required tone
|
||||||
|
const CTCSSTone rtone = requiredTone;
|
||||||
|
|
||||||
|
// Detect the tone frequency
|
||||||
|
for (int i = 0; i < ddcOutCount; i++) {
|
||||||
|
// Compute the running mean
|
||||||
|
const float val = fm.out.writeBuf[i];
|
||||||
|
mean = 0.95f*mean + 0.05f*val;
|
||||||
|
|
||||||
|
// Compute the running variance
|
||||||
|
const float err = val - mean;
|
||||||
|
var = 0.95f*var + 0.05f*err*err;
|
||||||
|
|
||||||
|
// Run a schmitt trigger on the variance
|
||||||
|
bool nvarOk = varOk ? (var < 1100.0f) : (var < 1000.0f);
|
||||||
|
|
||||||
|
// Check if the tone has to be rematched
|
||||||
|
if (nvarOk && (!varOk || mean < minFreq || mean > maxFreq)) {
|
||||||
|
// Compute the absolute frequency
|
||||||
|
float freq = mean + CTCSS_DECODE_OFFSET;
|
||||||
|
|
||||||
|
// Check it against the known tones
|
||||||
|
if (freq < CTCSS_TONES[0] - 2.5) {
|
||||||
|
currentTone = CTCSS_TONE_NONE;
|
||||||
|
}
|
||||||
|
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1] + 2.5) {
|
||||||
|
currentTone = CTCSS_TONE_NONE;
|
||||||
|
}
|
||||||
|
else if (freq < CTCSS_TONES[0]) {
|
||||||
|
currentTone = (CTCSSTone)0;
|
||||||
|
}
|
||||||
|
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1]) {
|
||||||
|
currentTone = (CTCSSTone)(_CTCSS_TONE_COUNT-1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int a = 0;
|
||||||
|
int b = _CTCSS_TONE_COUNT-1;
|
||||||
|
while (b - a > 1) {
|
||||||
|
int c = (a + b) >> 1;
|
||||||
|
((CTCSS_TONES[c] < freq) ? a : b) = c;
|
||||||
|
}
|
||||||
|
currentTone = (CTCSSTone)((freq - CTCSS_TONES[a] < CTCSS_TONES[b] - freq) ? a : b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the mute status
|
||||||
|
mute = !(currentTone == rtone || (currentTone != CTCSS_TONE_NONE && rtone == CTCSS_TONE_ANY));
|
||||||
|
|
||||||
|
// Unmuted the audio if needed
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// Recompute min and max freq if a valid tone is detected
|
||||||
|
if (currentTone != CTCSS_TONE_NONE) {
|
||||||
|
float c = CTCSS_TONES[currentTone];
|
||||||
|
float l = (currentTone > CTCSS_TONE_67Hz) ? CTCSS_TONES[currentTone - 1] : c - 2.5f;
|
||||||
|
float r = (currentTone < CTCSS_TONE_254_1Hz) ? CTCSS_TONES[currentTone + 1] : c + 2.5f;
|
||||||
|
minFreq = (l+c) / 2.0f;
|
||||||
|
maxFreq = (r+c) / 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a rising edge on the variance
|
||||||
|
if (!nvarOk && varOk) {
|
||||||
|
// Mute the audio
|
||||||
|
// TODO
|
||||||
|
mute = true;
|
||||||
|
currentTone = CTCSS_TONE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the new variance state
|
||||||
|
varOk = nvarOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG ONLY
|
||||||
|
if ((rtone != CTCSS_TONE_NONE) && mute) {
|
||||||
|
memset(out, 0, count * sizeof(stereo_t));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(out, in, count * sizeof(stereo_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run() {
|
||||||
|
int count = base_type::_in->read();
|
||||||
|
if (count < 0) { return -1; }
|
||||||
|
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||||
|
base_type::_in->flush();
|
||||||
|
if (!base_type::out.swap(count)) { return -1; }
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
double _samplerate;
|
||||||
|
std::atomic<CTCSSTone> requiredTone = CTCSS_TONE_ANY;
|
||||||
|
|
||||||
|
float mean = 0.0f;
|
||||||
|
float var = 0.0f;
|
||||||
|
bool varOk = false;
|
||||||
|
float minFreq = 0.0f;
|
||||||
|
float maxFreq = 0.0f;
|
||||||
|
bool mute = true;
|
||||||
|
std::atomic<CTCSSTone> currentTone = CTCSS_TONE_NONE;
|
||||||
|
|
||||||
|
channel::RxVFO ddc;
|
||||||
|
demod::Quadrature fm;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
// TODO: Rewrite better!!!!!
|
// TODO: Rewrite better!!!!!
|
||||||
namespace dsp::noise_reduction {
|
namespace dsp::noise_reduction {
|
||||||
class Squelch : public Processor<complex_t, complex_t> {
|
class PowerSquelch : public Processor<complex_t, complex_t> {
|
||||||
using base_type = Processor<complex_t, complex_t>;
|
using base_type = Processor<complex_t, complex_t>;
|
||||||
public:
|
public:
|
||||||
Squelch() {}
|
PowerSquelch() {}
|
||||||
|
|
||||||
Squelch(stream<complex_t>* in, double level) {}
|
PowerSquelch(stream<complex_t>* in, double level) {}
|
||||||
|
|
||||||
~Squelch() {
|
~PowerSquelch() {
|
||||||
if (!base_type::_block_init) { return; }
|
if (!base_type::_block_init) { return; }
|
||||||
base_type::stop();
|
base_type::stop();
|
||||||
buffer::free(normBuffer);
|
buffer::free(normBuffer);
|
||||||
@@ -31,8 +31,11 @@ namespace dsp::noise_reduction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline int process(int count, const complex_t* in, complex_t* out) {
|
inline int process(int count, const complex_t* in, complex_t* out) {
|
||||||
float sum;
|
// Compute the amplitude of each sample
|
||||||
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)in, count);
|
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)in, count);
|
||||||
|
|
||||||
|
// Compute the mean amplitude
|
||||||
|
float sum = 0.0f;
|
||||||
volk_32f_accumulator_s32f(&sum, normBuffer, count);
|
volk_32f_accumulator_s32f(&sum, normBuffer, count);
|
||||||
sum /= (float)count;
|
sum /= (float)count;
|
||||||
|
|
||||||
@@ -46,8 +49,6 @@ namespace dsp::noise_reduction {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DEFAULT_PROC_RUN();
|
|
||||||
|
|
||||||
int run() {
|
int run() {
|
||||||
int count = base_type::_in->read();
|
int count = base_type::_in->read();
|
||||||
if (count < 0) { return -1; }
|
if (count < 0) { return -1; }
|
||||||
@@ -10,6 +10,8 @@ namespace dsp {
|
|||||||
|
|
||||||
Operator(stream<A>* a, stream<B>* b) { init(a, b); }
|
Operator(stream<A>* a, stream<B>* b) { init(a, b); }
|
||||||
|
|
||||||
|
virtual ~Operator() {}
|
||||||
|
|
||||||
virtual void init(stream<A>* a, stream<B>* b) {
|
virtual void init(stream<A>* a, stream<B>* b) {
|
||||||
_a = a;
|
_a = a;
|
||||||
_b = b;
|
_b = b;
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ namespace dsp {
|
|||||||
template <class T>
|
template <class T>
|
||||||
class Source : public block {
|
class Source : public block {
|
||||||
public:
|
public:
|
||||||
Source() {}
|
|
||||||
|
|
||||||
Source() { init(); }
|
Source() { init(); }
|
||||||
|
|
||||||
virtual ~Source() {}
|
virtual ~Source() {}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
namespace dsp {
|
namespace dsp {
|
||||||
class untyped_stream {
|
class untyped_stream {
|
||||||
public:
|
public:
|
||||||
|
virtual ~untyped_stream() {}
|
||||||
virtual bool swap(int size) { return false; }
|
virtual bool swap(int size) { return false; }
|
||||||
virtual int read() { return -1; }
|
virtual int read() { return -1; }
|
||||||
virtual void flush() {}
|
virtual void flush() {}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace dsp::taps {
|
|||||||
if (oddTapCount && !(count % 2)) { count++; }
|
if (oddTapCount && !(count % 2)) { count++; }
|
||||||
return windowedSinc<T>(count, (bandStop - bandStart) / 2.0, sampleRate, [=](double n, double N) {
|
return windowedSinc<T>(count, (bandStop - bandStart) / 2.0, sampleRate, [=](double n, double N) {
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
return cosf(offsetOmega * (float)n) * window::nuttall(n, N);
|
return 2.0f * cosf(offsetOmega * (float)n) * window::nuttall(n, N);
|
||||||
}
|
}
|
||||||
if constexpr (std::is_same_v<T, complex_t>) {
|
if constexpr (std::is_same_v<T, complex_t>) {
|
||||||
// The offset is negative to flip the taps. Complex bandpass are asymetric
|
// The offset is negative to flip the taps. Complex bandpass are asymetric
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <volk/volk.h>
|
#include <volk/volk.h>
|
||||||
|
#include "../buffer/buffer.h"
|
||||||
|
|
||||||
namespace dsp {
|
namespace dsp {
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace dsp {
|
|||||||
|
|
||||||
inline float fastAmplitude() {
|
inline float fastAmplitude() {
|
||||||
float re_abs = fabsf(re);
|
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; }
|
if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; }
|
||||||
return im_abs + 0.4f * re_abs;
|
return im_abs + 0.4f * re_abs;
|
||||||
}
|
}
|
||||||
@@ -125,4 +125,4 @@ namespace dsp {
|
|||||||
float l;
|
float l;
|
||||||
float r;
|
float r;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -433,6 +433,9 @@ void MainWindow::draw() {
|
|||||||
showCredits = false;
|
showCredits = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset waterfall lock
|
||||||
|
lockWaterfallControls = showCredits;
|
||||||
|
|
||||||
// Handle menu resize
|
// Handle menu resize
|
||||||
ImVec2 winSize = ImGui::GetWindowSize();
|
ImVec2 winSize = ImGui::GetWindowSize();
|
||||||
ImVec2 mousePos = ImGui::GetMousePos();
|
ImVec2 mousePos = ImGui::GetMousePos();
|
||||||
@@ -463,9 +466,10 @@ void MainWindow::draw() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process menu keybinds
|
||||||
|
displaymenu::checkKeybinds();
|
||||||
|
|
||||||
// Left Column
|
// Left Column
|
||||||
lockWaterfallControls = false;
|
|
||||||
if (showMenu) {
|
if (showMenu) {
|
||||||
ImGui::Columns(3, "WindowColumns", false);
|
ImGui::Columns(3, "WindowColumns", false);
|
||||||
ImGui::SetColumnWidth(0, menuWidth);
|
ImGui::SetColumnWidth(0, menuWidth);
|
||||||
@@ -576,8 +580,20 @@ void MainWindow::draw() {
|
|||||||
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
|
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
|
||||||
double nfreq;
|
double nfreq;
|
||||||
if (vfo != NULL) {
|
if (vfo != NULL) {
|
||||||
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (vfo->snapInterval * wheel);
|
// Select factor depending on modifier keys
|
||||||
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
|
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 {
|
else {
|
||||||
nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0);
|
nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <signal_path/signal_path.h>
|
#include <signal_path/signal_path.h>
|
||||||
#include <gui/style.h>
|
#include <gui/style.h>
|
||||||
#include <utils/optionlist.h>
|
#include <utils/optionlist.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace displaymenu {
|
namespace displaymenu {
|
||||||
bool showWaterfall;
|
bool showWaterfall;
|
||||||
@@ -18,50 +19,44 @@ namespace displaymenu {
|
|||||||
std::string colorMapAuthor = "";
|
std::string colorMapAuthor = "";
|
||||||
int selectedWindow = 0;
|
int selectedWindow = 0;
|
||||||
int fftRate = 20;
|
int fftRate = 20;
|
||||||
|
int fftSizeId = 0;
|
||||||
int uiScaleId = 0;
|
int uiScaleId = 0;
|
||||||
bool restartRequired = false;
|
bool restartRequired = false;
|
||||||
bool fftHold = false;
|
bool fftHold = false;
|
||||||
int fftHoldSpeed = 60;
|
int fftHoldSpeed = 60;
|
||||||
|
bool fftSmoothing = false;
|
||||||
|
int fftSmoothingSpeed = 100;
|
||||||
|
bool snrSmoothing = false;
|
||||||
|
int snrSmoothingSpeed = 20;
|
||||||
|
|
||||||
|
OptionList<int, int> fftSizes;
|
||||||
OptionList<float, float> uiScales;
|
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[] = {
|
const IQFrontEnd::FFTWindow fftWindowList[] = {
|
||||||
IQFrontEnd::FFTWindow::RECTANGULAR,
|
IQFrontEnd::FFTWindow::RECTANGULAR,
|
||||||
IQFrontEnd::FFTWindow::BLACKMAN,
|
IQFrontEnd::FFTWindow::BLACKMAN,
|
||||||
IQFrontEnd::FFTWindow::NUTTALL
|
IQFrontEnd::FFTWindow::NUTTALL
|
||||||
};
|
};
|
||||||
|
|
||||||
void updateFFTHoldSpeed() {
|
void updateFFTSpeeds() {
|
||||||
gui::waterfall.setFFTHoldSpeed(fftHoldSpeed / (fftRate * 10.0f));
|
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() {
|
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 = core::configManager.conf["showWaterfall"];
|
||||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||||
std::string colormapName = core::configManager.conf["colorMap"];
|
std::string colormapName = core::configManager.conf["colorMap"];
|
||||||
@@ -83,15 +78,12 @@ namespace displaymenu {
|
|||||||
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
|
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
|
||||||
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
|
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
|
||||||
|
|
||||||
fftSizeId = 3;
|
fftSizeId = fftSizes.valueId(65536);
|
||||||
int fftSize = core::configManager.conf["fftSize"];
|
int size = core::configManager.conf["fftSize"];
|
||||||
for (int i = 0; i < 7; i++) {
|
if (fftSizes.keyExists(size)) {
|
||||||
if (fftSize == FFTSizes[i]) {
|
fftSizeId = fftSizes.keyId(size);
|
||||||
fftSizeId = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
|
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
|
||||||
|
|
||||||
fftRate = core::configManager.conf["fftRate"];
|
fftRate = core::configManager.conf["fftRate"];
|
||||||
sigpath::iqFrontEnd.setFFTRate(fftRate);
|
sigpath::iqFrontEnd.setFFTRate(fftRate);
|
||||||
@@ -104,7 +96,13 @@ namespace displaymenu {
|
|||||||
fftHold = core::configManager.conf["fftHold"];
|
fftHold = core::configManager.conf["fftHold"];
|
||||||
fftHoldSpeed = core::configManager.conf["fftHoldSpeed"];
|
fftHoldSpeed = core::configManager.conf["fftHoldSpeed"];
|
||||||
gui::waterfall.setFFTHold(fftHold);
|
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
|
// Define and load UI scales
|
||||||
uiScales.define(1.0f, "100%", 1.0f);
|
uiScales.define(1.0f, "100%", 1.0f);
|
||||||
@@ -114,15 +112,24 @@ namespace displaymenu {
|
|||||||
uiScaleId = uiScales.valueId(style::uiScale);
|
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) {
|
void draw(void* ctx) {
|
||||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||||
bool homePressed = ImGui::IsKeyPressed(ImGuiKey_Home, false);
|
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall)) {
|
||||||
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall) || homePressed) {
|
setWaterfallShown(showWaterfall);
|
||||||
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("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
|
if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
|
||||||
@@ -144,16 +151,47 @@ namespace displaymenu {
|
|||||||
core::configManager.conf["fftHold"] = fftHold;
|
core::configManager.conf["fftHold"] = fftHold;
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
ImGui::LeftLabel("FFT Hold Speed");
|
|
||||||
ImGui::FillWidth();
|
ImGui::FillWidth();
|
||||||
if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) {
|
if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) {
|
||||||
updateFFTHoldSpeed();
|
updateFFTSpeeds();
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed;
|
core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed;
|
||||||
core::configManager.release(true);
|
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::LeftLabel("High-DPI Scaling");
|
||||||
ImGui::FillWidth();
|
ImGui::FillWidth();
|
||||||
if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) {
|
if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) {
|
||||||
@@ -168,7 +206,7 @@ namespace displaymenu {
|
|||||||
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
|
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
|
||||||
fftRate = std::max<int>(1, fftRate);
|
fftRate = std::max<int>(1, fftRate);
|
||||||
sigpath::iqFrontEnd.setFFTRate(fftRate);
|
sigpath::iqFrontEnd.setFFTRate(fftRate);
|
||||||
updateFFTHoldSpeed();
|
updateFFTSpeeds();
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["fftRate"] = fftRate;
|
core::configManager.conf["fftRate"] = fftRate;
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
@@ -176,10 +214,10 @@ namespace displaymenu {
|
|||||||
|
|
||||||
ImGui::LeftLabel("FFT Size");
|
ImGui::LeftLabel("FFT Size");
|
||||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||||
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) {
|
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) {
|
||||||
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
|
sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["fftSize"] = FFTSizes[fftSizeId];
|
core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId);
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,4 +248,4 @@ namespace displaymenu {
|
|||||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Restart required.");
|
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Restart required.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
namespace displaymenu {
|
namespace displaymenu {
|
||||||
void init();
|
void init();
|
||||||
|
void checkKeybinds();
|
||||||
void draw(void* ctx);
|
void draw(void* ctx);
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ namespace module_manager_menu {
|
|||||||
ImVec2 btnSize = ImVec2(lheight, lheight - 1);
|
ImVec2 btnSize = ImVec2(lheight, lheight - 1);
|
||||||
ImVec2 textOff = ImVec2(3.0f * style::uiScale, -5.0f * style::uiScale);
|
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("Name");
|
||||||
ImGui::TableSetupColumn("Type");
|
ImGui::TableSetupColumn("Type");
|
||||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth);
|
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth);
|
||||||
|
|||||||
@@ -5,174 +5,301 @@
|
|||||||
#include <gui/main_window.h>
|
#include <gui/main_window.h>
|
||||||
#include <gui/style.h>
|
#include <gui/style.h>
|
||||||
#include <signal_path/signal_path.h>
|
#include <signal_path/signal_path.h>
|
||||||
|
#include <utils/optionlist.h>
|
||||||
|
#include <gui/dialogs/dialog_box.h>
|
||||||
|
|
||||||
namespace sourcemenu {
|
namespace sourcemenu {
|
||||||
int offsetMode = 0;
|
|
||||||
int sourceId = 0;
|
int sourceId = 0;
|
||||||
double customOffset = 0.0;
|
EventHandler<std::string> sourcesChangedHandler;
|
||||||
double effectiveOffset = 0.0;
|
EventHandler<std::string> sourceUnregisterHandler;
|
||||||
int decimationPower = 0;
|
OptionList<std::string, std::string> sources;
|
||||||
|
std::string selectedSource;
|
||||||
|
|
||||||
|
int decimId = 0;
|
||||||
|
OptionList<int, int> decimations;
|
||||||
|
|
||||||
bool iqCorrection = false;
|
bool iqCorrection = false;
|
||||||
bool invertIQ = false;
|
bool invertIQ = false;
|
||||||
|
|
||||||
std::vector<std::string> sourceNames;
|
int offsetId = 0;
|
||||||
std::string sourceNamesTxt;
|
double manualOffset = 0.0;
|
||||||
std::string selectedSource;
|
std::string selectedOffset;
|
||||||
|
double effectiveOffset = 0.0;
|
||||||
|
OptionList<std::string, double> offsets;
|
||||||
|
std::map<std::string, double> namedOffsets;
|
||||||
|
|
||||||
|
bool showAddOffsetDialog = false;
|
||||||
|
char newOffsetName[1024];
|
||||||
|
double newOffset = 0.0;
|
||||||
|
|
||||||
|
bool showDelOffsetDialog = false;
|
||||||
|
std::string delOffsetName = "";
|
||||||
|
|
||||||
|
// Offset IDs
|
||||||
enum {
|
enum {
|
||||||
OFFSET_MODE_NONE,
|
OFFSET_ID_NONE,
|
||||||
OFFSET_MODE_CUSTOM,
|
OFFSET_ID_MANUAL,
|
||||||
OFFSET_MODE_SPYVERTER,
|
OFFSET_ID_CUSTOM_BASE
|
||||||
OFFSET_MODE_HAM_IT_UP,
|
|
||||||
OFFSET_MODE_MMDS_SB_1998,
|
|
||||||
OFFSET_MODE_DK5AV_XB,
|
|
||||||
OFFSET_MODE_KU_LNB_9750,
|
|
||||||
OFFSET_MODE_KU_LNB_10700,
|
|
||||||
_OFFSET_MODE_COUNT
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* offsetModesTxt = "None\0"
|
|
||||||
"Custom\0"
|
|
||||||
"SpyVerter\0"
|
|
||||||
"Ham-It-Up\0"
|
|
||||||
"MMDS S-band (1998MHz)\0"
|
|
||||||
"DK5AV X-Band\0"
|
|
||||||
"Ku LNB (9750MHz)\0"
|
|
||||||
"Ku LNB (10700MHz)\0";
|
|
||||||
|
|
||||||
const char* decimationStages = "None\0"
|
|
||||||
"2\0"
|
|
||||||
"4\0"
|
|
||||||
"8\0"
|
|
||||||
"16\0"
|
|
||||||
"32\0"
|
|
||||||
"64\0";
|
|
||||||
|
|
||||||
void updateOffset() {
|
void updateOffset() {
|
||||||
if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; }
|
// Compute the effective offset
|
||||||
else if (offsetMode == OFFSET_MODE_SPYVERTER) {
|
switch (offsetId) {
|
||||||
effectiveOffset = 120000000;
|
case OFFSET_ID_NONE:
|
||||||
} // 120MHz Up-conversion
|
|
||||||
else if (offsetMode == OFFSET_MODE_HAM_IT_UP) {
|
|
||||||
effectiveOffset = 125000000;
|
|
||||||
} // 125MHz Up-conversion
|
|
||||||
else if (offsetMode == OFFSET_MODE_MMDS_SB_1998) {
|
|
||||||
effectiveOffset = -1998000000;
|
|
||||||
} // 1.998GHz Down-conversion
|
|
||||||
else if (offsetMode == OFFSET_MODE_DK5AV_XB) {
|
|
||||||
effectiveOffset = -6800000000;
|
|
||||||
} // 6.8GHz Down-conversion
|
|
||||||
else if (offsetMode == OFFSET_MODE_KU_LNB_9750) {
|
|
||||||
effectiveOffset = -9750000000;
|
|
||||||
} // 9.750GHz Down-conversion
|
|
||||||
else if (offsetMode == OFFSET_MODE_KU_LNB_10700) {
|
|
||||||
effectiveOffset = -10700000000;
|
|
||||||
} // 10.7GHz Down-conversion
|
|
||||||
else {
|
|
||||||
effectiveOffset = 0;
|
effectiveOffset = 0;
|
||||||
|
break;
|
||||||
|
case OFFSET_ID_MANUAL:
|
||||||
|
effectiveOffset = manualOffset;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
effectiveOffset = namedOffsets[offsets.name(offsetId)];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply it
|
||||||
sigpath::sourceManager.setTuningOffset(effectiveOffset);
|
sigpath::sourceManager.setTuningOffset(effectiveOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void selectOffsetById(int id) {
|
||||||
|
// Update the offset mode
|
||||||
|
offsetId = id;
|
||||||
|
selectedOffset = offsets.name(id);
|
||||||
|
|
||||||
|
// Update the offset
|
||||||
|
updateOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectOffsetByName(const std::string& name) {
|
||||||
|
// If the name doesn't exist, select 'None'
|
||||||
|
if (!offsets.nameExists(name)) {
|
||||||
|
selectOffsetById(OFFSET_ID_NONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select using the ID associated with the name
|
||||||
|
selectOffsetById(offsets.nameId(name));
|
||||||
|
}
|
||||||
|
|
||||||
void refreshSources() {
|
void refreshSources() {
|
||||||
sourceNames = sigpath::sourceManager.getSourceNames();
|
// Get sources
|
||||||
sourceNamesTxt.clear();
|
auto sourceNames = sigpath::sourceManager.getSourceNames();
|
||||||
|
|
||||||
|
// Define source options
|
||||||
|
sources.clear();
|
||||||
for (auto name : sourceNames) {
|
for (auto name : sourceNames) {
|
||||||
sourceNamesTxt += name;
|
sources.define(name, name, name);
|
||||||
sourceNamesTxt += '\0';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectSource(std::string name) {
|
void selectSource(std::string name) {
|
||||||
if (sourceNames.empty()) {
|
// If there is no source, give up
|
||||||
|
if (sources.empty()) {
|
||||||
|
sourceId = 0;
|
||||||
selectedSource.clear();
|
selectedSource.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto it = std::find(sourceNames.begin(), sourceNames.end(), name);
|
|
||||||
if (it == sourceNames.end()) {
|
// If a source with the given name doesn't exist, select the first source instead
|
||||||
selectSource(sourceNames[0]);
|
if (!sources.valueExists(name)) {
|
||||||
|
selectSource(sources.value(0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sourceId = std::distance(sourceNames.begin(), it);
|
|
||||||
selectedSource = sourceNames[sourceId];
|
// Update the GUI variables
|
||||||
sigpath::sourceManager.select(sourceNames[sourceId]);
|
sourceId = sources.valueId(name);
|
||||||
|
selectedSource = name;
|
||||||
|
|
||||||
|
// Select the source module
|
||||||
|
sigpath::sourceManager.selectSource(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSourceRegistered(std::string name) {
|
void onSourcesChanged(std::string name, void* ctx) {
|
||||||
|
// Update the source list
|
||||||
refreshSources();
|
refreshSources();
|
||||||
|
|
||||||
if (selectedSource.empty()) {
|
// Reselect the current source
|
||||||
sourceId = 0;
|
selectSource(selectedSource);
|
||||||
selectSource(sourceNames[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSourceUnregister(std::string name) {
|
void onSourceUnregister(std::string name, void* ctx) {
|
||||||
if (name != selectedSource) { return; }
|
if (name != selectedSource) { return; }
|
||||||
|
|
||||||
// TODO: Stop everything
|
// TODO: Stop everything
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSourceUnregistered(std::string name) {
|
void reloadOffsets() {
|
||||||
refreshSources();
|
// Clear list
|
||||||
|
offsets.clear();
|
||||||
|
namedOffsets.clear();
|
||||||
|
|
||||||
if (sourceNames.empty()) {
|
// Define special offset modes
|
||||||
selectedSource = "";
|
offsets.define("None", OFFSET_ID_NONE);
|
||||||
return;
|
offsets.define("Manual", OFFSET_ID_MANUAL);
|
||||||
|
|
||||||
|
// Acquire the config file
|
||||||
|
core::configManager.acquire();
|
||||||
|
|
||||||
|
// Load custom offsets
|
||||||
|
auto ofs = core::configManager.conf["offsets"].items();
|
||||||
|
for (auto& o : ofs) {
|
||||||
|
namedOffsets[o.key()] = (double)o.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name == selectedSource) {
|
// Define custom offsets
|
||||||
sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1);
|
for (auto& [name, offset] : namedOffsets) {
|
||||||
selectSource(sourceNames[sourceId]);
|
offsets.define(name, offsets.size());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
|
// Release the config file
|
||||||
|
core::configManager.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
|
// Load offset modes
|
||||||
|
reloadOffsets();
|
||||||
|
|
||||||
|
// Define decimation values
|
||||||
|
decimations.define(1, "None", 1);
|
||||||
|
decimations.define(2, "2x", 2);
|
||||||
|
decimations.define(4, "4x", 4);
|
||||||
|
decimations.define(8, "8x", 8);
|
||||||
|
decimations.define(16, "16x", 16);
|
||||||
|
decimations.define(32, "32x", 32);
|
||||||
|
decimations.define(64, "64x", 64);
|
||||||
|
|
||||||
|
// Acquire the config file
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
std::string selected = core::configManager.conf["source"];
|
|
||||||
customOffset = core::configManager.conf["offset"];
|
// Load other settings
|
||||||
offsetMode = core::configManager.conf["offsetMode"];
|
std::string selectedSource = core::configManager.conf["source"];
|
||||||
decimationPower = core::configManager.conf["decimationPower"];
|
manualOffset = core::configManager.conf["manualOffset"];
|
||||||
|
std::string selectedOffset = core::configManager.conf["selectedOffset"];
|
||||||
iqCorrection = core::configManager.conf["iqCorrection"];
|
iqCorrection = core::configManager.conf["iqCorrection"];
|
||||||
invertIQ = core::configManager.conf["invertIQ"];
|
invertIQ = core::configManager.conf["invertIQ"];
|
||||||
|
int decimation = core::configManager.conf["decimation"];
|
||||||
|
if (decimations.keyExists(decimation)) {
|
||||||
|
decimId = decimations.keyId(decimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the config file
|
||||||
|
core::configManager.release();
|
||||||
|
|
||||||
|
// Select the source module
|
||||||
|
refreshSources();
|
||||||
|
selectSource(selectedSource);
|
||||||
|
|
||||||
|
// Update frontend settings
|
||||||
sigpath::iqFrontEnd.setDCBlocking(iqCorrection);
|
sigpath::iqFrontEnd.setDCBlocking(iqCorrection);
|
||||||
sigpath::iqFrontEnd.setInvertIQ(invertIQ);
|
sigpath::iqFrontEnd.setInvertIQ(invertIQ);
|
||||||
updateOffset();
|
sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
|
||||||
|
selectOffsetByName(selectedOffset);
|
||||||
|
|
||||||
refreshSources();
|
// Register handlers
|
||||||
selectSource(selected);
|
sourcesChangedHandler.handler = onSourcesChanged;
|
||||||
sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
|
sourceUnregisterHandler.handler = onSourceUnregister;
|
||||||
|
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourcesChangedHandler);
|
||||||
|
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
|
||||||
|
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourcesChangedHandler);
|
||||||
|
}
|
||||||
|
|
||||||
sigpath::sourceManager.onSourceRegistered.bind(onSourceRegistered);
|
void addOffset(const std::string& name, double offset) {
|
||||||
sigpath::sourceManager.onSourceUnregister.bind(onSourceUnregister);
|
// Acquire the config file
|
||||||
sigpath::sourceManager.onSourceUnregistered.bind(onSourceUnregistered);
|
core::configManager.acquire();
|
||||||
|
|
||||||
core::configManager.release();
|
// Define a new offset
|
||||||
|
core::configManager.conf["offsets"][name] = offset;
|
||||||
|
|
||||||
|
// Acquire the config file
|
||||||
|
core::configManager.release(true);
|
||||||
|
|
||||||
|
// Reload the offsets
|
||||||
|
reloadOffsets();
|
||||||
|
|
||||||
|
// Attempt to re-select the same one
|
||||||
|
selectOffsetByName(selectedOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void delOffset(const std::string& name) {
|
||||||
|
// Acquire the config file
|
||||||
|
core::configManager.acquire();
|
||||||
|
|
||||||
|
// Define a new offset
|
||||||
|
core::configManager.conf["offsets"].erase(name);
|
||||||
|
|
||||||
|
// Acquire the config file
|
||||||
|
core::configManager.release(true);
|
||||||
|
|
||||||
|
// Reload the offsets
|
||||||
|
reloadOffsets();
|
||||||
|
|
||||||
|
// Attempt to re-select the same one
|
||||||
|
selectOffsetByName(selectedOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool addOffsetDialog() {
|
||||||
|
bool open = true;
|
||||||
|
gui::mainWindow.lockWaterfallControls = true;
|
||||||
|
|
||||||
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
|
||||||
|
const char* id = "Add offset##sdrpp_add_offset_dialog_";
|
||||||
|
ImGui::OpenPopup(id);
|
||||||
|
|
||||||
|
if (ImGui::BeginPopup(id, ImGuiWindowFlags_NoResize)) {
|
||||||
|
ImGui::LeftLabel("Name");
|
||||||
|
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||||
|
ImGui::InputText("##sdrpp_add_offset_name", newOffsetName, 1023);
|
||||||
|
|
||||||
|
ImGui::LeftLabel("Offset");
|
||||||
|
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||||
|
ImGui::InputDouble("##sdrpp_add_offset_offset", &newOffset);
|
||||||
|
|
||||||
|
bool nameExists = offsets.nameExists(newOffsetName);
|
||||||
|
bool reservedName = !strcmp(newOffsetName, "None") || !strcmp(newOffsetName, "Manual");
|
||||||
|
bool denyApply = !newOffsetName[0] || nameExists || reservedName;
|
||||||
|
|
||||||
|
if (nameExists) {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "An offset with the given name already exists.");
|
||||||
|
}
|
||||||
|
else if (reservedName) {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "The given name is reserved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (denyApply) { style::beginDisabled(); }
|
||||||
|
if (ImGui::Button("Apply")) {
|
||||||
|
addOffset(newOffsetName, newOffset);
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
if (denyApply) { style::endDisabled(); }
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel")) {
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return open;
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(void* ctx) {
|
void draw(void* ctx) {
|
||||||
float itemWidth = ImGui::GetContentRegionAvail().x;
|
float itemWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
float spacing = lineHeight - ImGui::GetTextLineHeight();
|
||||||
bool running = gui::mainWindow.sdrIsRunning();
|
bool running = gui::mainWindow.sdrIsRunning();
|
||||||
|
|
||||||
if (running) { style::beginDisabled(); }
|
if (running) { style::beginDisabled(); }
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(itemWidth);
|
ImGui::SetNextItemWidth(itemWidth);
|
||||||
if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) {
|
if (ImGui::Combo("##source", &sourceId, sources.txt)) {
|
||||||
selectSource(sourceNames[sourceId]);
|
std::string newSource = sources.value(sourceId);
|
||||||
|
selectSource(newSource);
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["source"] = sourceNames[sourceId];
|
core::configManager.conf["source"] = newSource;
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (running) { style::endDisabled(); }
|
if (running) { style::endDisabled(); }
|
||||||
|
|
||||||
sigpath::sourceManager.showMenu();
|
sigpath::sourceManager.showSelectedMenu();
|
||||||
|
|
||||||
if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) {
|
if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) {
|
||||||
sigpath::iqFrontEnd.setDCBlocking(iqCorrection);
|
sigpath::iqFrontEnd.setDCBlocking(iqCorrection);
|
||||||
@@ -189,21 +316,45 @@ namespace sourcemenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::LeftLabel("Offset mode");
|
ImGui::LeftLabel("Offset mode");
|
||||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX() - 2.0f*(lineHeight + 1.5f*spacing));
|
||||||
if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) {
|
if (ImGui::Combo("##_sdrpp_offset", &offsetId, offsets.txt)) {
|
||||||
updateOffset();
|
selectOffsetById(offsetId);
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["offsetMode"] = offsetMode;
|
core::configManager.conf["selectedOffset"] = offsets.key(offsetId);
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
|
||||||
|
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::BeginDisabled(); }
|
||||||
|
if (ImGui::Button("-##_sdrpp_offset_del_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
|
||||||
|
delOffsetName = selectedOffset;
|
||||||
|
showDelOffsetDialog = true;
|
||||||
|
}
|
||||||
|
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::EndDisabled(); }
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
|
||||||
|
if (ImGui::Button("+##_sdrpp_offset_add_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
|
||||||
|
strcpy(newOffsetName, "New Offset");
|
||||||
|
showAddOffsetDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset delete confirmation
|
||||||
|
if (ImGui::GenericDialog("sdrpp_del_offset_confirm", showDelOffsetDialog, GENERIC_DIALOG_BUTTONS_YES_NO, []() {
|
||||||
|
ImGui::Text("Deleting offset named \"%s\". Are you sure?", delOffsetName.c_str());
|
||||||
|
}) == GENERIC_DIALOG_BUTTON_YES) {
|
||||||
|
delOffset(delOffsetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset add diaglog
|
||||||
|
if (showAddOffsetDialog) { showAddOffsetDialog = addOffsetDialog(); }
|
||||||
|
|
||||||
ImGui::LeftLabel("Offset");
|
ImGui::LeftLabel("Offset");
|
||||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
ImGui::FillWidth();
|
||||||
if (offsetMode == OFFSET_MODE_CUSTOM) {
|
if (offsetId == OFFSET_ID_MANUAL) {
|
||||||
if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) {
|
if (ImGui::InputDouble("##freq_offset", &manualOffset, 1.0, 100.0)) {
|
||||||
updateOffset();
|
updateOffset();
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["offset"] = customOffset;
|
core::configManager.conf["manualOffset"] = manualOffset;
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,11 +366,11 @@ namespace sourcemenu {
|
|||||||
|
|
||||||
if (running) { style::beginDisabled(); }
|
if (running) { style::beginDisabled(); }
|
||||||
ImGui::LeftLabel("Decimation");
|
ImGui::LeftLabel("Decimation");
|
||||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
ImGui::FillWidth();
|
||||||
if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) {
|
if (ImGui::Combo("##source_decim", &decimId, decimations.txt)) {
|
||||||
sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
|
sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["decimationPower"] = decimationPower;
|
core::configManager.conf["decimation"] = decimations.key(decimId);
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
if (running) { style::endDisabled(); }
|
if (running) { style::endDisabled(); }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -144,4 +145,4 @@ namespace SmGui {
|
|||||||
// Config configs
|
// Config configs
|
||||||
void ForceSyncForNext();
|
void ForceSyncForNext();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <gui/style.h>
|
#include <gui/style.h>
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <backend.h>
|
#include <backend.h>
|
||||||
|
#include <utils/hrfreq.h>
|
||||||
|
|
||||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
@@ -90,6 +91,7 @@ void FrequencySelect::moveCursorToDigit(int i) {
|
|||||||
|
|
||||||
void FrequencySelect::draw() {
|
void FrequencySelect::draw() {
|
||||||
auto window = ImGui::GetCurrentWindow();
|
auto window = ImGui::GetCurrentWindow();
|
||||||
|
auto io = ImGui::GetIO();
|
||||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||||
ImVec2 cursorPos = ImGui::GetCursorPos();
|
ImVec2 cursorPos = ImGui::GetCursorPos();
|
||||||
widgetPos.x += window->Pos.x + cursorPos.x;
|
widgetPos.x += window->Pos.x + cursorPos.x;
|
||||||
@@ -132,7 +134,7 @@ void FrequencySelect::draw() {
|
|||||||
ImVec2 mousePos = ImGui::GetMousePos();
|
ImVec2 mousePos = ImGui::GetMousePos();
|
||||||
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||||
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||||
int mw = ImGui::GetIO().MouseWheel;
|
int mw = io.MouseWheel;
|
||||||
bool onDigit = false;
|
bool onDigit = false;
|
||||||
bool hovered = false;
|
bool hovered = false;
|
||||||
|
|
||||||
@@ -174,7 +176,7 @@ void FrequencySelect::draw() {
|
|||||||
moveCursorToDigit(i + 1);
|
moveCursorToDigit(i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto chars = ImGui::GetIO().InputQueueCharacters;
|
auto chars = io.InputQueueCharacters;
|
||||||
|
|
||||||
// For each keyboard characters, type it
|
// For each keyboard characters, type it
|
||||||
for (int j = 0; j < chars.Size; j++) {
|
for (int j = 0; j < chars.Size; j++) {
|
||||||
@@ -194,6 +196,34 @@ void FrequencySelect::draw() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
digitHovered = hovered;
|
digitHovered = hovered;
|
||||||
|
|
||||||
|
if (isInArea(mousePos, digitTopMins[0], digitBottomMaxs[11])) {
|
||||||
|
bool shortcutKey = io.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
|
||||||
|
bool ctrlOnly = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
|
||||||
|
bool shiftOnly = (io.KeyMods == ImGuiKeyModFlags_Shift);
|
||||||
|
bool copy = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_C)) || (ctrlOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
|
||||||
|
bool paste = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_V)) || (shiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
|
||||||
|
if (copy) {
|
||||||
|
// Convert the freqency to a string
|
||||||
|
std::string freqStr = hrfreq::toString(frequency);
|
||||||
|
|
||||||
|
// Write it to the clipboard
|
||||||
|
ImGui::SetClipboardText(freqStr.c_str());
|
||||||
|
}
|
||||||
|
if (paste) {
|
||||||
|
// Attempt to parse the clipboard as a number
|
||||||
|
const char* clip = ImGui::GetClipboardText();
|
||||||
|
|
||||||
|
// If the clipboard is not empty, attempt to parse it
|
||||||
|
if (clip) {
|
||||||
|
double newFreq;
|
||||||
|
if (hrfreq::fromString(clip, newFreq)) {
|
||||||
|
setFrequency(abs(newFreq));
|
||||||
|
frequencyChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t freq = 0;
|
uint64_t freq = 0;
|
||||||
|
|||||||
@@ -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 {
|
namespace ImGui {
|
||||||
WaterFall::WaterFall() {
|
WaterFall::WaterFall() {
|
||||||
fftMin = -70.0;
|
fftMin = -70.0;
|
||||||
@@ -586,7 +613,7 @@ namespace ImGui {
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
|
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
|
||||||
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
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++) {
|
for (int j = 0; j < dataWidth; j++) {
|
||||||
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||||
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
|
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
|
||||||
@@ -689,6 +716,7 @@ namespace ImGui {
|
|||||||
|
|
||||||
void WaterFall::onResize() {
|
void WaterFall::onResize() {
|
||||||
std::lock_guard<std::recursive_mutex> lck(latestFFTMtx);
|
std::lock_guard<std::recursive_mutex> lck(latestFFTMtx);
|
||||||
|
std::lock_guard<std::mutex> lck2(smoothingBufMtx);
|
||||||
// return if widget is too small
|
// return if widget is too small
|
||||||
if (widgetSize.x < 100 || widgetSize.y < 100) {
|
if (widgetSize.x < 100 || widgetSize.y < 100) {
|
||||||
return;
|
return;
|
||||||
@@ -740,14 +768,23 @@ namespace ImGui {
|
|||||||
}
|
}
|
||||||
latestFFTHold = new float[dataWidth];
|
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) {
|
if (waterfallVisible) {
|
||||||
delete[] waterfallFb;
|
delete[] waterfallFb;
|
||||||
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
|
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
|
||||||
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
|
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
|
||||||
}
|
}
|
||||||
for (int i = 0; i < dataWidth; i++) {
|
for (int i = 0; i < dataWidth; i++) {
|
||||||
latestFFT[i] = -1000.0; // Hide everything
|
latestFFT[i] = -1000.0f; // Hide everything
|
||||||
latestFFTHold[i] = -1000.0;
|
latestFFTHold[i] = -1000.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale));
|
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);
|
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||||
|
|
||||||
if (waterfallVisible) {
|
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));
|
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
|
||||||
float pixel;
|
float pixel;
|
||||||
float dataRange = waterfallMax - waterfallMin;
|
float dataRange = waterfallMax - waterfallMin;
|
||||||
@@ -869,13 +906,29 @@ namespace ImGui {
|
|||||||
waterfallUpdate = true;
|
waterfallUpdate = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
|
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, rawFFTs, latestFFT);
|
||||||
fftLines = 1;
|
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) {
|
if (selectedVFO != "" && vfos.size() > 0) {
|
||||||
float dummy;
|
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
|
// If FFT hold is enabled, update it
|
||||||
@@ -1110,6 +1163,45 @@ namespace ImGui {
|
|||||||
fftHoldSpeed = speed;
|
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) {
|
float* WaterFall::acquireLatestFFT(int& width) {
|
||||||
latestFFTMtx.lock();
|
latestFFTMtx.lock();
|
||||||
if (!latestFFT) {
|
if (!latestFFT) {
|
||||||
|
|||||||
@@ -90,33 +90,6 @@ namespace ImGui {
|
|||||||
float* getFFTBuffer();
|
float* getFFTBuffer();
|
||||||
void pushFFT();
|
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 updatePallette(float colors[][3], int colorCount);
|
||||||
void updatePalletteFromArray(float* colors, int colorCount);
|
void updatePalletteFromArray(float* colors, int colorCount);
|
||||||
|
|
||||||
@@ -169,6 +142,12 @@ namespace ImGui {
|
|||||||
void setFFTHold(bool hold);
|
void setFFTHold(bool hold);
|
||||||
void setFFTHoldSpeed(float speed);
|
void setFFTHoldSpeed(float speed);
|
||||||
|
|
||||||
|
void setFFTSmoothing(bool enabled);
|
||||||
|
void setFFTSmoothingSpeed(float speed);
|
||||||
|
|
||||||
|
void setSNRSmoothing(bool enabled);
|
||||||
|
void setSNRSmoothingSpeed(float speed);
|
||||||
|
|
||||||
float* acquireLatestFFT(int& width);
|
float* acquireLatestFFT(int& width);
|
||||||
void releaseLatestFFT();
|
void releaseLatestFFT();
|
||||||
|
|
||||||
@@ -182,7 +161,7 @@ namespace ImGui {
|
|||||||
bool mouseInFFT = false;
|
bool mouseInFFT = false;
|
||||||
bool mouseInWaterfall = false;
|
bool mouseInWaterfall = false;
|
||||||
|
|
||||||
float selectedVFOSNR = NAN;
|
float selectedVFOSNR = 0.0f;
|
||||||
|
|
||||||
bool centerFrequencyLocked = false;
|
bool centerFrequencyLocked = false;
|
||||||
|
|
||||||
@@ -270,6 +249,7 @@ namespace ImGui {
|
|||||||
std::recursive_mutex buf_mtx;
|
std::recursive_mutex buf_mtx;
|
||||||
std::recursive_mutex latestFFTMtx;
|
std::recursive_mutex latestFFTMtx;
|
||||||
std::mutex texMtx;
|
std::mutex texMtx;
|
||||||
|
std::mutex smoothingBufMtx;
|
||||||
|
|
||||||
float vRange;
|
float vRange;
|
||||||
|
|
||||||
@@ -304,8 +284,9 @@ namespace ImGui {
|
|||||||
//std::vector<std::vector<float>> rawFFTs;
|
//std::vector<std::vector<float>> rawFFTs;
|
||||||
int rawFFTSize;
|
int rawFFTSize;
|
||||||
float* rawFFTs = NULL;
|
float* rawFFTs = NULL;
|
||||||
float* latestFFT;
|
float* latestFFT = NULL;
|
||||||
float* latestFFTHold;
|
float* latestFFTHold = NULL;
|
||||||
|
float* smoothingBuf = NULL;
|
||||||
int currentFFTLine = 0;
|
int currentFFTLine = 0;
|
||||||
int fftLines = 0;
|
int fftLines = 0;
|
||||||
|
|
||||||
@@ -325,6 +306,14 @@ namespace ImGui {
|
|||||||
bool fftHold = false;
|
bool fftHold = false;
|
||||||
float fftHoldSpeed = 0.3f;
|
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
|
// UI Select elements
|
||||||
bool fftResizeSelect = false;
|
bool fftResizeSelect = false;
|
||||||
bool freqScaleSelect = false;
|
bool freqScaleSelect = false;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ ModuleManager::Module_t ModuleManager::loadModule(std::string path) {
|
|||||||
#else
|
#else
|
||||||
mod.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
mod.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||||
if (mod.handle == NULL) {
|
if (mod.handle == NULL) {
|
||||||
flog::error("Couldn't load {0}.", path);
|
flog::error("Couldn't load {0}: {1}", path, dlerror());
|
||||||
mod.handle = NULL;
|
mod.handle = NULL;
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
@@ -183,4 +183,4 @@ void ModuleManager::doPostInitAll() {
|
|||||||
flog::info("Running post-init for {0}", name);
|
flog::info("Running post-init for {0}", name);
|
||||||
inst.instance->postInit();
|
inst.instance->postInit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public:
|
|||||||
|
|
||||||
class Instance {
|
class Instance {
|
||||||
public:
|
public:
|
||||||
|
virtual ~Instance() {}
|
||||||
virtual void postInit() = 0;
|
virtual void postInit() = 0;
|
||||||
virtual void enable() = 0;
|
virtual void enable() = 0;
|
||||||
virtual void disable() = 0;
|
virtual void disable() = 0;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <utils/flog.h>
|
#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) {
|
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()) {
|
if (interfaces.find(name) != interfaces.end()) {
|
||||||
flog::error("Tried creating module interface with an existing name: {0}", name);
|
flog::error("Tried creating module interface with an existing name: {0}", name);
|
||||||
return false;
|
return false;
|
||||||
@@ -16,7 +16,7 @@ bool ModuleComManager::registerInterface(std::string moduleName, std::string nam
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ModuleComManager::unregisterInterface(std::string name) {
|
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()) {
|
if (interfaces.find(name) == interfaces.end()) {
|
||||||
flog::error("Tried to erase module interface with unknown name: {0}", name);
|
flog::error("Tried to erase module interface with unknown name: {0}", name);
|
||||||
return false;
|
return false;
|
||||||
@@ -26,13 +26,13 @@ bool ModuleComManager::unregisterInterface(std::string name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ModuleComManager::interfaceExists(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; }
|
if (interfaces.find(name) == interfaces.end()) { return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ModuleComManager::getModuleName(std::string name) {
|
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()) {
|
if (interfaces.find(name) == interfaces.end()) {
|
||||||
flog::error("Tried to call unknown module interface: {0}", name);
|
flog::error("Tried to call unknown module interface: {0}", name);
|
||||||
return "";
|
return "";
|
||||||
@@ -41,7 +41,7 @@ std::string ModuleComManager::getModuleName(std::string name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ModuleComManager::callInterface(std::string name, int code, void* in, void* out) {
|
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()) {
|
if (interfaces.find(name) == interfaces.end()) {
|
||||||
flog::error("Tried to call unknown module interface: {0}", name);
|
flog::error("Tried to call unknown module interface: {0}", name);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ public:
|
|||||||
bool callInterface(std::string name, int code, void* in, void* out);
|
bool callInterface(std::string name, int code, void* in, void* out);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::mutex mtx;
|
std::recursive_mutex mtx;
|
||||||
std::map<std::string, ModuleComInterface> interfaces;
|
std::map<std::string, ModuleComInterface> interfaces;
|
||||||
};
|
};
|
||||||
@@ -146,7 +146,7 @@ namespace server {
|
|||||||
// Load sourceId from config
|
// Load sourceId from config
|
||||||
sourceId = 0;
|
sourceId = 0;
|
||||||
if (sourceList.keyExists(sourceName)) { sourceId = sourceList.keyId(sourceName); }
|
if (sourceList.keyExists(sourceName)) { sourceId = sourceList.keyId(sourceName); }
|
||||||
sigpath::sourceManager.select(sourceList[sourceId]);
|
sigpath::sourceManager.selectSource(sourceList[sourceId]);
|
||||||
|
|
||||||
// TODO: Use command line option
|
// TODO: Use command line option
|
||||||
std::string host = (std::string)core::args["addr"];
|
std::string host = (std::string)core::args["addr"];
|
||||||
@@ -230,7 +230,7 @@ namespace server {
|
|||||||
// Compress data if needed and fill out header fields
|
// Compress data if needed and fill out header fields
|
||||||
if (compression) {
|
if (compression) {
|
||||||
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND_COMPRESSED;
|
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 {
|
else {
|
||||||
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND;
|
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND;
|
||||||
@@ -280,7 +280,8 @@ namespace server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (cmd == COMMAND_START) {
|
else if (cmd == COMMAND_START) {
|
||||||
running = sigpath::sourceManager.start();
|
sigpath::sourceManager.start();
|
||||||
|
running = true;
|
||||||
}
|
}
|
||||||
else if (cmd == COMMAND_STOP) {
|
else if (cmd == COMMAND_STOP) {
|
||||||
sigpath::sourceManager.stop();
|
sigpath::sourceManager.stop();
|
||||||
@@ -308,14 +309,14 @@ namespace server {
|
|||||||
SmGui::FillWidth();
|
SmGui::FillWidth();
|
||||||
SmGui::ForceSync();
|
SmGui::ForceSync();
|
||||||
if (SmGui::Combo("##sdrpp_server_src_sel", &sourceId, sourceList.txt)) {
|
if (SmGui::Combo("##sdrpp_server_src_sel", &sourceId, sourceList.txt)) {
|
||||||
sigpath::sourceManager.select(sourceList[sourceId]);
|
sigpath::sourceManager.selectSource(sourceList[sourceId]);
|
||||||
core::configManager.acquire();
|
core::configManager.acquire();
|
||||||
core::configManager.conf["source"] = sourceList.key(sourceId);
|
core::configManager.conf["source"] = sourceList.key(sourceId);
|
||||||
core::configManager.release(true);
|
core::configManager.release(true);
|
||||||
}
|
}
|
||||||
if (running) { SmGui::EndDisabled(); }
|
if (running) { SmGui::EndDisabled(); }
|
||||||
|
|
||||||
sigpath::sourceManager.showMenu();
|
sigpath::sourceManager.showSelectedMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderUI(SmGui::DrawList* dl, std::string diffId, SmGui::DrawListElem diffValue) {
|
void renderUI(SmGui::DrawList* dl, std::string diffId, SmGui::DrawListElem diffValue) {
|
||||||
|
|||||||
@@ -1,186 +1,106 @@
|
|||||||
#include "source.h"
|
#include <server.h>
|
||||||
|
#include <signal_path/source.h>
|
||||||
#include <utils/flog.h>
|
#include <utils/flog.h>
|
||||||
|
#include <signal_path/signal_path.h>
|
||||||
|
#include <core.h>
|
||||||
|
|
||||||
void SourceManager::registerSource(const std::string& name, Source* source) {
|
SourceManager::SourceManager() {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
}
|
||||||
|
|
||||||
// Check arguments
|
void SourceManager::registerSource(std::string name, SourceHandler* handler) {
|
||||||
if (source || name.empty()) {
|
|
||||||
flog::error("Invalid argument to register source", name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that a source with that name doesn't already exist
|
|
||||||
if (sources.find(name) != sources.end()) {
|
if (sources.find(name) != sources.end()) {
|
||||||
flog::error("Tried to register source with existing name: {}", name);
|
flog::error("Tried to register new source with existing name: {0}", name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
sources[name] = handler;
|
||||||
// Add source to map
|
onSourceRegistered.emit(name);
|
||||||
sources[name] = source;
|
|
||||||
|
|
||||||
// Add source to lists
|
|
||||||
sourceNames.push_back(name);
|
|
||||||
onSourceRegistered(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::unregisterSource(const std::string& name) {
|
void SourceManager::unregisterSource(std::string name) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
|
|
||||||
// Check that a source with that name exists
|
|
||||||
if (sources.find(name) == sources.end()) {
|
if (sources.find(name) == sources.end()) {
|
||||||
flog::error("Tried to unregister a non-existent source: {}", name);
|
flog::error("Tried to unregister non existent source: {0}", name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
onSourceUnregister.emit(name);
|
||||||
// Notify event listeners of the imminent deletion
|
if (name == selectedName) {
|
||||||
onSourceUnregister(name);
|
if (selectedHandler != NULL) {
|
||||||
|
sources[selectedName]->deselectHandler(sources[selectedName]->ctx);
|
||||||
// Delete from lists
|
}
|
||||||
sourceNames.erase(std::find(sourceNames.begin(), sourceNames.end(), name));
|
sigpath::iqFrontEnd.setInput(&nullSource);
|
||||||
|
selectedHandler = NULL;
|
||||||
|
}
|
||||||
sources.erase(name);
|
sources.erase(name);
|
||||||
|
onSourceUnregistered.emit(name);
|
||||||
// Notify event listeners of the deletion
|
|
||||||
onSourceUnregistered(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string>& SourceManager::getSourceNames() {
|
std::vector<std::string> SourceManager::getSourceNames() {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
std::vector<std::string> names;
|
||||||
return sourceNames;
|
for (auto const& [name, src] : sources) { names.push_back(name); }
|
||||||
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::select(const std::string& name) {
|
void SourceManager::selectSource(std::string name) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
|
|
||||||
// make sure that source isn't currently selected
|
|
||||||
if (selectedSourceName == name) { return; }
|
|
||||||
|
|
||||||
// Deselect current source
|
|
||||||
deselect();
|
|
||||||
|
|
||||||
// Check that a source with that name exists
|
|
||||||
if (sources.find(name) == sources.end()) {
|
if (sources.find(name) == sources.end()) {
|
||||||
flog::error("Tried to select a non-existent source: {}", name);
|
flog::error("Tried to select non existent source: {0}", name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (selectedHandler != NULL) {
|
||||||
// Select the source
|
sources[selectedName]->deselectHandler(sources[selectedName]->ctx);
|
||||||
selectedSourceName = name;
|
}
|
||||||
selectedSource = sources[name];
|
selectedHandler = sources[name];
|
||||||
|
selectedHandler->selectHandler(selectedHandler->ctx);
|
||||||
// Call the selected source
|
selectedName = name;
|
||||||
selectedSource->select();
|
if (core::args["server"].b()) {
|
||||||
|
server::setInput(selectedHandler->stream);
|
||||||
// Retune to make sure the source has the latest frequency
|
}
|
||||||
tune(frequency);
|
else {
|
||||||
|
sigpath::iqFrontEnd.setInput(selectedHandler->stream);
|
||||||
|
}
|
||||||
|
// Set server input here
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& SourceManager::getSelected() {
|
void SourceManager::showSelectedMenu() {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
if (selectedHandler == NULL) {
|
||||||
return selectedSourceName;
|
return;
|
||||||
|
}
|
||||||
|
selectedHandler->menuHandler(selectedHandler->ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SourceManager::start() {
|
void SourceManager::start() {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
if (selectedHandler == NULL) {
|
||||||
|
return;
|
||||||
// Check if not already running
|
}
|
||||||
if (running) { return true; }
|
selectedHandler->startHandler(selectedHandler->ctx);
|
||||||
|
|
||||||
// Call source if selected and save if started
|
|
||||||
running = (!selectedSource) ? false : selectedSource->start();
|
|
||||||
|
|
||||||
return running;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::stop() {
|
void SourceManager::stop() {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
if (selectedHandler == NULL) {
|
||||||
|
return;
|
||||||
// Check if running
|
}
|
||||||
if (!running) { return; }
|
selectedHandler->stopHandler(selectedHandler->ctx);
|
||||||
|
|
||||||
// Call source if selected and save state
|
|
||||||
if (selectedSource) { selectedSource->stop(); }
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SourceManager::isRunning() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
return running;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::tune(double freq) {
|
void SourceManager::tune(double freq) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
if (selectedHandler == NULL) {
|
||||||
|
return;
|
||||||
// Save frequency
|
|
||||||
frequency = freq;
|
|
||||||
|
|
||||||
// Call source if selected
|
|
||||||
if (selectedSource) {
|
|
||||||
selectedSource->tune(((mode == TUNING_MODE_NORMAL) ? freq : ifFrequency) + offset);
|
|
||||||
}
|
}
|
||||||
|
// TODO: No need to always retune the hardware in Panadapter mode
|
||||||
|
selectedHandler->tuneHandler(abs(((tuneMode == TuningMode::NORMAL) ? (freq + tuneOffset) : ifFreq)), selectedHandler->ctx);
|
||||||
|
onRetune.emit(freq + tuneOffset);
|
||||||
|
currentFreq = freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::showMenu() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
|
|
||||||
// Call source if selected
|
|
||||||
if (selectedSource) { selectedSource->showMenu(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
double SourceManager::getSamplerate() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
return samplerate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========== TODO: These functions should not happen in this class ===========
|
|
||||||
|
|
||||||
void SourceManager::setTuningOffset(double offset) {
|
void SourceManager::setTuningOffset(double offset) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
tuneOffset = offset;
|
||||||
|
tune(currentFreq);
|
||||||
// Update offset
|
|
||||||
this->offset = offset;
|
|
||||||
|
|
||||||
// Retune to take affect
|
|
||||||
tune(frequency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::setTuningMode(TuningMode mode) {
|
void SourceManager::setTuningMode(TuningMode mode) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
tuneMode = mode;
|
||||||
|
tune(currentFreq);
|
||||||
// Update mode
|
|
||||||
this->mode = mode;
|
|
||||||
|
|
||||||
// Retune to take affect
|
|
||||||
tune(frequency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceManager::setPanadpterIF(double freq) {
|
void SourceManager::setPanadapterIF(double freq) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
ifFreq = freq;
|
||||||
|
tune(currentFreq);
|
||||||
// Update offset
|
|
||||||
ifFrequency = freq;
|
|
||||||
|
|
||||||
// Return to take affect if in panadapter mode
|
|
||||||
if (mode == TUNING_MODE_PANADAPTER) { tune(frequency); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
void SourceManager::deselect() {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
|
|
||||||
// Call source if selected
|
|
||||||
if (selectedSource) { selectedSource->deselect(); }
|
|
||||||
|
|
||||||
// Mark as deselected
|
|
||||||
selectedSourceName.clear();
|
|
||||||
selectedSource = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SourceManager::setSamplerate(double samplerate) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
|
||||||
|
|
||||||
// Save samplerate and emit event
|
|
||||||
this->samplerate = samplerate;
|
|
||||||
onSamplerateChanged(samplerate);
|
|
||||||
}
|
}
|
||||||
@@ -1,153 +1,56 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
|
||||||
#include <dsp/types.h>
|
|
||||||
#include <dsp/stream.h>
|
#include <dsp/stream.h>
|
||||||
|
#include <dsp/types.h>
|
||||||
#include <utils/event.h>
|
#include <utils/event.h>
|
||||||
|
|
||||||
enum TuningMode {
|
|
||||||
TUNING_MODE_NORMAL,
|
|
||||||
TUNING_MODE_PANADAPTER
|
|
||||||
};
|
|
||||||
|
|
||||||
class Source;
|
|
||||||
|
|
||||||
class SourceManager {
|
class SourceManager {
|
||||||
friend Source;
|
|
||||||
public:
|
public:
|
||||||
/**
|
SourceManager();
|
||||||
* Register a source.
|
|
||||||
* @param name Name of the source.
|
|
||||||
* @param source Pointer to the source instance.
|
|
||||||
*/
|
|
||||||
void registerSource(const std::string& name, Source* source);
|
|
||||||
|
|
||||||
/**
|
struct SourceHandler {
|
||||||
* Unregister a source.
|
dsp::stream<dsp::complex_t>* stream;
|
||||||
* @param name Name of the source.
|
void (*menuHandler)(void* ctx);
|
||||||
*/
|
void (*selectHandler)(void* ctx);
|
||||||
void unregisterSource(const std::string& name);
|
void (*deselectHandler)(void* ctx);
|
||||||
|
void (*startHandler)(void* ctx);
|
||||||
|
void (*stopHandler)(void* ctx);
|
||||||
|
void (*tuneHandler)(double freq, void* ctx);
|
||||||
|
void* ctx;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
enum TuningMode {
|
||||||
* Get a list of source names.
|
NORMAL,
|
||||||
* @return List of source names.
|
PANADAPTER
|
||||||
*/
|
};
|
||||||
const std::vector<std::string>& getSourceNames();
|
|
||||||
|
|
||||||
/**
|
void registerSource(std::string name, SourceHandler* handler);
|
||||||
* Select a source.
|
void unregisterSource(std::string name);
|
||||||
* @param name Name of the source.
|
void selectSource(std::string name);
|
||||||
*/
|
void showSelectedMenu();
|
||||||
void select(const std::string& name);
|
void start();
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the name of the currently selected source.
|
|
||||||
* @return Name of the source or empty if no source is selected.
|
|
||||||
*/
|
|
||||||
const std::string& getSelected();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the radio.
|
|
||||||
* @return True if the radio started successfully, false if not.
|
|
||||||
*/
|
|
||||||
bool start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the radio.
|
|
||||||
*/
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the radio is running.
|
|
||||||
* @return True if the radio is running, false if not.
|
|
||||||
*/
|
|
||||||
bool isRunning();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tune the radio.
|
|
||||||
* @param freq Frequency in Hz.
|
|
||||||
*/
|
|
||||||
void tune(double freq);
|
void tune(double freq);
|
||||||
|
|
||||||
/**
|
|
||||||
* Tune the radio.
|
|
||||||
* @param freq Frequency to tune the radio to.
|
|
||||||
*/
|
|
||||||
void showMenu();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current samplerate of the radio.
|
|
||||||
* @return Samplerate in Hz.
|
|
||||||
*/
|
|
||||||
double getSamplerate();
|
|
||||||
|
|
||||||
// =========== TODO: These functions should not happen in this class ===========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set offset to add to the tuned frequency.
|
|
||||||
* @param offset Offset in Hz.
|
|
||||||
*/
|
|
||||||
void setTuningOffset(double offset);
|
void setTuningOffset(double offset);
|
||||||
|
|
||||||
/**
|
|
||||||
* Set tuning mode.
|
|
||||||
* @param mode Tuning mode.
|
|
||||||
*/
|
|
||||||
void setTuningMode(TuningMode mode);
|
void setTuningMode(TuningMode mode);
|
||||||
|
void setPanadapterIF(double freq);
|
||||||
|
|
||||||
/**
|
std::vector<std::string> getSourceNames();
|
||||||
* Set panadapter mode IF frequency.
|
|
||||||
* @param freq IF frequency in Hz.
|
|
||||||
*/
|
|
||||||
void setPanadpterIF(double freq);
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
// Emitted after a new source has been registered.
|
|
||||||
Event<std::string> onSourceRegistered;
|
Event<std::string> onSourceRegistered;
|
||||||
|
|
||||||
// Emitted when a source is about to be unregistered.
|
|
||||||
Event<std::string> onSourceUnregister;
|
Event<std::string> onSourceUnregister;
|
||||||
|
|
||||||
// Emitted after a source has been unregistered.
|
|
||||||
Event<std::string> onSourceUnregistered;
|
Event<std::string> onSourceUnregistered;
|
||||||
|
|
||||||
// Emitted when the samplerate of the incoming IQ has changed.
|
|
||||||
Event<double> onSamplerateChanged;
|
|
||||||
|
|
||||||
// Emitted when the source manager is instructed to tune the radio.
|
|
||||||
Event<double> onRetune;
|
Event<double> onRetune;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void deselect();
|
std::map<std::string, SourceHandler*> sources;
|
||||||
void setSamplerate(double samplerate);
|
std::string selectedName;
|
||||||
|
SourceHandler* selectedHandler = NULL;
|
||||||
std::vector<std::string> sourceNames;
|
double tuneOffset;
|
||||||
std::map<std::string, Source*> sources;
|
double currentFreq;
|
||||||
|
double ifFreq = 0.0;
|
||||||
std::string selectedSourceName = "";
|
TuningMode tuneMode = TuningMode::NORMAL;
|
||||||
Source* selectedSource = NULL;
|
dsp::stream<dsp::complex_t> nullSource;
|
||||||
|
|
||||||
bool running = false;
|
|
||||||
double samplerate = 1e6;
|
|
||||||
double frequency = 100e6;
|
|
||||||
double offset = 0;
|
|
||||||
double ifFrequency = 8.830e6;
|
|
||||||
TuningMode mode = TUNING_MODE_NORMAL;
|
|
||||||
|
|
||||||
std::recursive_mutex mtx;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Source {
|
|
||||||
public:
|
|
||||||
virtual void showMenu() {}
|
|
||||||
virtual void select() = 0;
|
|
||||||
virtual void deselect() {}
|
|
||||||
virtual bool start() = 0;
|
|
||||||
virtual void stop() = 0;
|
|
||||||
virtual void tune(double freq) {}
|
|
||||||
|
|
||||||
dsp::stream<dsp::complex_t> stream;
|
|
||||||
};
|
};
|
||||||
@@ -1,51 +1,43 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <functional>
|
#include <vector>
|
||||||
#include <stdexcept>
|
#include <utils/flog.h>
|
||||||
#include <mutex>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
typedef int HandlerID;
|
template <class T>
|
||||||
|
struct EventHandler {
|
||||||
|
EventHandler() {}
|
||||||
|
EventHandler(void (*handler)(T, void*), void* ctx) {
|
||||||
|
this->handler = handler;
|
||||||
|
this->ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
void (*handler)(T, void*);
|
||||||
|
void* ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
class Event {
|
class Event {
|
||||||
using Handler = std::function<void(Args...)>;
|
|
||||||
public:
|
public:
|
||||||
HandlerID bind(Handler handler) {
|
Event() {}
|
||||||
std::lock_guard<std::mutex> lck(mtx);
|
~Event() {}
|
||||||
HandlerID id = genID();
|
|
||||||
handlers[id] = handler;
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename MHandler, class T>
|
void emit(T value) {
|
||||||
HandlerID bind(MHandler handler, T* ctx) {
|
for (auto const& handler : handlers) {
|
||||||
return bind([=](Args... args){
|
handler->handler(value, handler->ctx);
|
||||||
(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) {
|
void bindHandler(EventHandler<T>* handler) {
|
||||||
std::lock_guard<std::mutex> lck(mtx);
|
handlers.push_back(handler);
|
||||||
for (const auto& [desc, handler] : handlers) {
|
}
|
||||||
handler(args...);
|
|
||||||
|
void unbindHandler(EventHandler<T>* handler) {
|
||||||
|
if (std::find(handlers.begin(), handlers.end(), handler) == handlers.end()) {
|
||||||
|
flog::error("Tried to remove a non-existent event handler");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HandlerID genID() {
|
std::vector<EventHandler<T>*> handlers;
|
||||||
int id;
|
|
||||||
for (id = 1; handlers.find(id) != handlers.end(); id++);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<HandlerID, Handler> handlers;
|
|
||||||
std::mutex mtx;
|
|
||||||
};
|
};
|
||||||
@@ -169,7 +169,7 @@ namespace flog {
|
|||||||
fprintf(outStream, "] %s\n", out.c_str());
|
fprintf(outStream, "] %s\n", out.c_str());
|
||||||
#elif defined(__ANDROID__)
|
#elif defined(__ANDROID__)
|
||||||
// Print format string
|
// Print format string
|
||||||
__android_log_buf_print(LOG_ID_DEFAULT, TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n",
|
__android_log_print(TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n",
|
||||||
nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str());
|
nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str());
|
||||||
#else
|
#else
|
||||||
// Print format string
|
// Print format string
|
||||||
|
|||||||
120
core/src/utils/hrfreq.cpp
Normal file
120
core/src/utils/hrfreq.cpp
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#include "hrfreq.h"
|
||||||
|
#include <utils/flog.h>
|
||||||
|
|
||||||
|
namespace hrfreq {
|
||||||
|
|
||||||
|
|
||||||
|
std::string toString(double freq) {
|
||||||
|
// Determine the scale
|
||||||
|
int maxDecimals = 0;
|
||||||
|
const char* suffix = "Hz";
|
||||||
|
if (freq >= 1e9) {
|
||||||
|
freq /= 1e9;
|
||||||
|
maxDecimals = 9;
|
||||||
|
suffix = "GHz";
|
||||||
|
}
|
||||||
|
else if (freq >= 1e6) {
|
||||||
|
freq /= 1e6;
|
||||||
|
maxDecimals = 6;
|
||||||
|
suffix = "MHz";
|
||||||
|
}
|
||||||
|
else if (freq >= 1e3) {
|
||||||
|
freq /= 1e3;
|
||||||
|
maxDecimals = 3;
|
||||||
|
suffix = "KHz";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to string (TODO: Not sure if limiting the decimals rounds)
|
||||||
|
char numBuf[128];
|
||||||
|
int numLen = sprintf(numBuf, "%0.*lf", maxDecimals, freq);
|
||||||
|
|
||||||
|
// If there is a decimal point, remove the useless zeros
|
||||||
|
if (maxDecimals) {
|
||||||
|
for (int i = numLen-1; i >= 0; i--) {
|
||||||
|
bool dot = (numBuf[i] == '.');
|
||||||
|
if (numBuf[i] != '0' && !dot) { break; }
|
||||||
|
numBuf[i] = 0;
|
||||||
|
if (dot) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concat the suffix
|
||||||
|
char finalBuf[128];
|
||||||
|
sprintf(finalBuf, "%s%s", numBuf, suffix);
|
||||||
|
|
||||||
|
// Return the final string
|
||||||
|
return finalBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNumeric(char c) {
|
||||||
|
return std::isdigit(c) || c == '+' || c == '-' || c == '.' || c == ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fromString(const std::string& str, double& freq) {
|
||||||
|
// Skip non-numeric characters
|
||||||
|
int i = 0;
|
||||||
|
char c;
|
||||||
|
for (; i < str.size(); i++) {
|
||||||
|
if (isNumeric(str[i])) { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the numeric part
|
||||||
|
std::string numeric;
|
||||||
|
for (; i < str.size(); i++) {
|
||||||
|
// Get the character
|
||||||
|
c = str[i];
|
||||||
|
|
||||||
|
// If it's a letter, stop
|
||||||
|
if (std::isalpha(c)) { break; }
|
||||||
|
|
||||||
|
// If isn't numeric, skip it
|
||||||
|
if (!isNumeric(c)) { continue; }
|
||||||
|
|
||||||
|
// If it's a comma, skip it for now. This enforces a dot as a decimal point
|
||||||
|
if (c == ',') { continue; }
|
||||||
|
|
||||||
|
// Add the character to the numeric string
|
||||||
|
numeric += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the numeric part
|
||||||
|
double num;
|
||||||
|
try {
|
||||||
|
num = std::stod(numeric);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
flog::error("Failed to parse numeric part: '{}'", numeric);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no more text is available, assume the numeric part gives a frequency in Hz
|
||||||
|
if (i == str.size()) {
|
||||||
|
flog::warn("No unit given, assuming it's Hz");
|
||||||
|
freq = num;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale the numeric value depending on the first scale character
|
||||||
|
char scale = std::toupper(str[i]);
|
||||||
|
switch (scale) {
|
||||||
|
case 'G':
|
||||||
|
num *= 1e9;
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
num *= 1e6;
|
||||||
|
break;
|
||||||
|
case 'K':
|
||||||
|
num *= 1e3;
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
flog::warn("Unknown frequency scale: '{}'", scale);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the frequency
|
||||||
|
freq = num;
|
||||||
|
return true; // TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
19
core/src/utils/hrfreq.h
Normal file
19
core/src/utils/hrfreq.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace hrfreq {
|
||||||
|
/**
|
||||||
|
* Convert a frequency to a human-readable string.
|
||||||
|
* @param freq Frequency in Hz.
|
||||||
|
* @return Human-readable representation of the frequency.
|
||||||
|
*/
|
||||||
|
std::string toString(double freq);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a human-readable representation of a frequency to a frequency value.
|
||||||
|
* @param str String containing the human-readable frequency.
|
||||||
|
* @param freq Value to write the decoded frequency to.
|
||||||
|
* @return True on success, false otherwise.
|
||||||
|
*/
|
||||||
|
bool fromString(const std::string& str, double& freq);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK)
|
#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK)
|
||||||
@@ -85,14 +86,14 @@ namespace net {
|
|||||||
addr.sin_port = htons(port);
|
addr.sin_port = htons(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Address::getIPStr() {
|
std::string Address::getIPStr() const {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
IP_t ip = getIP();
|
IP_t ip = getIP();
|
||||||
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
|
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
IP_t Address::getIP() {
|
IP_t Address::getIP() const {
|
||||||
return htonl(addr.sin_addr.s_addr);
|
return htonl(addr.sin_addr.s_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ namespace net {
|
|||||||
addr.sin_addr.s_addr = htonl(ip);
|
addr.sin_addr.s_addr = htonl(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Address::getPort() {
|
int Address::getPort() const {
|
||||||
return htons(addr.sin_port);
|
return htons(addr.sin_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +138,16 @@ namespace net {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int Socket::send(const uint8_t* data, size_t len, const Address* dest) {
|
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) {
|
int Socket::sendstr(const std::string& str, const Address* dest) {
|
||||||
@@ -159,8 +169,8 @@ namespace net {
|
|||||||
|
|
||||||
// Set timeout
|
// Set timeout
|
||||||
timeval tv;
|
timeval tv;
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = timeout / 1000;
|
||||||
tv.tv_usec = timeout * 1000;
|
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
|
||||||
|
|
||||||
// Wait for data
|
// Wait for data
|
||||||
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
|
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
|
||||||
@@ -224,8 +234,8 @@ namespace net {
|
|||||||
|
|
||||||
// Define timeout
|
// Define timeout
|
||||||
timeval tv;
|
timeval tv;
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = timeout / 1000;
|
||||||
tv.tv_usec = timeout * 1000;
|
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
|
||||||
|
|
||||||
// Wait for data or error
|
// Wait for data or error
|
||||||
if (timeout != NONBLOCKING) {
|
if (timeout != NONBLOCKING) {
|
||||||
@@ -288,6 +298,7 @@ namespace net {
|
|||||||
|
|
||||||
// Save data
|
// Save data
|
||||||
for (auto iface = addresses; iface; iface = iface->ifa_next) {
|
for (auto iface = addresses; iface; iface = iface->ifa_next) {
|
||||||
|
if (!iface->ifa_addr || !iface->ifa_netmask) { continue; }
|
||||||
if (iface->ifa_addr->sa_family != AF_INET) { continue; }
|
if (iface->ifa_addr->sa_family != AF_INET) { continue; }
|
||||||
InterfaceInfo info;
|
InterfaceInfo info;
|
||||||
info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]);
|
info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]);
|
||||||
@@ -373,13 +384,25 @@ namespace net {
|
|||||||
return connect(Address(host, port));
|
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 library if needed
|
||||||
init();
|
init();
|
||||||
|
|
||||||
// Create socket
|
// Create socket
|
||||||
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
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
|
// Bind socket to local port
|
||||||
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
|
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
|
||||||
closeSocket(s);
|
closeSocket(s);
|
||||||
@@ -391,15 +414,15 @@ namespace net {
|
|||||||
return std::make_shared<Socket>(s, &raddr);
|
return std::make_shared<Socket>(s, &raddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
return openudp(Address(rhost, rport), laddr);
|
return openudp(Address(rhost, rport), laddr, allowBroadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) {
|
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) {
|
||||||
return openudp(raddr, Address(lhost, lport));
|
return openudp(raddr, Address(lhost, lport), allowBroadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int 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));
|
return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -66,13 +67,13 @@ namespace net {
|
|||||||
* Get the IP address.
|
* Get the IP address.
|
||||||
* @return IP address in standard string format.
|
* @return IP address in standard string format.
|
||||||
*/
|
*/
|
||||||
std::string getIPStr();
|
std::string getIPStr() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the IP address.
|
* Get the IP address.
|
||||||
* @return IP address in host byte order.
|
* @return IP address in host byte order.
|
||||||
*/
|
*/
|
||||||
IP_t getIP();
|
IP_t getIP() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the IP address.
|
* Set the IP address.
|
||||||
@@ -84,7 +85,7 @@ namespace net {
|
|||||||
* Get the TCP/UDP port.
|
* Get the TCP/UDP port.
|
||||||
* @return TCP/UDP port number.
|
* @return TCP/UDP port number.
|
||||||
*/
|
*/
|
||||||
int getPort();
|
int getPort() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the TCP/UDP port.
|
* Set the TCP/UDP port.
|
||||||
@@ -245,37 +246,37 @@ namespace net {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create UDP socket.
|
* 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.
|
* @param laddr Local address to bind the socket to.
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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.
|
* 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 rport Remote port.
|
||||||
* @param laddr Local address to bind the socket to.
|
* @param laddr Local address to bind the socket to.
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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.
|
* 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 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).
|
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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.
|
* 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 rport Remote port.
|
||||||
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
|
* @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).
|
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <utils/networking.h>
|
#include <utils/networking.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <utils/flog.h>
|
#include <utils/flog.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace net {
|
namespace net {
|
||||||
|
|
||||||
@@ -319,7 +320,7 @@ namespace net {
|
|||||||
}
|
}
|
||||||
entry.handler(std::move(client), entry.ctx);
|
entry.handler(std::move(client), entry.ctx);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
listening = false;
|
listening = false;
|
||||||
return;
|
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;
|
||||||
|
};
|
||||||
@@ -8,7 +8,7 @@ class OptionList {
|
|||||||
public:
|
public:
|
||||||
OptionList() { updateText(); }
|
OptionList() { updateText(); }
|
||||||
|
|
||||||
void define(K key, std::string name, T value) {
|
void define(const K& key, const std::string& name, const T& value) {
|
||||||
if (keyExists(key)) { throw std::runtime_error("Key already exists"); }
|
if (keyExists(key)) { throw std::runtime_error("Key already exists"); }
|
||||||
if (nameExists(name)) { throw std::runtime_error("Name already exists"); }
|
if (nameExists(name)) { throw std::runtime_error("Name already exists"); }
|
||||||
if (valueExists(value)) { throw std::runtime_error("Value already exists"); }
|
if (valueExists(value)) { throw std::runtime_error("Value already exists"); }
|
||||||
@@ -18,27 +18,27 @@ public:
|
|||||||
updateText();
|
updateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void define(std::string name, T value) {
|
void define(const std::string& name, const T& value) {
|
||||||
define(name, name, value);
|
define(name, name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void undefined(int id) {
|
void undefine(int id) {
|
||||||
keys.erase(keys.begin() + id);
|
keys.erase(keys.begin() + id);
|
||||||
names.erase(names.begin() + id);
|
names.erase(names.begin() + id);
|
||||||
values.erase(values.begin() + id);
|
values.erase(values.begin() + id);
|
||||||
updateText();
|
updateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void undefineKey(K key) {
|
void undefineKey(const K& key) {
|
||||||
undefined(keyId(key));
|
undefine(keyId(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
void undefineName(std::string name) {
|
void undefineName(const std::string& name) {
|
||||||
undefined(nameId(name));
|
undefine(nameId(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
void undefineValue(T value) {
|
void undefineValue(const T& value) {
|
||||||
undefined(valueId(value));
|
undefine(valueId(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
@@ -48,61 +48,61 @@ public:
|
|||||||
updateText();
|
updateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
int size() {
|
int size() const {
|
||||||
return keys.size();
|
return keys.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() {
|
bool empty() const {
|
||||||
return keys.empty();
|
return keys.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyExists(K key) {
|
bool keyExists(const K& key) const {
|
||||||
if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; }
|
if (std::find(keys.begin(), keys.end(), key) != keys.end()) { return true; }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool nameExists(std::string name) {
|
bool nameExists(const std::string& name) const {
|
||||||
if (std::find(names.begin(), names.end(), name) != names.end()) { return true; }
|
if (std::find(names.begin(), names.end(), name) != names.end()) { return true; }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool valueExists(T value) {
|
bool valueExists(const T& value) const {
|
||||||
if (std::find(values.begin(), values.end(), value) != values.end()) { return true; }
|
if (std::find(values.begin(), values.end(), value) != values.end()) { return true; }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int keyId(K key) {
|
int keyId(const K& key) const {
|
||||||
auto it = std::find(keys.begin(), keys.end(), key);
|
auto it = std::find(keys.begin(), keys.end(), key);
|
||||||
if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); }
|
if (it == keys.end()) { throw std::runtime_error("Key doesn't exists"); }
|
||||||
return std::distance(keys.begin(), it);
|
return std::distance(keys.begin(), it);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nameId(std::string name) {
|
int nameId(const std::string& name) const {
|
||||||
auto it = std::find(names.begin(), names.end(), name);
|
auto it = std::find(names.begin(), names.end(), name);
|
||||||
if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); }
|
if (it == names.end()) { throw std::runtime_error("Name doesn't exists"); }
|
||||||
return std::distance(names.begin(), it);
|
return std::distance(names.begin(), it);
|
||||||
}
|
}
|
||||||
|
|
||||||
int valueId(T value) {
|
int valueId(const T& value) const {
|
||||||
auto it = std::find(values.begin(), values.end(), value);
|
auto it = std::find(values.begin(), values.end(), value);
|
||||||
if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); }
|
if (it == values.end()) { throw std::runtime_error("Value doesn't exists"); }
|
||||||
return std::distance(values.begin(), it);
|
return std::distance(values.begin(), it);
|
||||||
}
|
}
|
||||||
|
|
||||||
K key(int id) {
|
inline const K& key(int id) const {
|
||||||
return keys[id];
|
return keys[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name(int id) {
|
inline const std::string& name(int id) const {
|
||||||
return names[id];
|
return names[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
T value(int id) {
|
inline const T& value(int id) const {
|
||||||
return values[id];
|
return values[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
T operator[](int& id) {
|
inline const T& operator[](int& id) const {
|
||||||
return value(id);
|
return values[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* txt = NULL;
|
const char* txt = NULL;
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ namespace net::http {
|
|||||||
|
|
||||||
// Deserialize
|
// Deserialize
|
||||||
req.deserialize(respData);
|
req.deserialize(respData);
|
||||||
|
return 0; // Might wanna return size instead
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::sendResponseHeader(ResponseHeader& resp) {
|
int Client::sendResponseHeader(ResponseHeader& resp) {
|
||||||
@@ -274,6 +275,7 @@ namespace net::http {
|
|||||||
|
|
||||||
// Deserialize
|
// Deserialize
|
||||||
resp.deserialize(respData);
|
resp.deserialize(respData);
|
||||||
|
return 0; // Might wanna return size instead
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::sendChunkHeader(ChunkHeader& chdr) {
|
int Client::sendChunkHeader(ChunkHeader& chdr) {
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ namespace riff {
|
|||||||
const char* LIST_SIGNATURE = "LIST";
|
const char* LIST_SIGNATURE = "LIST";
|
||||||
const size_t RIFF_LABEL_SIZE = 4;
|
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]) {
|
bool Writer::open(std::string path, const char form[4]) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
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.write((char*)&desc.hdr.size, sizeof(desc.hdr.size));
|
||||||
file.seekp(pos);
|
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()) {
|
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 {
|
class Writer {
|
||||||
public:
|
public:
|
||||||
|
Writer() {}
|
||||||
|
// Writer(const Writer&& b);
|
||||||
|
~Writer();
|
||||||
|
|
||||||
bool open(std::string path, const char form[4]);
|
bool open(std::string path, const char form[4]);
|
||||||
bool isOpen();
|
bool isOpen();
|
||||||
void close();
|
void close();
|
||||||
@@ -40,4 +44,23 @@ namespace riff {
|
|||||||
std::ofstream file;
|
std::ofstream file;
|
||||||
std::stack<ChunkDesc> chunks;
|
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
|
#pragma once
|
||||||
|
|
||||||
#define VERSION_STR "1.1.0"
|
#define VERSION_STR "1.3.0"
|
||||||
66
decoder_modules/atv_decoder/src/amplitude.h
Normal file
66
decoder_modules/atv_decoder/src/amplitude.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <dsp/processor.h>
|
||||||
|
#include <dsp/math/fast_atan2.h>
|
||||||
|
#include <dsp/math/hz_to_rads.h>
|
||||||
|
#include <dsp/math/normalize_phase.h>
|
||||||
|
|
||||||
|
namespace dsp::demod {
|
||||||
|
class Amplitude : public Processor<complex_t, float> {
|
||||||
|
using base_type = Processor<complex_t, float>;
|
||||||
|
public:
|
||||||
|
Amplitude() {}
|
||||||
|
|
||||||
|
Amplitude(stream<complex_t>* in, double deviation) { init(in, deviation); }
|
||||||
|
|
||||||
|
Amplitude(stream<complex_t>* in, double deviation, double samplerate) { init(in, deviation, samplerate); }
|
||||||
|
|
||||||
|
|
||||||
|
virtual void init(stream<complex_t>* in, double deviation) {
|
||||||
|
_invDeviation = 1.0 / deviation;
|
||||||
|
base_type::init(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void init(stream<complex_t>* in, double deviation, double samplerate) {
|
||||||
|
init(in, math::hzToRads(deviation, samplerate));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDeviation(double deviation) {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
|
_invDeviation = 1.0 / deviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDeviation(double deviation, double samplerate) {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
|
_invDeviation = 1.0 / math::hzToRads(deviation, samplerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int process(int count, complex_t* in, float* out) {
|
||||||
|
volk_32fc_magnitude_32f(out, (lv_32fc_t*)in, count);
|
||||||
|
volk_32f_s32f_multiply_32f(out, out, -1.0f, count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
assert(base_type::_block_init);
|
||||||
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
|
phase = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run() {
|
||||||
|
int count = base_type::_in->read();
|
||||||
|
if (count < 0) { return -1; }
|
||||||
|
|
||||||
|
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||||
|
|
||||||
|
base_type::_in->flush();
|
||||||
|
if (!base_type::out.swap(count)) { return -1; }
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float _invDeviation;
|
||||||
|
float phase = 0.0f;
|
||||||
|
};
|
||||||
|
}
|
||||||
176
decoder_modules/atv_decoder/src/burst_blanking.txt
Normal file
176
decoder_modules/atv_decoder/src/burst_blanking.txt
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
617 + 6 -> I
|
||||||
|
|
||||||
|
304 + 6 -> II
|
||||||
|
|
||||||
|
616 + 6 -> III
|
||||||
|
|
||||||
|
305 + 6 -> IV
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
304
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
|
||||||
|
616
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
313
|
||||||
|
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
0
|
||||||
|
|
||||||
|
304
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
616
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
313
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
0
|
||||||
|
304
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
616
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
313
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
0
|
||||||
|
304
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
616
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
313
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
|
0
|
||||||
|
304
|
||||||
|
305
|
||||||
|
306
|
||||||
|
307
|
||||||
|
308
|
||||||
|
309
|
||||||
|
310
|
||||||
|
311
|
||||||
|
312
|
||||||
|
616
|
||||||
|
617
|
||||||
|
618
|
||||||
|
619
|
||||||
|
620
|
||||||
|
621
|
||||||
|
622
|
||||||
|
623
|
||||||
|
624
|
||||||
131
decoder_modules/atv_decoder/src/filters.h
Normal file
131
decoder_modules/atv_decoder/src/filters.h
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <dsp/types.h>
|
||||||
|
|
||||||
|
const dsp::complex_t CHROMA_BANDPASS[123] = {
|
||||||
|
{ -0.000007675039564594f, -0.000017362992335168f },
|
||||||
|
{ 0.000050180791439308f, -0.000005054021864311f },
|
||||||
|
{ -0.000022529111707761f, 0.000102942513429095f },
|
||||||
|
{ -0.000157609487484146f, -0.000092618697641464f },
|
||||||
|
{ 0.000205649042029007f, -0.000181710515677257f },
|
||||||
|
{ 0.000143445458895462f, 0.000331994546004200f },
|
||||||
|
{ -0.000414693079508517f, 0.000038265188132615f },
|
||||||
|
{ 0.000090081630021837f, -0.000395731646002122f },
|
||||||
|
{ 0.000257705918065856f, 0.000154354504676150f },
|
||||||
|
{ -0.000064051192147575f, 0.000055648228186439f },
|
||||||
|
{ 0.000089938060647145f, 0.000213032074676941f },
|
||||||
|
{ -0.000604775098099200f, 0.000050706635726124f },
|
||||||
|
{ 0.000223309865890358f, -0.000944433958755193f },
|
||||||
|
{ 0.001049943574694384f, 0.000640863688898729f },
|
||||||
|
{ -0.000983491651119595f, 0.000840133365053179f },
|
||||||
|
{ -0.000417178588714773f, -0.001011686459999295f },
|
||||||
|
{ 0.000616677332283103f, -0.000046513429902547f },
|
||||||
|
{ 0.000018549463752019f, -0.000075619948809012f },
|
||||||
|
{ 0.000734408386201158f, 0.000456742966201638f },
|
||||||
|
{ -0.001192460562555901f, 0.001001510577200253f },
|
||||||
|
{ -0.000729137747758392f, -0.001811046261815935f },
|
||||||
|
{ 0.001878272869910273f, -0.000125879189667096f },
|
||||||
|
{ -0.000312873903977849f, 0.001230889889574772f },
|
||||||
|
{ -0.000142534831707354f, -0.000090307321579771f },
|
||||||
|
{ -0.000942796972567241f, 0.000778470227412111f },
|
||||||
|
{ -0.000945381510920278f, -0.002406055808135091f },
|
||||||
|
{ 0.003537159230775561f, -0.000207350791625892f },
|
||||||
|
{ -0.000956199555190230f, 0.003634225577771235f },
|
||||||
|
{ -0.002543835202533561f, -0.001641705037372486f },
|
||||||
|
{ 0.001064108471592447f, -0.000863770138941644f },
|
||||||
|
{ -0.000335799601479829f, -0.000876091753216939f },
|
||||||
|
{ 0.003390761989356699f, -0.000170321604912419f },
|
||||||
|
{ -0.001408130728751909f, 0.005175554625981795f },
|
||||||
|
{ -0.005203055300834108f, -0.003419861284250694f },
|
||||||
|
{ 0.004342719678657084f, -0.003465264906764298f },
|
||||||
|
{ 0.001143432997855297f, 0.003059520699490539f },
|
||||||
|
{ 0.000304096484476364f, -0.000012725974706621f },
|
||||||
|
{ -0.001193870642975282f, 0.004247469277548632f },
|
||||||
|
{ -0.006681021498855877f, -0.004471771356204969f },
|
||||||
|
{ 0.007965721969864534f, -0.006247895626072559f },
|
||||||
|
{ 0.003365883969059717f, 0.009241201835481184f },
|
||||||
|
{ -0.006835562188141396f, 0.000228798228738161f },
|
||||||
|
{ 0.000409900284971528f, -0.001412838961851673f },
|
||||||
|
{ -0.004331406608345981f, -0.002951876085350234f },
|
||||||
|
{ 0.009290089917766562f, -0.007161958719089258f },
|
||||||
|
{ 0.005418326020709935f, 0.015272361365960607f },
|
||||||
|
{ -0.017077565432843410f, 0.000428641984774326f },
|
||||||
|
{ 0.003850771342644978f, -0.012869517593577566f },
|
||||||
|
{ 0.004380859690202961f, 0.003039552423897447f },
|
||||||
|
{ 0.004761181766399753f, -0.003607421240356480f },
|
||||||
|
{ 0.005926935731028822f, 0.017160134858844222f },
|
||||||
|
{ -0.028153584885925551f, 0.000471042980325370f },
|
||||||
|
{ 0.009655944938035437f, -0.031314555422639050f },
|
||||||
|
{ 0.023930146568136038f, 0.016901617811072800f },
|
||||||
|
{ -0.012998853255109976f, 0.009678807314399702f },
|
||||||
|
{ 0.002043176559434885f, 0.006079907699564680f },
|
||||||
|
{ -0.036686455817128191f, 0.000306882557812233f },
|
||||||
|
{ 0.021529138474771701f, -0.067800343150283604f },
|
||||||
|
{ 0.085421344938160879f, 0.061409588050754214f },
|
||||||
|
{ -0.108166660998898100f, 0.079141989828113088f },
|
||||||
|
{ -0.047617308971534079f, -0.145721049254261960f },
|
||||||
|
{ 0.160079041453427080f, -0.000000000000000427f },
|
||||||
|
{ -0.047617308971533295f, 0.145721049254262240f },
|
||||||
|
{ -0.108166660998898530f, -0.079141989828112505f },
|
||||||
|
{ 0.085421344938160546f, -0.061409588050754672f },
|
||||||
|
{ 0.021529138474772065f, 0.067800343150283493f },
|
||||||
|
{ -0.036686455817128191f, -0.000306882557812037f },
|
||||||
|
{ 0.002043176559434853f, -0.006079907699564691f },
|
||||||
|
{ -0.012998853255110026f, -0.009678807314399631f },
|
||||||
|
{ 0.023930146568135951f, -0.016901617811072928f },
|
||||||
|
{ 0.009655944938035604f, 0.031314555422638994f },
|
||||||
|
{ -0.028153584885925554f, -0.000471042980325220f },
|
||||||
|
{ 0.005926935731028730f, -0.017160134858844253f },
|
||||||
|
{ 0.004761181766399772f, 0.003607421240356455f },
|
||||||
|
{ 0.004380859690202943f, -0.003039552423897470f },
|
||||||
|
{ 0.003850771342645046f, 0.012869517593577545f },
|
||||||
|
{ -0.017077565432843413f, -0.000428641984774235f },
|
||||||
|
{ 0.005418326020709854f, -0.015272361365960637f },
|
||||||
|
{ 0.009290089917766600f, 0.007161958719089209f },
|
||||||
|
{ -0.004331406608345964f, 0.002951876085350257f },
|
||||||
|
{ 0.000409900284971536f, 0.001412838961851670f },
|
||||||
|
{ -0.006835562188141398f, -0.000228798228738125f },
|
||||||
|
{ 0.003365883969059667f, -0.009241201835481201f },
|
||||||
|
{ 0.007965721969864567f, 0.006247895626072517f },
|
||||||
|
{ -0.006681021498855855f, 0.004471771356205005f },
|
||||||
|
{ -0.001193870642975304f, -0.004247469277548626f },
|
||||||
|
{ 0.000304096484476364f, 0.000012725974706619f },
|
||||||
|
{ 0.001143432997855281f, -0.003059520699490545f },
|
||||||
|
{ 0.004342719678657102f, 0.003465264906764274f },
|
||||||
|
{ -0.005203055300834089f, 0.003419861284250722f },
|
||||||
|
{ -0.001408130728751936f, -0.005175554625981787f },
|
||||||
|
{ 0.003390761989356700f, 0.000170321604912401f },
|
||||||
|
{ -0.000335799601479825f, 0.000876091753216940f },
|
||||||
|
{ 0.001064108471592452f, 0.000863770138941638f },
|
||||||
|
{ -0.002543835202533552f, 0.001641705037372499f },
|
||||||
|
{ -0.000956199555190250f, -0.003634225577771230f },
|
||||||
|
{ 0.003537159230775563f, 0.000207350791625874f },
|
||||||
|
{ -0.000945381510920265f, 0.002406055808135096f },
|
||||||
|
{ -0.000942796972567245f, -0.000778470227412106f },
|
||||||
|
{ -0.000142534831707354f, 0.000090307321579771f },
|
||||||
|
{ -0.000312873903977856f, -0.001230889889574770f },
|
||||||
|
{ 0.001878272869910274f, 0.000125879189667086f },
|
||||||
|
{ -0.000729137747758382f, 0.001811046261815939f },
|
||||||
|
{ -0.001192460562555906f, -0.001001510577200246f },
|
||||||
|
{ 0.000734408386201156f, -0.000456742966201642f },
|
||||||
|
{ 0.000018549463752019f, 0.000075619948809012f },
|
||||||
|
{ 0.000616677332283103f, 0.000046513429902543f },
|
||||||
|
{ -0.000417178588714767f, 0.001011686459999298f },
|
||||||
|
{ -0.000983491651119600f, -0.000840133365053174f },
|
||||||
|
{ 0.001049943574694380f, -0.000640863688898734f },
|
||||||
|
{ 0.000223309865890363f, 0.000944433958755192f },
|
||||||
|
{ -0.000604775098099200f, -0.000050706635726121f },
|
||||||
|
{ 0.000089938060647144f, -0.000213032074676941f },
|
||||||
|
{ -0.000064051192147576f, -0.000055648228186438f },
|
||||||
|
{ 0.000257705918065856f, -0.000154354504676151f },
|
||||||
|
{ 0.000090081630021839f, 0.000395731646002121f },
|
||||||
|
{ -0.000414693079508517f, -0.000038265188132613f },
|
||||||
|
{ 0.000143445458895461f, -0.000331994546004200f },
|
||||||
|
{ 0.000205649042029008f, 0.000181710515677256f },
|
||||||
|
{ -0.000157609487484145f, 0.000092618697641465f },
|
||||||
|
{ -0.000022529111707761f, -0.000102942513429094f },
|
||||||
|
{ 0.000050180791439308f, 0.000005054021864311f },
|
||||||
|
{ -0.000007675039564594f, 0.000017362992335168f }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CHROMA_BANDPASS_SIZE (sizeof(CHROMA_BANDPASS)/sizeof(dsp::complex_t))
|
||||||
|
#define CHROMA_BANDPASS_DELAY (CHROMA_BANDPASS_SIZE/2)
|
||||||
255
decoder_modules/atv_decoder/src/linesync.h
Normal file
255
decoder_modules/atv_decoder/src/linesync.h
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
#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>
|
||||||
|
|
||||||
|
#define LINE_SIZE 945
|
||||||
|
|
||||||
|
#define SYNC_LEN 70
|
||||||
|
#define SYNC_SIDE_LEN 17
|
||||||
|
#define SYNC_L_START (LINE_SIZE - SYNC_SIDE_LEN)
|
||||||
|
#define SYNC_R_START (SYNC_LEN/2)
|
||||||
|
#define SYNC_R_END (SYNC_R_START + (SYNC_LEN/2) + SYNC_SIDE_LEN)
|
||||||
|
#define SYNC_HALF_LEN ((SYNC_LEN/2) + SYNC_SIDE_LEN)
|
||||||
|
|
||||||
|
#define EQUAL_LEN 35
|
||||||
|
|
||||||
|
#define HBLANK_START SYNC_LEN
|
||||||
|
#define HBLANK_END 155
|
||||||
|
#define HBLANK_LEN (HBLANK_END - HBLANK_START + 1)
|
||||||
|
|
||||||
|
#define SYNC_LEVEL (-0.428)
|
||||||
|
|
||||||
|
#define COLORBURST_START 84
|
||||||
|
#define COLORBURST_LEN 33
|
||||||
|
|
||||||
|
#define MAX_LOCK 1000
|
||||||
|
|
||||||
|
dsp::complex_t PHASE_REF[2] = {
|
||||||
|
{ -0.707106781186547f, 0.707106781186547f },
|
||||||
|
{ -0.707106781186547f, -0.707106781186547f }
|
||||||
|
};
|
||||||
|
|
||||||
|
class LineSync : public dsp::Processor<float, float> {
|
||||||
|
using base_type = dsp::Processor<float, float>;
|
||||||
|
public:
|
||||||
|
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;
|
||||||
|
|
||||||
|
generateInterpTaps();
|
||||||
|
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
|
||||||
|
bufStart = &buffer[_interpTapCount - 1];
|
||||||
|
|
||||||
|
// TODO: Needs tuning, so do the gains
|
||||||
|
maxPeriod = (int32_t)(1.0001 * (float)(1 << 30));
|
||||||
|
minPeriod = (int32_t)(0.9999 * (float)(1 << 30));
|
||||||
|
|
||||||
|
base_type::init(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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;
|
||||||
|
phase = 0;
|
||||||
|
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));
|
||||||
|
|
||||||
|
// Process samples while they are available
|
||||||
|
while (offset < count) {
|
||||||
|
// While the offset is negative, out put zeros
|
||||||
|
while (offset < 0 && pixel < LINE_SIZE) {
|
||||||
|
// Output a zero
|
||||||
|
base_type::out.writeBuf[pixel++] = 0.0f;
|
||||||
|
|
||||||
|
// Increment the phase
|
||||||
|
phase += period;
|
||||||
|
offset += (phase >> 30);
|
||||||
|
phase &= 0x3FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process as much of a line as possible
|
||||||
|
while (offset < count && pixel < LINE_SIZE) {
|
||||||
|
// Compute the output sample
|
||||||
|
volk_32f_x2_dot_prod_32f(&base_type::out.writeBuf[pixel++], &buffer[offset], interpBank.phases[(phase >> 23) & 0x7F], _interpTapCount);
|
||||||
|
|
||||||
|
// Increment the phase
|
||||||
|
phase += period;
|
||||||
|
offset += (phase >> 30);
|
||||||
|
phase &= 0x3FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the line is done, process it
|
||||||
|
if (pixel == LINE_SIZE) {
|
||||||
|
// Compute averages. (TODO: Try faster method)
|
||||||
|
float left = 0.0f, right = 0.0f;
|
||||||
|
int lc = 0, rc = 0;
|
||||||
|
for (int i = SYNC_L_START; i < LINE_SIZE; i++) {
|
||||||
|
left += base_type::out.writeBuf[i];
|
||||||
|
lc++;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < SYNC_R_START; i++) {
|
||||||
|
left += base_type::out.writeBuf[i];
|
||||||
|
lc++;
|
||||||
|
}
|
||||||
|
for (int i = SYNC_R_START; i < SYNC_R_END; i++) {
|
||||||
|
right += base_type::out.writeBuf[i];
|
||||||
|
rc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the error
|
||||||
|
float error = (left - right) * (1.0f/((float)SYNC_HALF_LEN));
|
||||||
|
|
||||||
|
// Compute the change in phase and frequency due to the error
|
||||||
|
float periodDelta = error * _omegaGain;
|
||||||
|
float phaseDelta = error * _muGain;
|
||||||
|
|
||||||
|
// Normalize the phase delta (TODO: Make faster)
|
||||||
|
while (phaseDelta <= -1.0f) {
|
||||||
|
phaseDelta += 1.0f;
|
||||||
|
offset--;
|
||||||
|
}
|
||||||
|
while (phaseDelta >= 1.0f) {
|
||||||
|
phaseDelta -= 1.0f;
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the period (TODO: Clamp error*omegaGain to prevent weird shit with corrupt samples)
|
||||||
|
period += (int32_t)(periodDelta * (float)(1 << 30));
|
||||||
|
period = std::clamp<uint32_t>(period, minPeriod, maxPeriod);
|
||||||
|
|
||||||
|
// Update the phase
|
||||||
|
phase += (int32_t)(phaseDelta * (float)(1 << 30));
|
||||||
|
|
||||||
|
// Normalize the phase
|
||||||
|
uint32_t overflow = phase >> 30;
|
||||||
|
if (overflow) {
|
||||||
|
if (error < 0) {
|
||||||
|
offset -= 4 - overflow;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offset += overflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
phase &= 0x3FFFFFFF;
|
||||||
|
|
||||||
|
// Find the lowest value
|
||||||
|
float lowest = INFINITY;
|
||||||
|
int lowestId = -1;
|
||||||
|
for (int i = 0; i < LINE_SIZE; i++) {
|
||||||
|
float val = base_type::out.writeBuf[i];
|
||||||
|
if (val < lowest) {
|
||||||
|
lowest = val;
|
||||||
|
lowestId = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the the line is in lock
|
||||||
|
bool lineLocked = (lowestId < SYNC_R_END || lowestId >= SYNC_L_START);
|
||||||
|
|
||||||
|
// Update the lock status based on the line lock
|
||||||
|
if (!lineLocked && locked) {
|
||||||
|
locked--;
|
||||||
|
}
|
||||||
|
else if (lineLocked && locked < MAX_LOCK) {
|
||||||
|
locked++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not locked, attempt to lock by forcing the sync to happen at the right spot
|
||||||
|
// TODO: This triggers waaaay too easily at low SNR
|
||||||
|
if (!locked && fastLock) {
|
||||||
|
offset += lowestId - SYNC_R_START;
|
||||||
|
locked = MAX_LOCK / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output line
|
||||||
|
if (!base_type::out.swap(LINE_SIZE)) { break; }
|
||||||
|
pixel = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the offset ready for the next buffer
|
||||||
|
offset -= count;
|
||||||
|
|
||||||
|
// Update delay buffer
|
||||||
|
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
|
||||||
|
|
||||||
|
// Swap if some data was generated
|
||||||
|
base_type::_in->flush();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float syncBias = 0;
|
||||||
|
|
||||||
|
uint32_t period = (0x800072F3 >> 1);//(1 << 31) + 1;
|
||||||
|
|
||||||
|
int locked = 0;
|
||||||
|
bool fastLock = true;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
double _omega;
|
||||||
|
double _omegaGain;
|
||||||
|
double _muGain;
|
||||||
|
double _omegaRelLimit;
|
||||||
|
int _interpPhaseCount;
|
||||||
|
int _interpTapCount;
|
||||||
|
float* buffer;
|
||||||
|
float* bufStart;
|
||||||
|
|
||||||
|
uint32_t phase = 0;
|
||||||
|
uint32_t maxPeriod;
|
||||||
|
uint32_t minPeriod;
|
||||||
|
float syncLevel = -0.03f;
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
int pixel = 0;
|
||||||
|
};
|
||||||
@@ -10,6 +10,19 @@
|
|||||||
|
|
||||||
#include <dsp/demod/quadrature.h>
|
#include <dsp/demod/quadrature.h>
|
||||||
#include <dsp/sink/handler_sink.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 "amplitude.h"
|
||||||
|
#include <dsp/demod/am.h>
|
||||||
|
#include <dsp/loop/fast_agc.h>
|
||||||
|
|
||||||
|
#include "filters.h"
|
||||||
|
#include <dsp/math/normalize_phase.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
@@ -17,21 +30,30 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
|
|||||||
/* Description: */ "ATV decoder for SDR++",
|
/* Description: */ "ATV decoder for SDR++",
|
||||||
/* Author: */ "Ryzerth",
|
/* Author: */ "Ryzerth",
|
||||||
/* Version: */ 0, 1, 0,
|
/* Version: */ 0, 1, 0,
|
||||||
/* Max instances */ -1};
|
/* Max instances */ -1
|
||||||
|
};
|
||||||
|
|
||||||
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
|
#define SAMPLE_RATE (625.0f * (float)LINE_SIZE * 25.0f)
|
||||||
|
|
||||||
class ATVDecoderModule : public ModuleManager::Instance {
|
class ATVDecoderModule : public ModuleManager::Instance {
|
||||||
public:
|
public:
|
||||||
ATVDecoderModule(std::string name) : img(720, 625) {
|
ATVDecoderModule(std::string name) : img(768, 576) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
|
|
||||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
|
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 7000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
|
||||||
|
|
||||||
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
|
agc.init(vfo->output, 1.0f, 1e6, 0.001f, 1.0f);
|
||||||
sink.init(&demod.out, handler, this);
|
demod.init(&agc.out, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
|
||||||
|
// demod.init(vfo->output, dsp::demod::AM<float>::CARRIER, 8000000.0f, 50.0 / SAMPLE_RATE, 50.0 / SAMPLE_RATE, 0.0f, SAMPLE_RATE);
|
||||||
|
// demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
|
||||||
|
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05);
|
||||||
|
sink.init(&sync.out, handler, this);
|
||||||
|
|
||||||
|
r2c.init(NULL);
|
||||||
|
|
||||||
|
agc.start();
|
||||||
demod.start();
|
demod.start();
|
||||||
|
sync.start();
|
||||||
sink.start();
|
sink.start();
|
||||||
|
|
||||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||||
@@ -41,15 +63,22 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
|||||||
if (vfo) {
|
if (vfo) {
|
||||||
sigpath::vfoManager.deleteVFO(vfo);
|
sigpath::vfoManager.deleteVFO(vfo);
|
||||||
}
|
}
|
||||||
|
agc.stop();
|
||||||
demod.stop();
|
demod.stop();
|
||||||
|
sync.stop();
|
||||||
|
sink.stop();
|
||||||
gui::menu.removeEntry(name);
|
gui::menu.removeEntry(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postInit() {}
|
void postInit() {}
|
||||||
|
|
||||||
void enable() { enabled = true; }
|
void enable() {
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
void disable() { enabled = false; }
|
void disable() {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
bool isEnabled() { return enabled; }
|
bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
@@ -64,110 +93,215 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
|||||||
ImGui::FillWidth();
|
ImGui::FillWidth();
|
||||||
_this->img.draw();
|
_this->img.draw();
|
||||||
|
|
||||||
ImGui::LeftLabel("Sync");
|
ImGui::TextUnformatted("Horizontal Sync:");
|
||||||
ImGui::FillWidth();
|
ImGui::SameLine();
|
||||||
ImGui::SliderFloat("##syncLvl", &_this->sync_level, -2, 2);
|
if (_this->sync.locked > 750) {
|
||||||
|
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ImGui::TextUnformatted("Not locked");
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::LeftLabel("Min");
|
ImGui::TextUnformatted("Vertical Sync:");
|
||||||
ImGui::FillWidth();
|
ImGui::SameLine();
|
||||||
ImGui::SliderFloat("##minLvl", &_this->minLvl, -1.0, 1.0);
|
if (_this->vlock > 15) {
|
||||||
|
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ImGui::TextUnformatted("Not locked");
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::LeftLabel("Span");
|
ImGui::Checkbox("Fast Lock", &_this->sync.fastLock);
|
||||||
ImGui::FillWidth();
|
ImGui::Checkbox("Color Mode", &_this->colorMode);
|
||||||
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
|
|
||||||
|
|
||||||
if (!_this->enabled) {
|
if (!_this->enabled) {
|
||||||
style::endDisabled();
|
style::endDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Gain: %f", _this->gain);
|
||||||
|
ImGui::Text("Offset: %f", _this->offset);
|
||||||
|
ImGui::Text("Subcarrier: %f", _this->subcarrierFreq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t pp = 0;
|
||||||
|
|
||||||
static void handler(float *data, int count, void *ctx) {
|
static void handler(float *data, int count, void *ctx) {
|
||||||
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
|
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
|
||||||
|
|
||||||
uint8_t *buf = (uint8_t *)_this->img.buffer;
|
// Correct the offset
|
||||||
float val;
|
#if VOLK_VERSION_MAJOR > 2 || (VOLK_VERSION_MAJOR == 2 && VOLK_VERSION_MINOR >= 3)
|
||||||
float imval;
|
volk_32f_s32f_add_32f(data, data, _this->offset, count);
|
||||||
int pos = 0;
|
#else
|
||||||
|
const float ofs = _this->offset;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
val = data[i];
|
data[i] += ofs;
|
||||||
// Sync
|
}
|
||||||
if (val < _this->sync_level) {
|
#endif
|
||||||
_this->sync_count++;
|
|
||||||
|
// Correct the gain
|
||||||
|
volk_32f_s32f_multiply_32f(data, data, _this->gain, count);
|
||||||
|
|
||||||
|
// Compute the sync levels
|
||||||
|
float syncLLevel = 0.0f;
|
||||||
|
float syncRLevel = 0.0f;
|
||||||
|
volk_32f_accumulator_s32f(&syncLLevel, data, EQUAL_LEN);
|
||||||
|
volk_32f_accumulator_s32f(&syncRLevel, &data[EQUAL_LEN], SYNC_LEN - EQUAL_LEN);
|
||||||
|
syncLLevel *= 1.0f / EQUAL_LEN;
|
||||||
|
syncRLevel *= 1.0f / (SYNC_LEN - EQUAL_LEN);
|
||||||
|
float syncLevel = (syncLLevel + syncRLevel) * 0.5f; // TODO: It's technically correct but if the sizes were different it wouldn't be
|
||||||
|
|
||||||
|
// Compute the blanking level
|
||||||
|
float blankLevel = 0.0f;
|
||||||
|
volk_32f_accumulator_s32f(&blankLevel, &data[HBLANK_START], HBLANK_LEN);
|
||||||
|
blankLevel /= (float)HBLANK_LEN;
|
||||||
|
|
||||||
|
// Run the offset control loop
|
||||||
|
_this->offset -= (blankLevel / _this->gain)*0.001;
|
||||||
|
_this->offset = std::clamp<float>(_this->offset, -1.0f, 1.0f);
|
||||||
|
_this->gain -= (blankLevel - syncLevel + SYNC_LEVEL)*0.01f;
|
||||||
|
_this->gain = std::clamp<float>(_this->gain, 0.1f, 10.0f);
|
||||||
|
|
||||||
|
// Detect the sync type
|
||||||
|
uint16_t shortSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel > 0.5f*SYNC_LEVEL) && (blankLevel > 0.5f*SYNC_LEVEL);
|
||||||
|
uint16_t longSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel < 0.5f*SYNC_LEVEL) && (blankLevel < 0.5f*SYNC_LEVEL);
|
||||||
|
|
||||||
|
// Save sync type to history
|
||||||
|
_this->syncHistory = (_this->syncHistory << 2) | (longSync << 1) | shortSync;
|
||||||
|
|
||||||
|
// // If the line has a colorburst, decode it
|
||||||
|
// dsp::complex_t* buf1 = _this->r2c.out.readBuf;
|
||||||
|
// dsp::complex_t* buf2 = _this->r2c.out.writeBuf;
|
||||||
|
// if (true) {
|
||||||
|
// // Convert the line into complex
|
||||||
|
// _this->r2c.process(count, data, buf1);
|
||||||
|
|
||||||
|
// // Extract the chroma subcarrier (TODO: Optimise by running only where needed)
|
||||||
|
// for (int i = COLORBURST_START; i < count-(CHROMA_BANDPASS_DELAY+1); i++) {
|
||||||
|
// volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&buf2[i], (lv_32fc_t*)&buf1[i - CHROMA_BANDPASS_DELAY], (lv_32fc_t*)CHROMA_BANDPASS, CHROMA_BANDPASS_SIZE);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Down convert the chroma subcarrier (TODO: Optimise by running only where needed)
|
||||||
|
// lv_32fc_t startPhase = { 1.0f, 0.0f };
|
||||||
|
// lv_32fc_t phaseDelta = { sinf(_this->subcarrierFreq), cosf(_this->subcarrierFreq) };
|
||||||
|
// #if VOLK_VERSION >= 030100
|
||||||
|
// volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], &phaseDelta, &startPhase, count - COLORBURST_START);
|
||||||
|
// #else
|
||||||
|
// volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], phaseDelta, &startPhase, count - COLORBURST_START);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// // Compute the phase of the burst
|
||||||
|
// dsp::complex_t burstAvg = { 0.0f, 0.0f };
|
||||||
|
// volk_32fc_accumulator_s32fc((lv_32fc_t*)&burstAvg, (lv_32fc_t*)&buf2[COLORBURST_START], COLORBURST_LEN);
|
||||||
|
// float burstAmp = burstAvg.amplitude();
|
||||||
|
// if (burstAmp*(1.0f/(float)COLORBURST_LEN) < 0.02f) {
|
||||||
|
// printf("%d\n", _this->line);
|
||||||
|
// }
|
||||||
|
// burstAvg *= (1.0f / (burstAmp*burstAmp));
|
||||||
|
// burstAvg = burstAvg.conj();
|
||||||
|
|
||||||
|
// // Normalize the chroma data (TODO: Optimise by running only where needed)
|
||||||
|
// volk_32fc_s32fc_multiply_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], *((lv_32fc_t*)&burstAvg), count - COLORBURST_START);
|
||||||
|
|
||||||
|
// // Compute the frequency error of the burst
|
||||||
|
// float phase = buf2[COLORBURST_START].phase();
|
||||||
|
// float error = 0.0f;
|
||||||
|
// for (int i = COLORBURST_START+1; i < COLORBURST_START+COLORBURST_LEN; i++) {
|
||||||
|
// float cphase = buf2[i].phase();
|
||||||
|
// error += dsp::math::normalizePhase(cphase - phase);
|
||||||
|
// phase = cphase;
|
||||||
|
// }
|
||||||
|
// error *= (1.0f / (float)(COLORBURST_LEN-1));
|
||||||
|
|
||||||
|
// // Update the subcarrier freq
|
||||||
|
// _this->subcarrierFreq += error*0.0001f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Render the line if it's visible
|
||||||
|
if (_this->ypos >= 34 && _this->ypos <= 34+576-1) {
|
||||||
|
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos - 34)*768];
|
||||||
|
if (_this->colorMode) {
|
||||||
|
// for (int i = 155; i < (155+768); i++) {
|
||||||
|
// int imval1 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].re*5.0f) * 255.0f, 0, 255);
|
||||||
|
// int imval2 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].im*5.0f) * 255.0f, 0, 255);
|
||||||
|
// currentLine[i-155] = 0xFF000000 | (imval2 << 8) | imval1;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (_this->sync_count >= 300) {
|
for (int i = 155; i < (155+768); i++) {
|
||||||
_this->short_sync = 0;
|
int imval = std::clamp<float>(data[i] * 255.0f, 0, 255);
|
||||||
}
|
currentLine[i-155] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute whether to rollover
|
||||||
|
bool rollToOdd = (_this->ypos == 624);
|
||||||
|
bool rollToEven = (_this->ypos == 623);
|
||||||
|
|
||||||
|
// Compute the field sync
|
||||||
|
bool syncToOdd = (_this->syncHistory == 0b0101011010010101);
|
||||||
|
bool syncToEven = (_this->syncHistory == 0b0001011010100101);
|
||||||
|
|
||||||
|
// Process the sync (NOTE: should start with 0b01, but for some reason I don't see a sync?)
|
||||||
|
if (rollToOdd || syncToOdd) {
|
||||||
|
// Update the vertical lock state
|
||||||
|
bool disagree = (rollToOdd ^ syncToOdd);
|
||||||
|
if (disagree && _this->vlock > 0) {
|
||||||
|
_this->vlock--;
|
||||||
|
}
|
||||||
|
else if (!disagree && _this->vlock < 20) {
|
||||||
|
_this->vlock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the odd field
|
||||||
|
_this->ypos = 1;
|
||||||
|
_this->line++;
|
||||||
|
}
|
||||||
|
else if (rollToEven || syncToEven) {
|
||||||
|
// Update the vertical lock state
|
||||||
|
bool disagree = (rollToEven ^ syncToEven);
|
||||||
|
if (disagree && _this->vlock > 0) {
|
||||||
|
_this->vlock--;
|
||||||
|
}
|
||||||
|
else if (!disagree && _this->vlock < 20) {
|
||||||
|
_this->vlock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the even field
|
||||||
|
_this->ypos = 0;
|
||||||
|
_this->line = 0;
|
||||||
|
|
||||||
|
// Swap the video buffer
|
||||||
|
_this->img.swap();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_this->ypos += 2;
|
||||||
|
_this->line++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NEW SYNC:
|
||||||
|
float offset = 0.0f;
|
||||||
|
float gain = 1.0f;
|
||||||
|
uint16_t syncHistory = 0;
|
||||||
|
int line = 0;
|
||||||
|
int ypos = 0;
|
||||||
|
int vlock = 0;
|
||||||
|
float subcarrierFreq = 0.0f;
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
|
|
||||||
VFOManager::VFO *vfo = NULL;
|
VFOManager::VFO *vfo = NULL;
|
||||||
dsp::demod::Quadrature demod;
|
// dsp::demod::Quadrature demod;
|
||||||
|
dsp::loop::FastAGC<dsp::complex_t> agc;
|
||||||
|
dsp::demod::Amplitude demod;
|
||||||
|
//dsp::demod::AM<float> demod;
|
||||||
|
LineSync sync;
|
||||||
dsp::sink::Handler<float> sink;
|
dsp::sink::Handler<float> sink;
|
||||||
|
dsp::convert::RealToComplex r2c;
|
||||||
|
|
||||||
int xpos = 0;
|
bool colorMode = false;
|
||||||
int ypos = 0;
|
|
||||||
bool even_field = false;
|
|
||||||
|
|
||||||
float sync_level = -0.3f;
|
|
||||||
int sync_count = 0;
|
|
||||||
int short_sync = 0;
|
|
||||||
|
|
||||||
float minLvl = 0.0f;
|
|
||||||
float spanLvl = 1.0f;
|
|
||||||
|
|
||||||
ImGui::ImageDisplay img;
|
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
|
||||||
@@ -214,10 +214,10 @@ private:
|
|||||||
|
|
||||||
if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) {
|
if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) {
|
||||||
if (_this->showLines) {
|
if (_this->showLines) {
|
||||||
_this->diag.lines.push_back(-0.75f);
|
_this->diag.lines.push_back(-1.0);
|
||||||
_this->diag.lines.push_back(-0.25f);
|
_this->diag.lines.push_back(-1.0/3.0);
|
||||||
_this->diag.lines.push_back(0.25f);
|
_this->diag.lines.push_back(1.0/3.0);
|
||||||
_this->diag.lines.push_back(0.75f);
|
_this->diag.lines.push_back(1.0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_this->diag.lines.clear();
|
_this->diag.lines.clear();
|
||||||
|
|||||||
@@ -57,10 +57,13 @@ public:
|
|||||||
if (config.conf[name].contains("brokenModulation")) {
|
if (config.conf[name].contains("brokenModulation")) {
|
||||||
brokenModulation = config.conf[name]["brokenModulation"];
|
brokenModulation = config.conf[name]["brokenModulation"];
|
||||||
}
|
}
|
||||||
|
if (config.conf[name].contains("oqpsk")) {
|
||||||
|
oqpsk = config.conf[name]["oqpsk"];
|
||||||
|
}
|
||||||
config.release();
|
config.release();
|
||||||
|
|
||||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
|
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, 1e-6, 0.01);
|
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.init(&demod.out);
|
||||||
split.bindStream(&symSinkStream);
|
split.bindStream(&symSinkStream);
|
||||||
split.bindStream(&sinkStream);
|
split.bindStream(&sinkStream);
|
||||||
@@ -99,6 +102,7 @@ public:
|
|||||||
double bw = gui::waterfall.getBandwidth();
|
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);
|
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.setInput(vfo->output);
|
||||||
|
|
||||||
demod.start();
|
demod.start();
|
||||||
@@ -151,6 +155,13 @@ private:
|
|||||||
config.release(true);
|
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->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); }
|
||||||
|
|
||||||
if (_this->recording) {
|
if (_this->recording) {
|
||||||
@@ -245,7 +256,7 @@ private:
|
|||||||
uint64_t dataWritten = 0;
|
uint64_t dataWritten = 0;
|
||||||
std::ofstream recFile;
|
std::ofstream recFile;
|
||||||
bool brokenModulation = false;
|
bool brokenModulation = false;
|
||||||
|
bool oqpsk = false;
|
||||||
int8_t* writeBuffer;
|
int8_t* writeBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ namespace dsp::demod {
|
|||||||
public:
|
public:
|
||||||
Meteor() {}
|
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) {
|
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, omegaGain, muGain);
|
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, oqpsk, omegaGain, muGain);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Meteor() {
|
~Meteor() {
|
||||||
@@ -21,11 +21,12 @@ namespace dsp::demod {
|
|||||||
taps::free(rrcTaps);
|
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;
|
_symbolrate = symbolrate;
|
||||||
_samplerate = samplerate;
|
_samplerate = samplerate;
|
||||||
_rrcTapCount = rrcTapCount;
|
_rrcTapCount = rrcTapCount;
|
||||||
_rrcBeta = rrcBeta;
|
_rrcBeta = rrcBeta;
|
||||||
|
_oqpsk = oqpsk;
|
||||||
|
|
||||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||||
rrc.init(NULL, rrcTaps);
|
rrc.init(NULL, rrcTaps);
|
||||||
@@ -129,6 +130,12 @@ namespace dsp::demod {
|
|||||||
costas.setBrokenModulation(enabled);
|
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() {
|
void reset() {
|
||||||
assert(base_type::_block_init);
|
assert(base_type::_block_init);
|
||||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||||
@@ -144,6 +151,18 @@ namespace dsp::demod {
|
|||||||
rrc.process(count, in, out);
|
rrc.process(count, in, out);
|
||||||
agc.process(count, out, out);
|
agc.process(count, out, out);
|
||||||
costas.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);
|
return recov.process(count, out, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +185,8 @@ namespace dsp::demod {
|
|||||||
double _samplerate;
|
double _samplerate;
|
||||||
int _rrcTapCount;
|
int _rrcTapCount;
|
||||||
double _rrcBeta;
|
double _rrcBeta;
|
||||||
|
float lastI = 0.0f;
|
||||||
|
bool _oqpsk = false;
|
||||||
|
|
||||||
tap<float> rrcTaps;
|
tap<float> rrcTaps;
|
||||||
filter::FIR<complex_t, float> rrc;
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -20,6 +20,16 @@ enum IFNRPreset {
|
|||||||
IFNR_PRESET_BROADCAST
|
IFNR_PRESET_BROADCAST
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum SquelchMode {
|
||||||
|
SQUELCH_MODE_OFF,
|
||||||
|
SQUELCH_MODE_POWER,
|
||||||
|
SQUELCH_MODE_SNR,
|
||||||
|
SQUELCH_MODE_CTCSS_MUTE,
|
||||||
|
SQUELCH_MODE_CTCSS_DECODE,
|
||||||
|
SQUELCH_MODE_DCS_MUTE,
|
||||||
|
SQUELCH_MODE_DCS_DECODE,
|
||||||
|
};
|
||||||
|
|
||||||
namespace demod {
|
namespace demod {
|
||||||
class Demodulator {
|
class Demodulator {
|
||||||
public:
|
public:
|
||||||
@@ -45,6 +55,8 @@ namespace demod {
|
|||||||
virtual int getDefaultDeemphasisMode() = 0;
|
virtual int getDefaultDeemphasisMode() = 0;
|
||||||
virtual bool getFMIFNRAllowed() = 0;
|
virtual bool getFMIFNRAllowed() = 0;
|
||||||
virtual bool getNBAllowed() = 0;
|
virtual bool getNBAllowed() = 0;
|
||||||
|
virtual bool getHighPassAllowed() = 0;
|
||||||
|
virtual bool getSquelchAllowed() = 0;
|
||||||
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
|
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ namespace demod {
|
|||||||
|
|
||||||
void showMenu() {
|
void showMenu() {
|
||||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
|
||||||
|
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
|
||||||
|
_config->acquire();
|
||||||
|
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
|
||||||
|
_config->release(true);
|
||||||
|
}
|
||||||
ImGui::LeftLabel("AGC Attack");
|
ImGui::LeftLabel("AGC Attack");
|
||||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||||
if (ImGui::SliderFloat(("##_radio_am_agc_attack_" + name).c_str(), &agcAttack, 1.0f, 200.0f)) {
|
if (ImGui::SliderFloat(("##_radio_am_agc_attack_" + name).c_str(), &agcAttack, 1.0f, 200.0f)) {
|
||||||
@@ -56,12 +62,6 @@ namespace demod {
|
|||||||
_config->conf[name][getName()]["agcDecay"] = agcDecay;
|
_config->conf[name][getName()]["agcDecay"] = agcDecay;
|
||||||
_config->release(true);
|
_config->release(true);
|
||||||
}
|
}
|
||||||
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
|
|
||||||
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
|
|
||||||
_config->acquire();
|
|
||||||
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
|
|
||||||
_config->release(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBandwidth(double bandwidth) { demod.setBandwidth(bandwidth); }
|
void setBandwidth(double bandwidth) { demod.setBandwidth(bandwidth); }
|
||||||
@@ -86,6 +86,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getHighPassAllowed() { return true; }
|
||||||
|
bool getSquelchAllowed() { return true; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getHighPassAllowed() { return false; }
|
||||||
|
bool getSquelchAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getHighPassAllowed() { return true; }
|
||||||
|
bool getSquelchAllowed() { return true; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getHighPassAllowed() { return true; }
|
||||||
|
bool getSquelchAllowed() { return true; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ namespace demod {
|
|||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
_config->acquire();
|
_config->acquire();
|
||||||
bool modified = false;
|
|
||||||
if (config->conf[name][getName()].contains("lowPass")) {
|
if (config->conf[name][getName()].contains("lowPass")) {
|
||||||
_lowPass = config->conf[name][getName()]["lowPass"];
|
_lowPass = config->conf[name][getName()]["lowPass"];
|
||||||
}
|
}
|
||||||
_config->release(modified);
|
_config->release();
|
||||||
|
|
||||||
|
|
||||||
// Define structure
|
// Define structure
|
||||||
@@ -67,6 +66,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return true; }
|
bool getFMIFNRAllowed() { return true; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getHighPassAllowed() { return true; }
|
||||||
|
bool getSquelchAllowed() { return true; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getHighPassAllowed() { return false; }
|
||||||
|
bool getSquelchAllowed() { return false; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
|
||||||
bool getFMIFNRAllowed() { return false; }
|
bool getFMIFNRAllowed() { return false; }
|
||||||
bool getNBAllowed() { return true; }
|
bool getNBAllowed() { return true; }
|
||||||
|
bool getHighPassAllowed() { return true; }
|
||||||
|
bool getSquelchAllowed() { return true; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../demod.h"
|
#include "../demod.h"
|
||||||
#include <dsp/demod/broadcast_fm.h>
|
#include <dsp/demod/broadcast_fm.h>
|
||||||
#include <dsp/clock_recovery/mm.h>
|
#include "../rds_demod.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 <gui/widgets/symbol_diagram.h>
|
#include <gui/widgets/symbol_diagram.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <rds.h>
|
#include <rds.h>
|
||||||
|
|
||||||
namespace demod {
|
namespace demod {
|
||||||
|
enum RDSRegion {
|
||||||
|
RDS_REGION_EUROPE,
|
||||||
|
RDS_REGION_NORTH_AMERICA
|
||||||
|
};
|
||||||
|
|
||||||
class WFM : public Demodulator {
|
class WFM : public Demodulator {
|
||||||
public:
|
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);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,10 +29,18 @@ namespace demod {
|
|||||||
this->name = name;
|
this->name = name;
|
||||||
_config = config;
|
_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.handler = fftRedraw;
|
||||||
fftRedrawHandler.ctx = this;
|
fftRedrawHandler.ctx = this;
|
||||||
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
|
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
|
||||||
|
|
||||||
|
// Default
|
||||||
|
std::string rdsRegionStr = "eu";
|
||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
_config->acquire();
|
_config->acquire();
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
@@ -45,48 +53,65 @@ namespace demod {
|
|||||||
if (config->conf[name][getName()].contains("rds")) {
|
if (config->conf[name][getName()].contains("rds")) {
|
||||||
_rds = config->conf[name][getName()]["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);
|
_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);
|
demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
|
||||||
recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01);
|
rdsDemod.init(&demod.rdsOut, _rdsInfo);
|
||||||
slice.init(&recov.out);
|
hs.init(&rdsDemod.out, rdsHandler, this);
|
||||||
manch.init(&slice.out);
|
reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096);
|
||||||
diff.init(&manch.out, 2);
|
diagHandler.init(&reshape.out, _diagHandler, this);
|
||||||
hs.init(&diff.out, rdsHandler, this);
|
|
||||||
|
// Init RDS display
|
||||||
|
diag.lines.push_back(-0.8);
|
||||||
|
diag.lines.push_back(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
demod.start();
|
demod.start();
|
||||||
recov.start();
|
rdsDemod.start();
|
||||||
slice.start();
|
|
||||||
manch.start();
|
|
||||||
diff.start();
|
|
||||||
hs.start();
|
hs.start();
|
||||||
|
reshape.start();
|
||||||
|
diagHandler.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
demod.stop();
|
demod.stop();
|
||||||
recov.stop();
|
rdsDemod.stop();
|
||||||
slice.stop();
|
|
||||||
manch.stop();
|
|
||||||
diff.stop();
|
|
||||||
hs.stop();
|
hs.stop();
|
||||||
|
reshape.stop();
|
||||||
|
diagHandler.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showMenu() {
|
void showMenu() {
|
||||||
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
|
|
||||||
setStereo(_stereo);
|
|
||||||
_config->acquire();
|
|
||||||
_config->conf[name][getName()]["stereo"] = _stereo;
|
|
||||||
_config->release(true);
|
|
||||||
}
|
|
||||||
if (ImGui::Checkbox(("Low Pass##_radio_wfm_lowpass_" + name).c_str(), &_lowPass)) {
|
if (ImGui::Checkbox(("Low Pass##_radio_wfm_lowpass_" + name).c_str(), &_lowPass)) {
|
||||||
demod.setLowPass(_lowPass);
|
demod.setLowPass(_lowPass);
|
||||||
_config->acquire();
|
_config->acquire();
|
||||||
_config->conf[name][getName()]["lowPass"] = _lowPass;
|
_config->conf[name][getName()]["lowPass"] = _lowPass;
|
||||||
_config->release(true);
|
_config->release(true);
|
||||||
}
|
}
|
||||||
|
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
|
||||||
|
setStereo(_stereo);
|
||||||
|
_config->acquire();
|
||||||
|
_config->conf[name][getName()]["stereo"] = _stereo;
|
||||||
|
_config->release(true);
|
||||||
|
}
|
||||||
if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) {
|
if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) {
|
||||||
demod.setRDSOut(_rds);
|
demod.setRDSOut(_rds);
|
||||||
_config->acquire();
|
_config->acquire();
|
||||||
@@ -94,14 +119,129 @@ namespace demod {
|
|||||||
_config->release(true);
|
_config->release(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (_rds) {
|
// TODO: This might break when the entire radio module is disabled
|
||||||
// if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); }
|
if (!_rds) { ImGui::BeginDisabled(); }
|
||||||
// if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); }
|
if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) {
|
||||||
// if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); }
|
setAdvancedRds(_rdsInfo);
|
||||||
// if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); }
|
_config->acquire();
|
||||||
// if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); }
|
_config->conf[name][getName()]["rdsInfo"] = _rdsInfo;
|
||||||
// if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); }
|
_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) {
|
void setBandwidth(double bandwidth) {
|
||||||
@@ -130,6 +270,8 @@ namespace demod {
|
|||||||
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
|
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
|
||||||
bool getFMIFNRAllowed() { return true; }
|
bool getFMIFNRAllowed() { return true; }
|
||||||
bool getNBAllowed() { return false; }
|
bool getNBAllowed() { return false; }
|
||||||
|
bool getHighPassAllowed() { return true; }
|
||||||
|
bool getSquelchAllowed() { return true; }
|
||||||
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
|
||||||
|
|
||||||
// ============= DEDICATED FUNCTIONS =============
|
// ============= DEDICATED FUNCTIONS =============
|
||||||
@@ -139,12 +281,24 @@ namespace demod {
|
|||||||
demod.setStereo(_stereo);
|
demod.setStereo(_stereo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAdvancedRds(bool enabled) {
|
||||||
|
rdsDemod.setSoftEnabled(enabled);
|
||||||
|
_rdsInfo = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void rdsHandler(uint8_t* data, int count, void* ctx) {
|
static void rdsHandler(uint8_t* data, int count, void* ctx) {
|
||||||
WFM* _this = (WFM*)ctx;
|
WFM* _this = (WFM*)ctx;
|
||||||
_this->rdsDecode.process(data, count);
|
_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) {
|
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
|
||||||
WFM* _this = (WFM*)ctx;
|
WFM* _this = (WFM*)ctx;
|
||||||
if (!_this->_rds) { return; }
|
if (!_this->_rds) { return; }
|
||||||
@@ -186,23 +340,31 @@ namespace demod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dsp::demod::BroadcastFM demod;
|
dsp::demod::BroadcastFM demod;
|
||||||
dsp::clock_recovery::FD recov;
|
RDSDemod rdsDemod;
|
||||||
dsp::digital::BinarySlicer slice;
|
|
||||||
dsp::digital::ManchesterDecoder manch;
|
|
||||||
dsp::digital::DifferentialDecoder diff;
|
|
||||||
dsp::sink::Handler<uint8_t> hs;
|
dsp::sink::Handler<uint8_t> hs;
|
||||||
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
|
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;
|
ConfigManager* _config = NULL;
|
||||||
|
|
||||||
bool _stereo = false;
|
bool _stereo = false;
|
||||||
bool _lowPass = true;
|
bool _lowPass = true;
|
||||||
bool _rds = false;
|
bool _rds = false;
|
||||||
|
bool _rdsInfo = false;
|
||||||
float muGain = 0.01;
|
float muGain = 0.01;
|
||||||
float omegaGain = (0.01*0.01)/4.0;
|
float omegaGain = (0.01*0.01)/4.0;
|
||||||
|
|
||||||
|
int rdsRegionId = 0;
|
||||||
|
RDSRegion rdsRegion = RDS_REGION_EUROPE;
|
||||||
|
|
||||||
|
OptionList<std::string, RDSRegion> rdsRegions;
|
||||||
|
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user