mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-10-26 14:42:00 +01:00
Compare commits
758 Commits
1.0.4
...
0e1ab29b5d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e1ab29b5d | ||
|
|
fbbafddd3d | ||
|
|
1cbc8ec6f5 | ||
|
|
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 | ||
|
|
5b47f900a6 | ||
|
|
82fd3732a9 | ||
|
|
8d6ed17e01 | ||
|
|
aa2071e2df | ||
|
|
b32618708f | ||
|
|
ac875aee3b | ||
|
|
df83d65271 | ||
|
|
7723d15e8f | ||
|
|
a8cbc37e0d | ||
|
|
0d149f997f | ||
|
|
7e80bbd02c | ||
|
|
edb4ac45b2 | ||
|
|
63416fe93a | ||
|
|
0fedcf8745 | ||
|
|
fe821fb830 | ||
|
|
ea882cb285 | ||
|
|
13e81c9f6b | ||
|
|
290c989451 | ||
|
|
b7ca19583a | ||
|
|
1f97e9e10b | ||
|
|
93e985ab54 | ||
|
|
8f972ee0b2 | ||
|
|
570b8dbd7c | ||
|
|
c0c5b1186c | ||
|
|
d5a9538d0c | ||
|
|
7cfc30ee6e | ||
|
|
a1b6cbb38a | ||
|
|
a3b13e572b | ||
|
|
16eaa0cf59 | ||
|
|
fd65984762 | ||
|
|
7094368113 | ||
|
|
208851ebc5 | ||
|
|
0a6fbdb393 | ||
|
|
3451edb131 | ||
|
|
bbf0c17cb8 | ||
|
|
7758c40bd7 | ||
|
|
8a2d0fe56b | ||
|
|
31ff7f3224 | ||
|
|
e59d804b31 | ||
|
|
66bbc93535 | ||
|
|
ab5d7a73c1 | ||
|
|
7a1a37fbf6 | ||
|
|
a58683f748 | ||
|
|
d020dcc4a3 | ||
|
|
691ab585e2 | ||
|
|
bad8ecba49 | ||
|
|
d21a6af5a9 | ||
|
|
a53dc1a6ae | ||
|
|
b3222003b1 | ||
|
|
e3914ebdc6 | ||
|
|
1ae1cc0e77 | ||
|
|
55db98d1df | ||
|
|
90ce82190b | ||
|
|
a1cbc69a65 | ||
|
|
ffefd9cce8 | ||
|
|
4d890e78ed | ||
|
|
4eabc829fa | ||
|
|
ee79bf9f81 | ||
|
|
447d0e969e | ||
|
|
637d683c0a | ||
|
|
5773419257 | ||
|
|
385b34a0a1 | ||
|
|
8771e4bf09 | ||
|
|
0ba0a3ab97 | ||
|
|
9b33d8d63b | ||
|
|
bd99210fd3 | ||
|
|
a426f7b3ab | ||
|
|
3d4c7550be | ||
|
|
d14fc3805c | ||
|
|
ea23c44266 | ||
|
|
fa2e13f3ea | ||
|
|
643d47fe47 | ||
|
|
5eba556605 | ||
|
|
5c690c9753 | ||
|
|
643cad3581 | ||
|
|
bd854b590e | ||
|
|
f163e926c7 | ||
|
|
d069fb3af8 | ||
|
|
f97ca9ac05 | ||
|
|
b104e82874 | ||
|
|
3520424208 | ||
|
|
9ae6a74408 | ||
|
|
5eb42b61e9 | ||
|
|
a1259d7d32 | ||
|
|
31ac8f7b70 | ||
|
|
9f472330f8 | ||
|
|
df91f56283 | ||
|
|
f022c21f11 | ||
|
|
5aac9d66e0 | ||
|
|
a9a0798d7d | ||
|
|
bd947c2669 | ||
|
|
6adf0baaa7 | ||
|
|
fb10d0f917 | ||
|
|
61f19f44a2 | ||
|
|
638306e2da | ||
|
|
2ac1a38ea4 | ||
|
|
1c373e9cdb | ||
|
|
c4bac3b298 | ||
|
|
0a4c191054 | ||
|
|
c72c8d056d | ||
|
|
d74ea13878 | ||
|
|
d16c08c8b2 | ||
|
|
ebc9911f18 | ||
|
|
aa06db4d7a | ||
|
|
9ccc14848b | ||
|
|
09d814e845 | ||
|
|
d4a6ada56e | ||
|
|
c4fd285f7b | ||
|
|
cc6b239d22 | ||
|
|
a9e8db2a24 | ||
|
|
8eed0fcc9c | ||
|
|
68ba6d7d19 | ||
|
|
a85bae9527 | ||
|
|
34b0577f3b | ||
|
|
52a4143cf5 | ||
|
|
b65da2fd49 | ||
|
|
c95c7b18af | ||
|
|
f4bd483410 | ||
|
|
869b3e0abd | ||
|
|
3421aae9a2 | ||
|
|
f49fbd2a73 | ||
|
|
5b8b344142 | ||
|
|
0c8f6ab836 | ||
|
|
4047d3bcc2 | ||
|
|
dcb6321531 | ||
|
|
bd9df6ecf9 | ||
|
|
81ac817639 | ||
|
|
fccd08a6e0 | ||
|
|
e97dcecc6e | ||
|
|
54db4b41a4 | ||
|
|
70ff76e553 | ||
|
|
0f7c17f007 | ||
|
|
6cf474144b | ||
|
|
7ae59681d5 | ||
|
|
c7a1bda364 | ||
|
|
1c02f4edca | ||
|
|
4479170cc2 | ||
|
|
cd71becd8a | ||
|
|
72b895fc67 | ||
|
|
6c93d9b2fa | ||
|
|
ce3fea3747 | ||
|
|
9f0daf7d45 | ||
|
|
6d784dfe27 | ||
|
|
efcf26f0ac | ||
|
|
14e37b41d4 | ||
|
|
ad36ede26b | ||
|
|
18a924d3a9 | ||
|
|
fb85177987 | ||
|
|
90a8f617e9 | ||
|
|
575a941e24 | ||
|
|
8efd5cd01a | ||
|
|
50d7e5f86d | ||
|
|
dcda49aea8 | ||
|
|
3262f54100 | ||
|
|
63bb9ad9af | ||
|
|
277d399e48 | ||
|
|
25f09a355e | ||
|
|
5bf5a10a12 | ||
|
|
8b7afd88f9 | ||
|
|
c57482771b | ||
|
|
e03e95cc54 | ||
|
|
76b41cb9ab | ||
|
|
55b468a02e | ||
|
|
039ef5eae3 | ||
|
|
d3e9ebef72 | ||
|
|
dbedc0d352 | ||
|
|
d422230ae9 | ||
|
|
db5914a4d1 | ||
|
|
164970abf3 | ||
|
|
76c744a1bb | ||
|
|
e5f73e36d9 | ||
|
|
381c9d0e4b | ||
|
|
30b6c2b1da | ||
|
|
3ab8badb6a | ||
|
|
96f5b37f76 | ||
|
|
bdd4998b1b | ||
|
|
ae149b256b | ||
|
|
06210ae825 | ||
|
|
b5857e2078 | ||
|
|
edf22ccfe8 | ||
|
|
1af234e379 | ||
|
|
46f17019a7 | ||
|
|
74fd30f08c | ||
|
|
ff6754099f | ||
|
|
3f6687659e | ||
|
|
c3ddffb3a9 | ||
|
|
7d64d78d67 | ||
|
|
fb0e0d732b | ||
|
|
db034527e9 | ||
|
|
97643edf2f | ||
|
|
01e1430847 | ||
|
|
218fd6ccf4 | ||
|
|
f2b8418a25 | ||
|
|
b943e6e869 | ||
|
|
f7c566f652 | ||
|
|
834890b69a | ||
|
|
53afaeda9e | ||
|
|
ce1b0d0170 | ||
|
|
927bbab330 | ||
|
|
fd5970b35a | ||
|
|
1dddbadd04 | ||
|
|
ccc57cddc7 | ||
|
|
9b1ec79d61 | ||
|
|
e06646367b | ||
|
|
b9e269f9dc | ||
|
|
ed3f87da29 | ||
|
|
4f601405a1 | ||
|
|
cb59b04b17 | ||
|
|
c5f30f6d6a | ||
|
|
36adc102ee | ||
|
|
d1318d3a0f | ||
|
|
343ec6ca1c | ||
|
|
e4926c236f | ||
|
|
61614da910 | ||
|
|
5712db9bf8 | ||
|
|
79a15ed186 | ||
|
|
527d40c95e | ||
|
|
6f747dad0d | ||
|
|
663dd8d887 | ||
|
|
2836292d58 | ||
|
|
09c8346d4b | ||
|
|
8dbae4e307 | ||
|
|
4c004d16a2 | ||
|
|
747b6bfbc6 | ||
|
|
83da29e80b | ||
|
|
28dccee34b | ||
|
|
54dd3a77db | ||
|
|
8d78eb301c | ||
|
|
03f173a3ac | ||
|
|
b8bc942b84 | ||
|
|
80d244003a | ||
|
|
50416b3319 | ||
|
|
ea59bc5cc0 | ||
|
|
47b54743bd | ||
|
|
58fcbbc5d2 | ||
|
|
842b23b2f4 | ||
|
|
f58c7dd62f | ||
|
|
6958e46875 | ||
|
|
e82e89a87c | ||
|
|
763807565c | ||
|
|
8a603c5420 | ||
|
|
9999693c0d | ||
|
|
81692fa910 | ||
|
|
9bbc50ff3c | ||
|
|
2779516378 | ||
|
|
5c138aa4a5 | ||
|
|
7de9f3f497 | ||
|
|
98f4f560ad | ||
|
|
a87aedabb8 | ||
|
|
f46fa2157b | ||
|
|
b5cbafb01d | ||
|
|
51c940acd4 | ||
|
|
04e54a2d57 | ||
|
|
6eb8bfc29a | ||
|
|
96da404149 | ||
|
|
e9cb7fda42 | ||
|
|
e5f3d1672c | ||
|
|
71c95af711 | ||
|
|
94cf720cfd | ||
|
|
9969ce018b | ||
|
|
3c19081561 | ||
|
|
3ab669ecda | ||
|
|
860121dad2 | ||
|
|
5e5c575e93 | ||
|
|
8dcf17bef7 | ||
|
|
4fcd06eff6 | ||
|
|
dcc0fef235 | ||
|
|
260651fb5c | ||
|
|
97346e6621 | ||
|
|
80dcf2d968 | ||
|
|
963c5c6581 | ||
|
|
a7b0b52da9 | ||
|
|
03f0704dff | ||
|
|
e158eabbf4 | ||
|
|
4c0220a228 | ||
|
|
aa265ea312 | ||
|
|
2c3b603b88 | ||
|
|
75da59833a | ||
|
|
fa0967313f | ||
|
|
6fba9d7904 | ||
|
|
c1685a441c | ||
|
|
9d7c1369ca | ||
|
|
6dc97de57b | ||
|
|
0dc2f5f7c9 | ||
|
|
983c4c0f87 | ||
|
|
0ac85e0daf | ||
|
|
da06fc39db | ||
|
|
4c1b50a3ac | ||
|
|
34fc32dcf7 | ||
|
|
070e27505b | ||
|
|
74b9d13360 | ||
|
|
81d23967b6 | ||
|
|
174158233b | ||
|
|
1185e4e114 | ||
|
|
290bf5e32a | ||
|
|
4db485e209 | ||
|
|
a58ae2bd98 | ||
|
|
aab9d9e6ad | ||
|
|
7cc06b7a7b | ||
|
|
efecd14281 | ||
|
|
33914a7316 | ||
|
|
608979a0b7 | ||
|
|
3a815d94b9 | ||
|
|
7b77bb3d45 | ||
|
|
201a612c19 | ||
|
|
5a0c1ab5d0 | ||
|
|
470e748e4a | ||
|
|
66269659c5 | ||
|
|
6ded678048 | ||
|
|
f34614e169 | ||
|
|
9d3f0f4f7e | ||
|
|
25d585a35a | ||
|
|
34bd09a92e | ||
|
|
e5bbd0fdb3 | ||
|
|
b141c4b2a5 | ||
|
|
5a19829068 | ||
|
|
51f84566c4 | ||
|
|
db8f736d58 | ||
|
|
07294034f6 | ||
|
|
ad2ddc6ad3 | ||
|
|
b736f83993 | ||
|
|
d893eaae32 | ||
|
|
ea587db0cb | ||
|
|
8644957881 | ||
|
|
cc2d73202e | ||
|
|
fe0b63a275 | ||
|
|
ef01205fb6 | ||
|
|
c1cb3d5a1c | ||
|
|
f1a231b791 | ||
|
|
5483268f8f | ||
|
|
6c7e952be3 | ||
|
|
20b44403b2 | ||
|
|
964fd467f8 | ||
|
|
b4924a8fae | ||
|
|
cee0f75870 | ||
|
|
15010cff01 | ||
|
|
241632288e | ||
|
|
f8ff67c5b0 | ||
|
|
c1942ac51d | ||
|
|
91370993a2 | ||
|
|
2a5671878f | ||
|
|
1594051a5d | ||
|
|
355a6352da | ||
|
|
7208028c01 | ||
|
|
cdc060aa2a | ||
|
|
31b2cdd284 | ||
|
|
636bb214e8 | ||
|
|
8accb531b0 | ||
|
|
fe285c71ff | ||
|
|
62d2dfafd7 | ||
|
|
2748c13142 | ||
|
|
2daaf00cb3 | ||
|
|
b148fc8a28 | ||
|
|
407efef935 | ||
|
|
8c35639767 | ||
|
|
5e89c52007 | ||
|
|
2261b0fc2b | ||
|
|
e394b64ad3 | ||
|
|
fc7d902c1d | ||
|
|
2f5a8c38a1 | ||
|
|
f3e987fb7e | ||
|
|
abd718b024 | ||
|
|
28d056b776 | ||
|
|
8c631f40c7 | ||
|
|
a84d525252 | ||
|
|
4ee677154f | ||
|
|
098b78dcd1 | ||
|
|
46a9ecee72 | ||
|
|
c320a486b3 | ||
|
|
21b405d2bb | ||
|
|
9c2cb26376 | ||
|
|
3ca7dc87e7 | ||
|
|
916c95a75e | ||
|
|
dde95019ea | ||
|
|
170a48f83f | ||
|
|
a08f5c35af | ||
|
|
1fe799b445 | ||
|
|
7474241e01 | ||
|
|
7faaa21ac1 | ||
|
|
a6e6c93a50 | ||
|
|
10733f7a5d | ||
|
|
7f6c555310 | ||
|
|
c659c4b007 | ||
|
|
66dcb5eb79 | ||
|
|
6f8fc86236 | ||
|
|
1bbb1eea0e | ||
|
|
b81d0c47cf | ||
|
|
49cf6944f0 | ||
|
|
ca657c8ca8 | ||
|
|
2620d688ec | ||
|
|
799e8c7e02 | ||
|
|
f5b07f6a60 | ||
|
|
599a53b49b | ||
|
|
0e5384b8ad | ||
|
|
715129566d | ||
|
|
106e0ada80 | ||
|
|
7ad616d62b | ||
|
|
40e2564ef9 | ||
|
|
0ab4d16f9d | ||
|
|
d20b41401f | ||
|
|
cca18f3a70 | ||
|
|
07a4ff3e9f | ||
|
|
a414b75ef4 | ||
|
|
12d16fe9ee | ||
|
|
07419275ff | ||
|
|
927115b50b | ||
|
|
a938324886 | ||
|
|
894700cbab | ||
|
|
6ea4f5fd68 | ||
|
|
ca185bb416 | ||
|
|
a3dc20b384 | ||
|
|
fd643c37f5 | ||
|
|
c784630345 | ||
|
|
e6377d4f3d | ||
|
|
d5becc5fc2 | ||
|
|
9d44f91f45 | ||
|
|
c7db2d2b6a | ||
|
|
c8bdd2e52b | ||
|
|
2c7c93b64c | ||
|
|
312c80b355 | ||
|
|
a11b74a595 | ||
|
|
0f5398b064 |
131
.clang-format
Normal file
131
.clang-format
Normal file
@@ -0,0 +1,131 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: true
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 0
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '.'
|
||||
Priority: 2
|
||||
SortPriority: 2
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentCaseLabels: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: All
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report crashes or unexpected behavior
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# WARNING: Filling out the template below is NOT optional. Issues not filling out this template will be closed without review.
|
||||
|
||||
FIRST: Before reporting any bug, make sure that the bug you are reporting has not been reported before. Also, try to use the [nightly version](https://www.sdrpp.org/nightly) if possible in case I've already fixed the bug.
|
||||
|
||||
**Hardware**
|
||||
- CPU:
|
||||
- RAM:
|
||||
- GPU:
|
||||
- SDR: (Remote or local? If remote, what protocol?)
|
||||
|
||||
**Software**
|
||||
- Operating System: Name + Exact version (eg. Windows 10 x64, Ubuntu 22.04, MacOS 10.15)
|
||||
- SDR++: Version + Build date (available either in the window title or in the credits menu which you can access by clicking on the SDR++ icon in the top right corner of the software).
|
||||
|
||||
**Bug Description**
|
||||
A clear description of the bug.
|
||||
|
||||
**Steps To Reproduce**
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
**Only If SDR++ fails to lauch or the SDR fails to start:**
|
||||
Run SDR++ from a command line window with special parameters:
|
||||
* On Windows, open a terminal and `cd` to SDR++'s directory and run `.\sdrpp.exe -c` (if running SDR++ version 1.0.4 or older, use `-s` instead, though you should probably update SDR++ instead...)
|
||||
* On Linux: Open a terminal and run `sdrpp -c`
|
||||
* On MacOS: Open a terminal and run `/path/to/the/SDR++.app/Contents/MacOS/sdrpp -c`
|
||||
Then, post the **entire** logs from start to after the issue. **DOT NOT truncate to where you *think* the error is...**
|
||||
|
||||
**Screenshots**
|
||||
Add any screenshot that is relevant to the bug (GUI error messages, strange behavior, graphics glitch, etc...).
|
||||
|
||||
**Additional info**
|
||||
Add any other relevant information.
|
||||
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Feature description**
|
||||
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.
|
||||
383
.github/workflows/build_all.yml
vendored
383
.github/workflows/build_all.yml
vendored
@@ -1,17 +1,24 @@
|
||||
name: Build Binaries
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- nightly
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- nightly
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build_windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
@@ -27,16 +34,23 @@ jobs:
|
||||
|
||||
- name: Patch Pothos with earlier libusb version
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/"
|
||||
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/"
|
||||
|
||||
- name: Download librtlsdr
|
||||
run: Invoke-WebRequest -Uri "https://ftp.osmocom.org/binaries/windows/rtl-sdr/rtl-sdr-64bit-20240623.zip" -OutFile ${{runner.workspace}}/rtl-sdr.zip
|
||||
|
||||
- name: Patch Pothos with newer librtlsdr version
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: 7z x rtl-sdr.zip ; rm "C:/Program Files/PothosSDR/bin/rtlsdr.dll" ; cp "rtl-sdr-64bit-20240623/librtlsdr.dll" "C:/Program Files/PothosSDR/bin/rtlsdr.dll"
|
||||
|
||||
- name: Download SDRPlay API
|
||||
run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu" -OutFile ${{runner.workspace}}/SDRPlay.zip
|
||||
run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip
|
||||
|
||||
- name: Install SDRPlay API
|
||||
run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/"
|
||||
run: 7z x ${{runner.workspace}}/SDRplay.zip -o"C:/Program Files/"
|
||||
|
||||
- name: Download codec2
|
||||
run: git clone https://github.com/drowe67/codec2
|
||||
run: git clone https://github.com/AlexandreRouma/codec2
|
||||
|
||||
- name: Prepare MinGW
|
||||
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
|
||||
@@ -51,14 +65,23 @@ jobs:
|
||||
run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2"
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: vcpkg install fftw3:x64-windows glew:x64-windows glfw3:x64-windows portaudio:x64-windows
|
||||
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows spdlog:x64-windows
|
||||
|
||||
- name: Install rtaudio
|
||||
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Install libperseus-sdr
|
||||
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos ; cd libfobos ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
|
||||
run: cmake -DCOPY_MSVC_REDISTRIBUTABLES=ON "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@@ -69,29 +92,53 @@ jobs:
|
||||
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_windows_x64
|
||||
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
||||
|
||||
build_macos:
|
||||
runs-on: macos-latest
|
||||
build_macos_intel:
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Update brew repositories
|
||||
run: brew update
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install fftw glew glfw volk airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako
|
||||
|
||||
- name: Install volk
|
||||
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install SDRplay API
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
|
||||
|
||||
- name: Install libiio
|
||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install libad9361
|
||||
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install LimeSuite
|
||||
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install libperseus
|
||||
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && sudo make install && cd ..
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install more recent librtlsdr
|
||||
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=OFF -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
|
||||
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
@@ -99,20 +146,77 @@ jobs:
|
||||
|
||||
- name: Create Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: sh $GITHUB_WORKSPACE/make_macos_package.sh ${{runner.workspace}}/build
|
||||
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_intel.zip SDR++.app
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_macos_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_macos_amd64.pkg
|
||||
|
||||
name: sdrpp_macos_intel
|
||||
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
|
||||
|
||||
build_macos_arm:
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages
|
||||
|
||||
- name: Install volk
|
||||
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install SDRplay API
|
||||
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
|
||||
|
||||
- name: Install libiio
|
||||
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install libad9361
|
||||
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
- name: Install LimeSuite
|
||||
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
|
||||
|
||||
# - name: Install libperseus
|
||||
# run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
|
||||
|
||||
- name: Install librfnm
|
||||
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install libfobos
|
||||
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
|
||||
|
||||
- name: Install more recent librtlsdr
|
||||
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: make VERBOSE=1 -j3
|
||||
|
||||
- name: Create Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_arm.zip SDR++.app
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_macos_arm
|
||||
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
|
||||
|
||||
build_debian_buster:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
|
||||
|
||||
@@ -124,7 +228,7 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_buster_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
@@ -133,7 +237,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
|
||||
@@ -146,16 +250,38 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_bullseye_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_bookworm:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_bookworm_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_sid:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
|
||||
@@ -168,38 +294,16 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_debian_sid_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_bionic:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_ubuntu_bionic_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_focal:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
|
||||
@@ -212,19 +316,19 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_focal_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_groovy:
|
||||
build_ubuntu_jammy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_groovy && docker build . --tag sdrpp_build
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
@@ -234,67 +338,168 @@ jobs:
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_groovy_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_hirsute:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_hirsute && 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@v2
|
||||
with:
|
||||
name: sdrpp_ubuntu_hirsute_amd64
|
||||
name: sdrpp_ubuntu_jammy_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_mantic:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_mantic && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_mantic_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_noble:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_ubuntu_noble_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_raspios_bullseye_armhf:
|
||||
runs-on: ARM
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: make VERBOSE=1 -j3
|
||||
|
||||
- name: Create Dev Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_raspios_bullseye_armhf
|
||||
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
|
||||
|
||||
build_android:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch container
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: git clone https://github.com/AlexandreRouma/android-sdr-kit
|
||||
|
||||
- name: Build container
|
||||
working-directory: ${{runner.workspace}}/android-sdr-kit
|
||||
run: docker build --progress=plain -t android-sdr-kit .
|
||||
|
||||
- name: Build
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus android-sdr-kit /bin/bash -l -c "cd /root/SDRPlusPlus/android && gradle --info assembleDebug"
|
||||
|
||||
- name: Recover APK
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
|
||||
|
||||
- name: Save APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_android
|
||||
path: ${{runner.workspace}}/sdrpp.apk
|
||||
|
||||
create_full_archive:
|
||||
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_bionic', 'build_ubuntu_focal', 'build_ubuntu_groovy', 'build_ubuntu_hirsute']
|
||||
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_ubuntu_noble', 'build_raspios_bullseye_armhf', 'build_android']
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download All Builds
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Create Archive
|
||||
run: >
|
||||
mkdir sdrpp_all &&
|
||||
mv sdrpp_windows_x64/sdrpp_windows_x64.zip sdrpp_all/ &&
|
||||
mv sdrpp_macos_amd64/sdrpp_macos_amd64.pkg sdrpp_all/ &&
|
||||
mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
|
||||
mv sdrpp_macos_arm/sdrpp_macos_arm.zip sdrpp_all/ &&
|
||||
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
|
||||
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
|
||||
mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
|
||||
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
|
||||
mv sdrpp_ubuntu_bionic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_bionic_amd64.deb &&
|
||||
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
||||
mv sdrpp_ubuntu_groovy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_groovy_amd64.deb &&
|
||||
mv sdrpp_ubuntu_hirsute_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_hirsute_amd64.deb
|
||||
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
||||
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
|
||||
mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb &&
|
||||
mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb &&
|
||||
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
|
||||
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdrpp_all
|
||||
path: sdrpp_all/
|
||||
|
||||
update_nightly_release:
|
||||
needs: [create_full_archive]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
|
||||
steps:
|
||||
- name: Download All Builds
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Update Nightly
|
||||
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
|
||||
|
||||
check_spelling:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install codespell
|
||||
run: sudo apt update -y && sudo apt install -y codespell
|
||||
|
||||
- name: Running codespell
|
||||
run: cd $GITHUB_WORKSPACE && codespell -q 2 || true
|
||||
|
||||
check_formatting:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run check_clang_format
|
||||
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -9,7 +9,11 @@ build/
|
||||
*.wav
|
||||
.DS_Store
|
||||
root_dev/
|
||||
root_dev_srv/
|
||||
Folder.DotSettings.user
|
||||
CMakeSettings.json
|
||||
poggers_decoder
|
||||
m17_decoder/libcorrect
|
||||
m17_decoder/libcorrect
|
||||
SDR++.app
|
||||
android/deps
|
||||
android/app/assets
|
||||
235
CMakeLists.txt
235
CMakeLists.txt
@@ -1,11 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(sdrpp)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CMAKE_INSTALL_PREFIX "/usr/local")
|
||||
else()
|
||||
set(CMAKE_INSTALL_PREFIX "/usr")
|
||||
endif()
|
||||
# Backends
|
||||
option(OPT_BACKEND_GLFW "Use the GLFW backend" ON)
|
||||
option(OPT_BACKEND_ANDROID "Use the Android backend" OFF)
|
||||
|
||||
# Compatibility Options
|
||||
option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on systems that don't have it yet" OFF)
|
||||
@@ -13,36 +11,111 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy
|
||||
# Sources
|
||||
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON)
|
||||
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON)
|
||||
option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON)
|
||||
option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF)
|
||||
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
|
||||
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
||||
option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF)
|
||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
|
||||
option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF)
|
||||
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF)
|
||||
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
|
||||
option(OPT_BUILD_SDDC_SOURCE "Build SDDC Source Module (Dependencies: libusb-1.0)" OFF)
|
||||
option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF)
|
||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
|
||||
option(OPT_BUILD_RFNM_SOURCE "Build RFNM Source Module (Dependencies: librfnm)" OFF)
|
||||
option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON)
|
||||
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF)
|
||||
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON)
|
||||
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" OFF)
|
||||
option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF)
|
||||
option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
|
||||
option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF)
|
||||
|
||||
# Sinks
|
||||
option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF)
|
||||
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON)
|
||||
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
|
||||
option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF)
|
||||
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
|
||||
|
||||
# Decoders
|
||||
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
|
||||
option(OPT_BUILD_M17_DECODER "Build the M17 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_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_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
|
||||
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||
|
||||
# Misc
|
||||
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
|
||||
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
|
||||
option(OPT_BUILD_IQ_EXPORTER "Build the IQ Exporter module" ON)
|
||||
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
|
||||
option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
|
||||
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
|
||||
option(OPT_BUILD_SCANNER "Frequency scanner" ON)
|
||||
option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
|
||||
|
||||
# Other options
|
||||
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON)
|
||||
option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF)
|
||||
option(COPY_MSVC_REDISTRIBUTABLES "Copy over the Visual C++ Redistributable" OFF)
|
||||
|
||||
# Module cmake path
|
||||
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
|
||||
|
||||
# Root source folder
|
||||
set(SDRPP_CORE_ROOT "${CMAKE_SOURCE_DIR}/core/src/")
|
||||
|
||||
# Compiler flags
|
||||
if (${CMAKE_BUILD_TYPE} MATCHES "Debug")
|
||||
# Debug Flags
|
||||
if (MSVC)
|
||||
set(SDRPP_COMPILER_FLAGS /std:c++17 /EHsc)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(SDRPP_COMPILER_FLAGS -g -Og -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
|
||||
else ()
|
||||
set(SDRPP_COMPILER_FLAGS -g -Og -std=c++17)
|
||||
endif ()
|
||||
else()
|
||||
# Normal Flags
|
||||
if (MSVC)
|
||||
set(SDRPP_COMPILER_FLAGS /O2 /Ob2 /std:c++17 /EHsc)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(SDRPP_COMPILER_FLAGS -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
|
||||
else ()
|
||||
set(SDRPP_COMPILER_FLAGS -O3 -std=c++17)
|
||||
endif ()
|
||||
endif()
|
||||
set(SDRPP_MODULE_COMPILER_FLAGS ${SDRPP_COMPILER_FLAGS})
|
||||
|
||||
# Set a default install prefix
|
||||
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "..." FORCE)
|
||||
else()
|
||||
set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Configure toolchain for android
|
||||
if (ANDROID)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS
|
||||
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate"
|
||||
)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX14_EXTENSION_COMPILE_OPTION "-std=c++17")
|
||||
endif (ANDROID)
|
||||
|
||||
# Core of SDR++
|
||||
add_subdirectory("core")
|
||||
@@ -56,6 +129,14 @@ if (OPT_BUILD_AIRSPYHF_SOURCE)
|
||||
add_subdirectory("source_modules/airspyhf_source")
|
||||
endif (OPT_BUILD_AIRSPYHF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_AUDIO_SOURCE)
|
||||
add_subdirectory("source_modules/audio_source")
|
||||
endif (OPT_BUILD_AUDIO_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BADGESDR_SOURCE)
|
||||
add_subdirectory("source_modules/badgesdr_source")
|
||||
endif (OPT_BUILD_BADGESDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BLADERF_SOURCE)
|
||||
add_subdirectory("source_modules/bladerf_source")
|
||||
endif (OPT_BUILD_BLADERF_SOURCE)
|
||||
@@ -64,17 +145,49 @@ if (OPT_BUILD_FILE_SOURCE)
|
||||
add_subdirectory("source_modules/file_source")
|
||||
endif (OPT_BUILD_FILE_SOURCE)
|
||||
|
||||
if (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
add_subdirectory("source_modules/fobossdr_source")
|
||||
endif (OPT_BUILD_FOBOSSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HACKRF_SOURCE)
|
||||
add_subdirectory("source_modules/hackrf_source")
|
||||
endif (OPT_BUILD_HACKRF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HAROGIC_SOURCE)
|
||||
add_subdirectory("source_modules/harogic_source")
|
||||
endif (OPT_BUILD_HAROGIC_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HERMES_SOURCE)
|
||||
add_subdirectory("source_modules/hermes_source")
|
||||
endif (OPT_BUILD_HERMES_SOURCE)
|
||||
|
||||
if (OPT_BUILD_KCSDR_SOURCE)
|
||||
add_subdirectory("source_modules/kcsdr_source")
|
||||
endif (OPT_BUILD_KCSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_LIMESDR_SOURCE)
|
||||
add_subdirectory("source_modules/limesdr_source")
|
||||
endif (OPT_BUILD_LIMESDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDDC_SOURCE)
|
||||
add_subdirectory("source_modules/sddc_source")
|
||||
endif (OPT_BUILD_SDDC_SOURCE)
|
||||
if (OPT_BUILD_NETWORK_SOURCE)
|
||||
add_subdirectory("source_modules/network_source")
|
||||
endif (OPT_BUILD_NETWORK_SOURCE)
|
||||
|
||||
if (OPT_BUILD_PERSEUS_SOURCE)
|
||||
add_subdirectory("source_modules/perseus_source")
|
||||
endif (OPT_BUILD_PERSEUS_SOURCE)
|
||||
|
||||
if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("source_modules/plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFNM_SOURCE)
|
||||
add_subdirectory("source_modules/rfnm_source")
|
||||
endif (OPT_BUILD_RFNM_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RFSPACE_SOURCE)
|
||||
add_subdirectory("source_modules/rfspace_source")
|
||||
endif (OPT_BUILD_RFSPACE_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RTL_SDR_SOURCE)
|
||||
add_subdirectory("source_modules/rtl_sdr_source")
|
||||
@@ -84,6 +197,10 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
add_subdirectory("source_modules/rtl_tcp_source")
|
||||
endif (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
add_subdirectory("source_modules/sdrpp_server_source")
|
||||
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDRPLAY_SOURCE)
|
||||
add_subdirectory("source_modules/sdrplay_source")
|
||||
endif (OPT_BUILD_SDRPLAY_SOURCE)
|
||||
@@ -92,16 +209,28 @@ if (OPT_BUILD_SOAPY_SOURCE)
|
||||
add_subdirectory("source_modules/soapy_source")
|
||||
endif (OPT_BUILD_SOAPY_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SPECTRAN_SOURCE)
|
||||
add_subdirectory("source_modules/spectran_source")
|
||||
endif (OPT_BUILD_SPECTRAN_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SPECTRAN_HTTP_SOURCE)
|
||||
add_subdirectory("source_modules/spectran_http_source")
|
||||
endif (OPT_BUILD_SPECTRAN_HTTP_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
add_subdirectory("source_modules/spyserver_source")
|
||||
endif (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
|
||||
if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("source_modules/plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
if (OPT_BUILD_USRP_SOURCE)
|
||||
add_subdirectory("source_modules/usrp_source")
|
||||
endif (OPT_BUILD_USRP_SOURCE)
|
||||
|
||||
|
||||
# Sink modules
|
||||
if (OPT_BUILD_ANDROID_AUDIO_SINK)
|
||||
add_subdirectory("sink_modules/android_audio_sink")
|
||||
endif (OPT_BUILD_ANDROID_AUDIO_SINK)
|
||||
|
||||
if (OPT_BUILD_AUDIO_SINK)
|
||||
add_subdirectory("sink_modules/audio_sink")
|
||||
endif (OPT_BUILD_AUDIO_SINK)
|
||||
@@ -120,10 +249,22 @@ endif (OPT_BUILD_NEW_PORTAUDIO_SINK)
|
||||
|
||||
|
||||
# Decoders
|
||||
if (OPT_BUILD_ATV_DECODER)
|
||||
add_subdirectory("decoder_modules/atv_decoder")
|
||||
endif (OPT_BUILD_ATV_DECODER)
|
||||
|
||||
if (OPT_BUILD_DAB_DECODER)
|
||||
add_subdirectory("decoder_modules/dab_decoder")
|
||||
endif (OPT_BUILD_DAB_DECODER)
|
||||
|
||||
if (OPT_BUILD_FALCON9_DECODER)
|
||||
add_subdirectory("decoder_modules/falcon9_decoder")
|
||||
endif (OPT_BUILD_FALCON9_DECODER)
|
||||
|
||||
if (OPT_BUILD_KG_SSTV_DECODER)
|
||||
add_subdirectory("decoder_modules/kg_sstv_decoder")
|
||||
endif (OPT_BUILD_KG_SSTV_DECODER)
|
||||
|
||||
if (OPT_BUILD_M17_DECODER)
|
||||
add_subdirectory("decoder_modules/m17_decoder")
|
||||
endif (OPT_BUILD_M17_DECODER)
|
||||
@@ -132,10 +273,18 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
add_subdirectory("decoder_modules/meteor_demodulator")
|
||||
endif (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
|
||||
if (OPT_BUILD_PAGER_DECODER)
|
||||
add_subdirectory("decoder_modules/pager_decoder")
|
||||
endif (OPT_BUILD_PAGER_DECODER)
|
||||
|
||||
if (OPT_BUILD_RADIO)
|
||||
add_subdirectory("decoder_modules/radio")
|
||||
endif (OPT_BUILD_RADIO)
|
||||
|
||||
if (OPT_BUILD_RYFI_DECODER)
|
||||
add_subdirectory("decoder_modules/ryfi_decoder")
|
||||
endif (OPT_BUILD_RYFI_DECODER)
|
||||
|
||||
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
@@ -150,31 +299,60 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
|
||||
add_subdirectory("misc_modules/frequency_manager")
|
||||
endif (OPT_BUILD_FREQUENCY_MANAGER)
|
||||
|
||||
if (OPT_BUILD_IQ_EXPORTER)
|
||||
add_subdirectory("misc_modules/iq_exporter")
|
||||
endif (OPT_BUILD_IQ_EXPORTER)
|
||||
|
||||
if (OPT_BUILD_RECORDER)
|
||||
add_subdirectory("misc_modules/recorder")
|
||||
endif (OPT_BUILD_RECORDER)
|
||||
|
||||
if (OPT_BUILD_RIGCTL_CLIENT)
|
||||
add_subdirectory("misc_modules/rigctl_client")
|
||||
endif (OPT_BUILD_RIGCTL_CLIENT)
|
||||
|
||||
if (OPT_BUILD_RIGCTL_SERVER)
|
||||
add_subdirectory("misc_modules/rigctl_server")
|
||||
endif (OPT_BUILD_RIGCTL_SERVER)
|
||||
|
||||
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||
target_link_libraries(sdrpp PRIVATE sdrpp_core)
|
||||
if (OPT_BUILD_SCANNER)
|
||||
add_subdirectory("misc_modules/scanner")
|
||||
endif (OPT_BUILD_SCANNER)
|
||||
|
||||
if (OPT_BUILD_SCHEDULER)
|
||||
add_subdirectory("misc_modules/scheduler")
|
||||
endif (OPT_BUILD_SCHEDULER)
|
||||
|
||||
# Compiler arguments for each platform
|
||||
if (MSVC)
|
||||
target_compile_options(sdrpp PRIVATE /O2 /Ob2 /std:c++17 /EHsc)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
target_compile_options(sdrpp PRIVATE -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
|
||||
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||
else ()
|
||||
target_compile_options(sdrpp PRIVATE -O3 -std=c++17)
|
||||
add_executable(sdrpp "src/main.cpp")
|
||||
endif ()
|
||||
|
||||
target_link_libraries(sdrpp PRIVATE sdrpp_core)
|
||||
|
||||
# Compiler arguments
|
||||
target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
|
||||
|
||||
# Copy dynamic libs over
|
||||
if (MSVC)
|
||||
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
|
||||
|
||||
if (COPY_MSVC_REDISTRIBUTABLES)
|
||||
# Get the list of Visual C++ runtime DLLs
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP True)
|
||||
include(InstallRequiredSystemLibraries)
|
||||
|
||||
# Create a space sperated list
|
||||
set(REDIST_DLLS_STR "")
|
||||
foreach(DLL IN LISTS CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS)
|
||||
set(REDIST_DLLS_STR COMMAND xcopy /F \"${DLL}\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y ${REDIST_DLLS_STR})
|
||||
endforeach()
|
||||
|
||||
# Create target
|
||||
add_custom_target(do_always_msvc ALL ${REDIST_DLLS_STR})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
||||
@@ -195,7 +373,10 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PAGER_DECODER=ON
|
||||
|
||||
# Create module cmake file
|
||||
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
|
||||
|
||||
# Install directives
|
||||
install(TARGETS sdrpp DESTINATION bin)
|
||||
@@ -207,9 +388,11 @@ install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/themes DESTINATION share/sdrpp)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/sdrpp.desktop ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop @ONLY)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop DESTINATION /usr/share/applications)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop DESTINATION share/applications)
|
||||
endif ()
|
||||
|
||||
# Create uninstall target
|
||||
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
|
||||
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
|
||||
|
||||
# Create headers target
|
||||
12
android/.gitignore
vendored
Normal file
12
android/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
.cxx
|
||||
.externalNativeBuild
|
||||
build/
|
||||
*.iml
|
||||
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
|
||||
# Android Studio puts a Gradle wrapper here, that we don't want:
|
||||
gradle/
|
||||
gradlew*
|
||||
76
android/app/build.gradle
Normal file
76
android/app/build.gradle
Normal file
@@ -0,0 +1,76 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion "30.0.3"
|
||||
ndkVersion "25.1.8937393"
|
||||
defaultConfig {
|
||||
applicationId "org.sdrpp.sdrpp"
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.2.0"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file("debug.keystore")
|
||||
storePassword "android"
|
||||
keyAlias "androiddebugkey"
|
||||
keyPassword "android"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
|
||||
}
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version "3.18.1"
|
||||
path "../../CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
assets.srcDirs += ['assets']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task deleteTempAssets (type: Delete) {
|
||||
delete 'assets'
|
||||
}
|
||||
|
||||
task copyResources(type: Copy) {
|
||||
description = 'Copy resources...'
|
||||
from '../../root/'
|
||||
into 'assets/'
|
||||
include('**/*')
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
}
|
||||
|
||||
copyResources.dependsOn deleteTempAssets
|
||||
preBuild.dependsOn copyResources
|
||||
BIN
android/app/debug.keystore
Normal file
BIN
android/app/debug.keystore
Normal file
Binary file not shown.
28
android/app/src/main/AndroidManifest.xml
Normal file
28
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.sdrpp.sdrpp">
|
||||
|
||||
<application
|
||||
android:label="SDR++"
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:hasCode="true">
|
||||
|
||||
<activity
|
||||
android:name="org.sdrpp.sdrpp.MainActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize">
|
||||
<meta-data android:name="android.app.lib_name" android:value="sdrpp_core" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
32
android/app/src/main/java/DeviceManager.kt
Normal file
32
android/app/src/main/java/DeviceManager.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
package org.sdrpp.sdrpp;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.*;
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.util.Log;
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import androidx.core.content.PermissionChecker;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.io.*;
|
||||
|
||||
class DeviceManager {
|
||||
public fun init() {
|
||||
|
||||
}
|
||||
}
|
||||
192
android/app/src/main/java/MainActivity.kt
Normal file
192
android/app/src/main/java/MainActivity.kt
Normal file
@@ -0,0 +1,192 @@
|
||||
package org.sdrpp.sdrpp;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.*;
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.util.Log;
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import androidx.core.content.PermissionChecker;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.io.*;
|
||||
|
||||
private const val ACTION_USB_PERMISSION = "org.sdrpp.sdrpp.USB_PERMISSION";
|
||||
|
||||
private val usbReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (ACTION_USB_PERMISSION == intent.action) {
|
||||
synchronized(this) {
|
||||
var _this = context as MainActivity;
|
||||
_this.SDR_device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||
_this.SDR_conn = _this.usbManager!!.openDevice(_this.SDR_device);
|
||||
|
||||
// Save SDR info
|
||||
_this.SDR_VID = _this.SDR_device!!.getVendorId();
|
||||
_this.SDR_PID = _this.SDR_device!!.getProductId()
|
||||
_this.SDR_FD = _this.SDR_conn!!.getFileDescriptor();
|
||||
}
|
||||
|
||||
// Whatever the hell this does
|
||||
context.unregisterReceiver(this);
|
||||
|
||||
// Hide again the system bars
|
||||
_this.hideSystemBars();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MainActivity : NativeActivity() {
|
||||
private val TAG : String = "SDR++";
|
||||
public var usbManager : UsbManager? = null;
|
||||
public var SDR_device : UsbDevice? = null;
|
||||
public var SDR_conn : UsbDeviceConnection? = null;
|
||||
public var SDR_VID : Int = -1;
|
||||
public var SDR_PID : Int = -1;
|
||||
public var SDR_FD : Int = -1;
|
||||
|
||||
fun checkAndAsk(permission: String) {
|
||||
if (PermissionChecker.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(permission), 1);
|
||||
}
|
||||
}
|
||||
|
||||
public fun hideSystemBars() {
|
||||
val decorView = getWindow().getDecorView();
|
||||
val uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
decorView.setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// Hide bars
|
||||
hideSystemBars();
|
||||
|
||||
// Ask for required permissions, without these the app cannot run.
|
||||
checkAndAsk(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
checkAndAsk(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
|
||||
// TODO: Have the main code wait until these two permissions are available
|
||||
|
||||
// Register events
|
||||
usbManager = getSystemService(Context.USB_SERVICE) as UsbManager;
|
||||
val permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0)
|
||||
val filter = IntentFilter(ACTION_USB_PERMISSION)
|
||||
registerReceiver(usbReceiver, filter)
|
||||
|
||||
// Get permission for all USB devices
|
||||
val devList = usbManager!!.getDeviceList();
|
||||
for ((name, dev) in devList) {
|
||||
usbManager!!.requestPermission(dev, permissionIntent);
|
||||
}
|
||||
|
||||
// Ask for internet permission
|
||||
checkAndAsk(Manifest.permission.INTERNET);
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
public override fun onResume() {
|
||||
// Hide bars again
|
||||
hideSystemBars();
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
fun showSoftInput() {
|
||||
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager;
|
||||
inputMethodManager.showSoftInput(window.decorView, 0);
|
||||
}
|
||||
|
||||
fun hideSoftInput() {
|
||||
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager;
|
||||
inputMethodManager.hideSoftInputFromWindow(window.decorView.windowToken, 0);
|
||||
hideSystemBars();
|
||||
}
|
||||
|
||||
// Queue for the Unicode characters to be polled from native code (via pollUnicodeChar())
|
||||
private var unicodeCharacterQueue: LinkedBlockingQueue<Int> = LinkedBlockingQueue()
|
||||
|
||||
// We assume dispatchKeyEvent() of the NativeActivity is actually called for every
|
||||
// KeyEvent and not consumed by any View before it reaches here
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
unicodeCharacterQueue.offer(event.getUnicodeChar(event.metaState))
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
fun pollUnicodeChar(): Int {
|
||||
return unicodeCharacterQueue.poll() ?: 0
|
||||
}
|
||||
|
||||
public fun createIfDoesntExist(path: String) {
|
||||
// This is a directory, create it in the filesystem
|
||||
var folder = File(path);
|
||||
var success = true;
|
||||
if (!folder.exists()) {
|
||||
success = folder.mkdirs();
|
||||
}
|
||||
if (!success) {
|
||||
Log.e(TAG, "Could not create folder with path " + path);
|
||||
}
|
||||
}
|
||||
|
||||
public fun extractDir(aman: AssetManager, local: String, rsrc: String): Int {
|
||||
val flist = aman.list(rsrc);
|
||||
var ecount = 0;
|
||||
for (fp in flist) {
|
||||
val lpath = local + "/" + fp;
|
||||
val rpath = rsrc + "/" + fp;
|
||||
|
||||
Log.w(TAG, "Extracting '" + rpath + "' to '" + lpath + "'");
|
||||
|
||||
// Create local path if non-existent
|
||||
createIfDoesntExist(local);
|
||||
|
||||
// Create if directory
|
||||
val ext = extractDir(aman, lpath, rpath);
|
||||
|
||||
// Extract if file
|
||||
if (ext == 0) {
|
||||
// This is a file, extract it
|
||||
val _os = FileOutputStream(lpath);
|
||||
val _is = aman.open(rpath);
|
||||
val ilen = _is.available();
|
||||
var fbuf = ByteArray(ilen);
|
||||
_is.read(fbuf, 0, ilen);
|
||||
_os.write(fbuf);
|
||||
_os.close();
|
||||
_is.close();
|
||||
}
|
||||
|
||||
ecount++;
|
||||
}
|
||||
return ecount;
|
||||
}
|
||||
|
||||
public fun getAppDir(): String {
|
||||
val fdir = getFilesDir().getAbsolutePath();
|
||||
|
||||
// Extract all resources to the app directory
|
||||
val aman = getAssets();
|
||||
extractDir(aman, fdir + "/res", "res");
|
||||
createIfDoesntExist(fdir + "/modules");
|
||||
|
||||
return fdir;
|
||||
}
|
||||
}
|
||||
BIN
android/app/src/main/res/mipmap/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
22
android/build.gradle
Normal file
22
android/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.31'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
1
android/gradle.properties
Normal file
1
android/gradle.properties
Normal file
@@ -0,0 +1 @@
|
||||
android.useAndroidX=true
|
||||
1
android/settings.gradle
Normal file
1
android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||
20
check_clang_format.sh
Normal file
20
check_clang_format.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo Searching directories...
|
||||
CODE_FILES=$(find . -iregex '.*\.\(h\|hpp\|c\|cpp\)$')
|
||||
while read -r CPP_FILE_PATH; do
|
||||
# Skip unwanted files
|
||||
if [[ "$CPP_FILE_PATH" == "./.old"* ]]; then continue; fi
|
||||
if [[ "$CPP_FILE_PATH" == "./build"* ]]; then continue; fi
|
||||
if [[ "$CPP_FILE_PATH" == "./core/libcorrect"* ]]; then continue; fi
|
||||
if [[ "$CPP_FILE_PATH" == "./core/std_replacement"* ]]; then continue; fi
|
||||
if [[ "$CPP_FILE_PATH" == "./core/src/imgui"* ]]; then continue; fi
|
||||
if [[ "$CPP_FILE_PATH" == "./misc_modules/discord_integration/discord-rpc"* ]]; then continue; fi
|
||||
if [[ "$CPP_FILE_PATH" == "./source_modules/sddc_source/src/libsddc"* ]]; then continue; fi
|
||||
|
||||
if [ "$CPP_FILE_PATH" = ./core/src/json.hpp ]; then continue; fi
|
||||
if [ "$CPP_FILE_PATH" = ./core/src/gui/file_dialogs.h ]; then continue; fi
|
||||
|
||||
echo Checking $CPP_FILE_PATH
|
||||
clang-format --style=file -i -n -Werror $CPP_FILE_PATH
|
||||
done <<< "$CODE_FILES"
|
||||
@@ -1,64 +1,6 @@
|
||||
# Pull Requests
|
||||
|
||||
TODO
|
||||
|
||||
# Code Style
|
||||
|
||||
## Naming Convention
|
||||
|
||||
- Files: `snake_case.h` `snake_case.cpp`
|
||||
- Namespaces: `CamelCase`
|
||||
- Classes: `CamelCase`
|
||||
- Structs: `CamelCase_t`
|
||||
- Members: `camelCase`
|
||||
- Enum: `SNAKE_CASE`
|
||||
- Macros: `SNAKE_CASE`
|
||||
|
||||
## Brace Style
|
||||
|
||||
```c++
|
||||
int myFunction() {
|
||||
if (shortIf) { shortFunctionName(); }
|
||||
|
||||
if (longIf) {
|
||||
longFunction();
|
||||
otherStuff();
|
||||
myLongFunction();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: If it makes the code cleaner, remember to use the `?` keyword instead of a `if else` statement.
|
||||
|
||||
## Pointers
|
||||
|
||||
Please use `type* name` for pointers.
|
||||
|
||||
## Structure
|
||||
|
||||
Headers and their associated C++ files shall be in the same directory. All headers must use `#pragma once` instead of other include guards. Only include files in a header that are being used in that header. Include the rest in the associated C++ file.
|
||||
|
||||
# Modules
|
||||
|
||||
## Module Naming Convention
|
||||
|
||||
All modules names must be `snake_case`. If the module is a source, it must end with `_source`. If it is a sink, it must end with `_sink`.
|
||||
|
||||
For example, lets take the module named `cool_source`:
|
||||
|
||||
- Directory: `cool_source`
|
||||
- Class: `CoolSourceModule`
|
||||
- Binary: `cool_source.<os dynlib extension>`
|
||||
|
||||
## Integration into main repository
|
||||
|
||||
If the module meets the code quality requirements, it may be added to the official repository. A module that doesn't require any external dependencies that the core doesn't already use may be enabled for build by default. Otherwise, they must be disabled for build by default with a `OPT_BUILD_MODULE_NAME` variable set to `OFF`.
|
||||
|
||||
# JSON Formatting
|
||||
|
||||
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSOn files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
|
||||
|
||||
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
||||
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
|
||||
|
||||
## Band Frequency Allocation
|
||||
|
||||
@@ -75,13 +17,13 @@ Please follow this guide to properly format the JSON files for custom radio band
|
||||
// Bands in this array must be sorted by their starting frequency
|
||||
{
|
||||
"name": "Name of the band",
|
||||
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type decalre in config.json)",
|
||||
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type declared in config.json)",
|
||||
"start": 148500, //In Hz, must be an integer
|
||||
"end": 283500 //In Hz, must be an integer
|
||||
},
|
||||
{
|
||||
"name": "Name of the band",
|
||||
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type decalre in config.json)",
|
||||
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type declared in config.json)",
|
||||
"start": 526500, //In Hz, must be an integer
|
||||
"end": 1606500 //In Hz, must be an integer
|
||||
}
|
||||
@@ -98,7 +40,7 @@ Please follow this guide to properly format the JSON files for custom color maps
|
||||
"name": "Short name (has to fit in the menu)",
|
||||
"author": "Name of the original/main creator of the color map",
|
||||
"map": [
|
||||
// These are the color codes, in hexadecimal (#RRGGBB) format, for the custom color scales for the waterfall. They must be entered as strings, not integers, with the hastag/pound-symbol proceeding the 6 digit number.
|
||||
// These are the color codes, in hexadecimal (#RRGGBB) format, for the custom color scales for the waterfall. They must be entered as strings, not integers, with the hashtag/pound-symbol proceeding the 6 digit number.
|
||||
"#000020",
|
||||
"#000030",
|
||||
"#000050",
|
||||
@@ -118,8 +60,8 @@ Please follow this guide to properly format the JSON files for custom color maps
|
||||
}
|
||||
```
|
||||
|
||||
# Best Practices
|
||||
# JSON Formatting
|
||||
|
||||
* All additions and/or bug fixes to the core must not add additional dependencies.
|
||||
* Use VSCode for development, VS seems to cause issues.
|
||||
* DO NOT use libboost for any code meant for this repository
|
||||
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
|
||||
|
||||
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
||||
@@ -1,28 +1,37 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(sdrpp_core)
|
||||
|
||||
add_subdirectory("libcorrect/")
|
||||
if (USE_INTERNAL_LIBCORRECT)
|
||||
add_subdirectory("libcorrect/")
|
||||
endif (USE_INTERNAL_LIBCORRECT)
|
||||
|
||||
if (USE_BUNDLE_DEFAULTS)
|
||||
add_definitions(-DIS_MACOS_BUNDLE)
|
||||
endif (USE_BUNDLE_DEFAULTS)
|
||||
|
||||
# Main code
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
add_definitions(-DSDRPP_IS_CORE)
|
||||
add_definitions(-DFLOG_ANDROID_TAG="SDR++")
|
||||
if (MSVC)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif ()
|
||||
|
||||
# Configure backend sources
|
||||
if (OPT_BACKEND_GLFW)
|
||||
file(GLOB_RECURSE BACKEND_SRC "backends/glfw/*.cpp" "backends/glfw/*.c")
|
||||
endif (OPT_BACKEND_GLFW)
|
||||
if (OPT_BACKEND_ANDROID)
|
||||
file(GLOB_RECURSE BACKEND_SRC "backends/android/*.cpp" "backends/android/*.c")
|
||||
set(BACKEND_SRC ${BACKEND_SRC} ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
|
||||
endif (OPT_BACKEND_ANDROID)
|
||||
|
||||
# Add code to dyn lib
|
||||
add_library(sdrpp_core SHARED ${SRC})
|
||||
add_library(sdrpp_core SHARED ${SRC} ${BACKEND_SRC})
|
||||
|
||||
# Set compiler options
|
||||
if (MSVC)
|
||||
target_compile_options(sdrpp_core PRIVATE /O2 /Ob2 /std:c++17 /EHsc)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
target_compile_options(sdrpp_core PRIVATE -O3 -std=c++17)
|
||||
else ()
|
||||
target_compile_options(sdrpp_core PRIVATE -O3 -std=c++17)
|
||||
endif ()
|
||||
|
||||
target_compile_options(sdrpp_core PRIVATE ${SDRPP_COMPILER_FLAGS})
|
||||
|
||||
# Set the install prefix
|
||||
target_compile_definitions(sdrpp_core PUBLIC INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
|
||||
@@ -31,12 +40,37 @@ target_compile_definitions(sdrpp_core PUBLIC INSTALL_PREFIX="${CMAKE_INSTALL_PRE
|
||||
target_include_directories(sdrpp_core PUBLIC "src/")
|
||||
target_include_directories(sdrpp_core PUBLIC "src/imgui")
|
||||
|
||||
# Link to linkcorrect
|
||||
target_include_directories(sdrpp_core PUBLIC "libcorrect/include")
|
||||
target_link_libraries(sdrpp_core PUBLIC correct_static)
|
||||
# Configure backend includes and libraries
|
||||
if (OPT_BACKEND_GLFW)
|
||||
target_include_directories(sdrpp_core PUBLIC "backends/glfw")
|
||||
target_include_directories(sdrpp_core PUBLIC "backends/glfw/imgui")
|
||||
|
||||
if (MSVC)
|
||||
# GLFW3
|
||||
find_package(glfw3 CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC glfw)
|
||||
else()
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(GLFW3 REQUIRED glfw3)
|
||||
|
||||
target_include_directories(sdrpp_core PUBLIC ${GLFW3_INCLUDE_DIRS})
|
||||
target_link_directories(sdrpp_core PUBLIC ${GLFW3_LIBRARY_DIRS})
|
||||
target_link_libraries(sdrpp_core PUBLIC ${GLFW3_LIBRARIES})
|
||||
endif()
|
||||
endif (OPT_BACKEND_GLFW)
|
||||
if (OPT_BACKEND_ANDROID)
|
||||
target_include_directories(sdrpp_core PUBLIC "backends/android")
|
||||
target_include_directories(sdrpp_core PUBLIC "backends/android/imgui")
|
||||
endif (OPT_BACKEND_ANDROID)
|
||||
|
||||
# Link to libcorrect
|
||||
if (USE_INTERNAL_LIBCORRECT)
|
||||
target_include_directories(sdrpp_core PUBLIC "libcorrect/include")
|
||||
target_link_libraries(sdrpp_core PUBLIC correct_static)
|
||||
endif (USE_INTERNAL_LIBCORRECT)
|
||||
|
||||
if (OPT_OVERRIDE_STD_FILESYSTEM)
|
||||
target_include_directories(sdrpp_core PUBLIC "std_replacement")
|
||||
target_include_directories(sdrpp_core PUBLIC "std_replacement")
|
||||
endif (OPT_OVERRIDE_STD_FILESYSTEM)
|
||||
|
||||
if (MSVC)
|
||||
@@ -49,9 +83,9 @@ if (MSVC)
|
||||
# Volk
|
||||
target_link_libraries(sdrpp_core PUBLIC volk)
|
||||
|
||||
# Glew
|
||||
find_package(GLEW REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC GLEW::GLEW)
|
||||
# OpenGL
|
||||
find_package(OpenGL REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC OpenGL::GL)
|
||||
|
||||
# GLFW3
|
||||
find_package(glfw3 CONFIG REQUIRED)
|
||||
@@ -62,39 +96,66 @@ if (MSVC)
|
||||
target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f)
|
||||
|
||||
# WinSock2
|
||||
target_link_libraries(sdrpp_core PUBLIC wsock32 ws2_32)
|
||||
target_link_libraries(sdrpp_core PUBLIC wsock32 ws2_32 iphlpapi)
|
||||
|
||||
# ZSTD
|
||||
find_package(zstd CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC zstd::libzstd_shared)
|
||||
elseif (ANDROID)
|
||||
target_include_directories(sdrpp_core PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/include
|
||||
${ANDROID_NDK}/sources/android/native_app_glue
|
||||
)
|
||||
|
||||
target_link_libraries(sdrpp_core PUBLIC
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libvolk.so
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so
|
||||
/sdr-kit/${ANDROID_ABI}/lib/libzstd.so
|
||||
android
|
||||
EGL
|
||||
GLESv3
|
||||
log
|
||||
)
|
||||
else()
|
||||
find_package(PkgConfig)
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
pkg_check_modules(GLEW REQUIRED glew)
|
||||
pkg_check_modules(FFTW3 REQUIRED fftw3f)
|
||||
pkg_check_modules(VOLK REQUIRED volk)
|
||||
pkg_check_modules(GLFW3 REQUIRED glfw3)
|
||||
pkg_check_modules(LIBZSTD REQUIRED libzstd)
|
||||
|
||||
target_include_directories(sdrpp_core PUBLIC
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
${OPENGL_INCLUDE_DIRS}
|
||||
${FFTW3_INCLUDE_DIRS}
|
||||
${GLFW3_INCLUDE_DIRS}
|
||||
${VOLK_INCLUDE_DIRS}
|
||||
${LIBZSTD_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
target_link_directories(sdrpp_core PUBLIC
|
||||
${GLEW_LIBRARY_DIRS}
|
||||
${OPENGL_LIBRARY_DIRS}
|
||||
${FFTW3_LIBRARY_DIRS}
|
||||
${GLFW3_LIBRARY_DIRS}
|
||||
${VOLK_LIBRARY_DIRS}
|
||||
${LIBZSTD_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(sdrpp_core PUBLIC
|
||||
${OPENGL_LIBRARIES}
|
||||
${GLEW_LIBRARIES}
|
||||
${FFTW3_LIBRARIES}
|
||||
${GLFW3_LIBRARIES}
|
||||
${VOLK_LIBRARIES}
|
||||
${LIBZSTD_LIBRARIES}
|
||||
)
|
||||
|
||||
if (NOT USE_INTERNAL_LIBCORRECT)
|
||||
pkg_check_modules(CORRECT REQUIRED libcorrect)
|
||||
target_include_directories(sdrpp_core PUBLIC ${CORRECT_INCLUDE_DIRS})
|
||||
target_link_directories(sdrpp_core PUBLIC ${CORRECT_LIBRARY_DIRS})
|
||||
target_link_libraries(sdrpp_core PUBLIC ${CORRECT_LIBRARIES})
|
||||
endif (NOT USE_INTERNAL_LIBCORRECT)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
target_link_libraries(sdrpp_core PUBLIC stdc++fs)
|
||||
endif ()
|
||||
|
||||
17
core/backends/android/android_backend.h
Normal file
17
core/backends/android/android_backend.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace backend {
|
||||
struct DevVIDPID {
|
||||
uint16_t vid;
|
||||
uint16_t pid;
|
||||
};
|
||||
|
||||
extern const std::vector<DevVIDPID> AIRSPY_VIDPIDS;
|
||||
extern const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS;
|
||||
extern const std::vector<DevVIDPID> HACKRF_VIDPIDS;
|
||||
extern const std::vector<DevVIDPID> RTL_SDR_VIDPIDS;
|
||||
|
||||
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids);
|
||||
}
|
||||
483
core/backends/android/backend.cpp
Normal file
483
core/backends/android/backend.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
#include <backend.h>log
|
||||
#include "android_backend.h"
|
||||
#include <core.h>
|
||||
#include <gui/gui.h>
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_android.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <android/log.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#include <android/asset_manager.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <stdint.h>
|
||||
#include <gui/icons.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/menus/theme.h>
|
||||
#include <filesystem>
|
||||
|
||||
// Credit to the ImGui android OpenGL3 example for a lot of this code!
|
||||
|
||||
namespace backend {
|
||||
struct android_app* app = NULL;
|
||||
EGLDisplay _EglDisplay = EGL_NO_DISPLAY;
|
||||
EGLSurface _EglSurface = EGL_NO_SURFACE;
|
||||
EGLContext _EglContext = EGL_NO_CONTEXT;
|
||||
bool _Initialized = false;
|
||||
char _LogTag[] = "SDR++";
|
||||
bool initialized = false;
|
||||
bool pauseRendering = false;
|
||||
bool exited = false;
|
||||
|
||||
// Forward declaration
|
||||
int ShowSoftKeyboardInput();
|
||||
int PollUnicodeChars();
|
||||
|
||||
void doPartialInit() {
|
||||
std::string root = (std::string)core::args["root"];
|
||||
backend::init();
|
||||
style::loadFonts(root + "/res"); // TODO: Don't hardcode, use config
|
||||
icons::load(root + "/res");
|
||||
thememenu::applyTheme();
|
||||
ImGui::GetStyle().ScaleAllSizes(style::uiScale);
|
||||
gui::mainWindow.setFirstMenuRender();
|
||||
}
|
||||
|
||||
void handleAppCmd(struct android_app* app, int32_t appCmd) {
|
||||
switch (appCmd) {
|
||||
case APP_CMD_SAVE_STATE:
|
||||
flog::warn("APP_CMD_SAVE_STATE");
|
||||
break;
|
||||
case APP_CMD_INIT_WINDOW:
|
||||
flog::warn("APP_CMD_INIT_WINDOW");
|
||||
if (pauseRendering && !exited) {
|
||||
doPartialInit();
|
||||
pauseRendering = false;
|
||||
}
|
||||
exited = false;
|
||||
break;
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
flog::warn("APP_CMD_TERM_WINDOW");
|
||||
pauseRendering = true;
|
||||
backend::end();
|
||||
break;
|
||||
case APP_CMD_GAINED_FOCUS:
|
||||
flog::warn("APP_CMD_GAINED_FOCUS");
|
||||
break;
|
||||
case APP_CMD_LOST_FOCUS:
|
||||
flog::warn("APP_CMD_LOST_FOCUS");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t handleInputEvent(struct android_app* app, AInputEvent* inputEvent) {
|
||||
return ImGui_ImplAndroid_HandleInputEvent(inputEvent);
|
||||
}
|
||||
|
||||
int aquireWindow() {
|
||||
while (!app->window) {
|
||||
flog::warn("Waiting on the shitty window thing"); std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
int out_events;
|
||||
struct android_poll_source* out_data;
|
||||
|
||||
while (ALooper_pollAll(0, NULL, &out_events, (void**)&out_data) >= 0) {
|
||||
// Process one event
|
||||
if (out_data != NULL) { out_data->process(app, out_data); }
|
||||
|
||||
// Exit the app by returning from within the infinite loop
|
||||
if (app->destroyRequested != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
ANativeWindow_acquire(app->window);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init(std::string resDir) {
|
||||
flog::warn("Backend init");
|
||||
|
||||
// Get window
|
||||
aquireWindow();
|
||||
|
||||
// EGL Init
|
||||
{
|
||||
_EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (_EglDisplay == EGL_NO_DISPLAY)
|
||||
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglGetDisplay(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY");
|
||||
|
||||
if (eglInitialize(_EglDisplay, 0, 0) != EGL_TRUE)
|
||||
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglInitialize() returned with an error");
|
||||
|
||||
const EGLint egl_attributes[] = { EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE };
|
||||
EGLint num_configs = 0;
|
||||
if (eglChooseConfig(_EglDisplay, egl_attributes, nullptr, 0, &num_configs) != EGL_TRUE)
|
||||
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglChooseConfig() returned with an error");
|
||||
if (num_configs == 0)
|
||||
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglChooseConfig() returned 0 matching config");
|
||||
|
||||
// Get the first matching config
|
||||
EGLConfig egl_config;
|
||||
eglChooseConfig(_EglDisplay, egl_attributes, &egl_config, 1, &num_configs);
|
||||
EGLint egl_format;
|
||||
eglGetConfigAttrib(_EglDisplay, egl_config, EGL_NATIVE_VISUAL_ID, &egl_format);
|
||||
ANativeWindow_setBuffersGeometry(app->window, 0, 0, egl_format);
|
||||
|
||||
const EGLint egl_context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
|
||||
_EglContext = eglCreateContext(_EglDisplay, egl_config, EGL_NO_CONTEXT, egl_context_attributes);
|
||||
|
||||
if (_EglContext == EGL_NO_CONTEXT)
|
||||
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglCreateContext() returned EGL_NO_CONTEXT");
|
||||
|
||||
_EglSurface = eglCreateWindowSurface(_EglDisplay, egl_config, app->window, NULL);
|
||||
eglMakeCurrent(_EglDisplay, _EglSurface, _EglSurface, _EglContext);
|
||||
}
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
(void)io;
|
||||
|
||||
// Disable loading/saving of .ini file from disk.
|
||||
// FIXME: Consider using LoadIniSettingsFromMemory() / SaveIniSettingsToMemory() to save in appropriate location for Android.
|
||||
io.IniFilename = NULL;
|
||||
|
||||
// Setup Platform/Renderer backends
|
||||
ImGui_ImplAndroid_Init(app->window);
|
||||
ImGui_ImplOpenGL3_Init("#version 300 es");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void beginFrame() {
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplAndroid_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void render(bool vsync) {
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
auto dSize = ImGui::GetIO().DisplaySize;
|
||||
glViewport(0, 0, dSize.x, dSize.y);
|
||||
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
eglSwapBuffers(_EglDisplay, _EglSurface);
|
||||
}
|
||||
|
||||
// No screen pos to detect
|
||||
void getMouseScreenPos(double& x, double& y) { x = 0; y = 0; }
|
||||
void setMouseScreenPos(double x, double y) {}
|
||||
|
||||
int renderLoop() {
|
||||
while (true) {
|
||||
int out_events;
|
||||
struct android_poll_source* out_data;
|
||||
|
||||
while (ALooper_pollAll(0, NULL, &out_events, (void**)&out_data) >= 0) {
|
||||
// Process one event
|
||||
if (out_data != NULL) { out_data->process(app, out_data); }
|
||||
|
||||
// Exit the app by returning from within the infinite loop
|
||||
if (app->destroyRequested != 0) {
|
||||
flog::warn("ASKED TO EXIT");
|
||||
exited = true;
|
||||
|
||||
// Stop SDR
|
||||
gui::mainWindow.setPlayState(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_EglDisplay == EGL_NO_DISPLAY) { continue; }
|
||||
|
||||
if (!pauseRendering) {
|
||||
// Initiate a new frame
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
auto dsize = io.DisplaySize;
|
||||
|
||||
// Poll Unicode characters via JNI
|
||||
// FIXME: do not call this every frame because of JNI overhead
|
||||
PollUnicodeChars();
|
||||
|
||||
// Open on-screen (soft) input if requested by Dear ImGui
|
||||
static bool WantTextInputLast = false;
|
||||
if (io.WantTextInput && !WantTextInputLast)
|
||||
ShowSoftKeyboardInput();
|
||||
WantTextInputLast = io.WantTextInput;
|
||||
|
||||
// Render
|
||||
beginFrame();
|
||||
|
||||
if (dsize.x > 0 && dsize.y > 0) {
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(dsize.x, dsize.y));
|
||||
gui::mainWindow.draw();
|
||||
}
|
||||
render();
|
||||
}
|
||||
else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int end() {
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplAndroid_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
// Destroy all
|
||||
if (_EglDisplay != EGL_NO_DISPLAY) {
|
||||
eglMakeCurrent(_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
if (_EglContext != EGL_NO_CONTEXT) { eglDestroyContext(_EglDisplay, _EglContext); }
|
||||
if (_EglSurface != EGL_NO_SURFACE) { eglDestroySurface(_EglDisplay, _EglSurface); }
|
||||
eglTerminate(_EglDisplay);
|
||||
}
|
||||
|
||||
_EglDisplay = EGL_NO_DISPLAY;
|
||||
_EglContext = EGL_NO_CONTEXT;
|
||||
_EglSurface = EGL_NO_SURFACE;
|
||||
|
||||
if (app->window) { ANativeWindow_release(app->window); }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ShowSoftKeyboardInput() {
|
||||
JavaVM* java_vm = app->activity->vm;
|
||||
JNIEnv* java_env = NULL;
|
||||
|
||||
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
|
||||
if (jni_return == JNI_ERR)
|
||||
return -1;
|
||||
|
||||
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
|
||||
if (jni_return != JNI_OK)
|
||||
return -2;
|
||||
|
||||
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
|
||||
if (native_activity_clazz == NULL)
|
||||
return -3;
|
||||
|
||||
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "showSoftInput", "()V");
|
||||
if (method_id == NULL)
|
||||
return -4;
|
||||
|
||||
java_env->CallVoidMethod(app->activity->clazz, method_id);
|
||||
|
||||
jni_return = java_vm->DetachCurrentThread();
|
||||
if (jni_return != JNI_OK)
|
||||
return -5;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids) {
|
||||
JavaVM* java_vm = app->activity->vm;
|
||||
JNIEnv* java_env = NULL;
|
||||
|
||||
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
|
||||
if (jni_return == JNI_ERR)
|
||||
return -1;
|
||||
|
||||
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
|
||||
if (jni_return != JNI_OK)
|
||||
return -1;
|
||||
|
||||
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
|
||||
if (native_activity_clazz == NULL)
|
||||
return -1;
|
||||
|
||||
jfieldID fd_field_id = java_env->GetFieldID(native_activity_clazz, "SDR_FD", "I");
|
||||
jfieldID vid_field_id = java_env->GetFieldID(native_activity_clazz, "SDR_VID", "I");
|
||||
jfieldID pid_field_id = java_env->GetFieldID(native_activity_clazz, "SDR_PID", "I");
|
||||
|
||||
if (!vid_field_id || !vid_field_id || !pid_field_id)
|
||||
return -1;
|
||||
|
||||
int fd = java_env->GetIntField(app->activity->clazz, fd_field_id);
|
||||
vid = java_env->GetIntField(app->activity->clazz, vid_field_id);
|
||||
pid = java_env->GetIntField(app->activity->clazz, pid_field_id);
|
||||
|
||||
jni_return = java_vm->DetachCurrentThread();
|
||||
if (jni_return != JNI_OK)
|
||||
return -1;
|
||||
|
||||
// If no vid/pid was given, just return successfully
|
||||
if (allowedVidPids.empty()) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Otherwise, check that the vid/pid combo is allowed
|
||||
for (auto const& vp : allowedVidPids) {
|
||||
if (vp.vid != vid || vp.pid != pid) { continue; }
|
||||
return fd;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Unfortunately, the native KeyEvent implementation has no getUnicodeChar() function.
|
||||
// Therefore, we implement the processing of KeyEvents in MainActivity.kt and poll
|
||||
// the resulting Unicode characters here via JNI and send them to Dear ImGui.
|
||||
int PollUnicodeChars() {
|
||||
JavaVM* java_vm = app->activity->vm;
|
||||
JNIEnv* java_env = NULL;
|
||||
|
||||
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
|
||||
if (jni_return == JNI_ERR)
|
||||
return -1;
|
||||
|
||||
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
|
||||
if (jni_return != JNI_OK)
|
||||
return -2;
|
||||
|
||||
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
|
||||
if (native_activity_clazz == NULL)
|
||||
return -3;
|
||||
|
||||
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "pollUnicodeChar", "()I");
|
||||
if (method_id == NULL)
|
||||
return -4;
|
||||
|
||||
// Send the actual characters to Dear ImGui
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
jint unicode_character;
|
||||
while ((unicode_character = java_env->CallIntMethod(app->activity->clazz, method_id)) != 0)
|
||||
io.AddInputCharacter(unicode_character);
|
||||
|
||||
jni_return = java_vm->DetachCurrentThread();
|
||||
if (jni_return != JNI_OK)
|
||||
return -5;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string getAppFilesDir() {
|
||||
JavaVM* java_vm = app->activity->vm;
|
||||
JNIEnv* java_env = NULL;
|
||||
|
||||
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
|
||||
if (jni_return == JNI_ERR)
|
||||
throw std::runtime_error("Could not get JNI environment");
|
||||
|
||||
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
|
||||
if (jni_return != JNI_OK)
|
||||
throw std::runtime_error("Could not attach to thread");
|
||||
|
||||
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
|
||||
if (native_activity_clazz == NULL)
|
||||
throw std::runtime_error("Could not get MainActivity class");
|
||||
|
||||
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "getAppDir", "()Ljava/lang/String;");
|
||||
if (method_id == NULL)
|
||||
throw std::runtime_error("Could not get method ID");
|
||||
|
||||
jstring jstr = (jstring)java_env->CallObjectMethod(app->activity->clazz, method_id);
|
||||
|
||||
const char* _str = java_env->GetStringUTFChars(jstr, NULL);
|
||||
std::string str(_str);
|
||||
java_env->ReleaseStringUTFChars(jstr, _str);
|
||||
|
||||
jni_return = java_vm->DetachCurrentThread();
|
||||
if (jni_return != JNI_OK)
|
||||
throw std::runtime_error("Could not detach from thread");
|
||||
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const std::vector<DevVIDPID> AIRSPY_VIDPIDS = {
|
||||
{ 0x1d50, 0x60a1 }
|
||||
};
|
||||
|
||||
const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS = {
|
||||
{ 0x03EB, 0x800C }
|
||||
};
|
||||
|
||||
const std::vector<DevVIDPID> HACKRF_VIDPIDS = {
|
||||
{ 0x1d50, 0x604b },
|
||||
{ 0x1d50, 0x6089 },
|
||||
{ 0x1d50, 0xcc15 }
|
||||
};
|
||||
|
||||
const std::vector<DevVIDPID> RTL_SDR_VIDPIDS = {
|
||||
{ 0x0bda, 0x2832 },
|
||||
{ 0x0bda, 0x2838 },
|
||||
{ 0x0413, 0x6680 },
|
||||
{ 0x0413, 0x6f0f },
|
||||
{ 0x0458, 0x707f },
|
||||
{ 0x0ccd, 0x00a9 },
|
||||
{ 0x0ccd, 0x00b3 },
|
||||
{ 0x0ccd, 0x00b4 },
|
||||
{ 0x0ccd, 0x00b5 },
|
||||
{ 0x0ccd, 0x00b7 },
|
||||
{ 0x0ccd, 0x00b8 },
|
||||
{ 0x0ccd, 0x00b9 },
|
||||
{ 0x0ccd, 0x00c0 },
|
||||
{ 0x0ccd, 0x00c6 },
|
||||
{ 0x0ccd, 0x00d3 },
|
||||
{ 0x0ccd, 0x00d7 },
|
||||
{ 0x0ccd, 0x00e0 },
|
||||
{ 0x1554, 0x5020 },
|
||||
{ 0x15f4, 0x0131 },
|
||||
{ 0x15f4, 0x0133 },
|
||||
{ 0x185b, 0x0620 },
|
||||
{ 0x185b, 0x0650 },
|
||||
{ 0x185b, 0x0680 },
|
||||
{ 0x1b80, 0xd393 },
|
||||
{ 0x1b80, 0xd394 },
|
||||
{ 0x1b80, 0xd395 },
|
||||
{ 0x1b80, 0xd397 },
|
||||
{ 0x1b80, 0xd398 },
|
||||
{ 0x1b80, 0xd39d },
|
||||
{ 0x1b80, 0xd3a4 },
|
||||
{ 0x1b80, 0xd3a8 },
|
||||
{ 0x1b80, 0xd3af },
|
||||
{ 0x1b80, 0xd3b0 },
|
||||
{ 0x1d19, 0x1101 },
|
||||
{ 0x1d19, 0x1102 },
|
||||
{ 0x1d19, 0x1103 },
|
||||
{ 0x1d19, 0x1104 },
|
||||
{ 0x1f4d, 0xa803 },
|
||||
{ 0x1f4d, 0xb803 },
|
||||
{ 0x1f4d, 0xc803 },
|
||||
{ 0x1f4d, 0xd286 },
|
||||
{ 0x1f4d, 0xd803 }
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
void android_main(struct android_app* app) {
|
||||
// Save app instance
|
||||
app->onAppCmd = backend::handleAppCmd;
|
||||
app->onInputEvent = backend::handleInputEvent;
|
||||
backend::app = app;
|
||||
|
||||
// Check if this is the first time we run or not
|
||||
if (backend::initialized) {
|
||||
flog::warn("android_main called again");
|
||||
backend::doPartialInit();
|
||||
backend::pauseRendering = false;
|
||||
backend::renderLoop();
|
||||
return;
|
||||
}
|
||||
backend::initialized = true;
|
||||
|
||||
// Grab files dir
|
||||
std::string appdir = backend::getAppFilesDir();
|
||||
|
||||
// Call main
|
||||
char* rootpath = new char[appdir.size() + 1];
|
||||
strcpy(rootpath, appdir.c_str());
|
||||
char* dummy[] = { "", "-r", rootpath };
|
||||
sdrpp_main(3, dummy);
|
||||
}
|
||||
}
|
||||
278
core/backends/android/imgui/imgui_impl_android.cpp
Normal file
278
core/backends/android/imgui/imgui_impl_android.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
// dear imgui: Platform Binding for Android native app
|
||||
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
|
||||
// Missing features:
|
||||
// [ ] Platform: Clipboard support.
|
||||
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
|
||||
// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
|
||||
// Important:
|
||||
// - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
|
||||
// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
|
||||
// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
|
||||
// Read online: https://github.com/ocornut/imgui/tree/master/docs
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago)with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
|
||||
// 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
|
||||
// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
|
||||
// 2021-03-04: Initial version.
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_android.h"
|
||||
#include <time.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/input.h>
|
||||
#include <android/keycodes.h>
|
||||
#include <android/log.h>
|
||||
|
||||
// Android data
|
||||
static double g_Time = 0.0;
|
||||
static ANativeWindow* g_Window;
|
||||
static char g_LogTag[] = "ImGuiExample";
|
||||
|
||||
static ImGuiKey ImGui_ImplAndroid_KeyCodeToImGuiKey(int32_t key_code)
|
||||
{
|
||||
switch (key_code)
|
||||
{
|
||||
case AKEYCODE_TAB: return ImGuiKey_Tab;
|
||||
case AKEYCODE_DPAD_LEFT: return ImGuiKey_LeftArrow;
|
||||
case AKEYCODE_DPAD_RIGHT: return ImGuiKey_RightArrow;
|
||||
case AKEYCODE_DPAD_UP: return ImGuiKey_UpArrow;
|
||||
case AKEYCODE_DPAD_DOWN: return ImGuiKey_DownArrow;
|
||||
case AKEYCODE_PAGE_UP: return ImGuiKey_PageUp;
|
||||
case AKEYCODE_PAGE_DOWN: return ImGuiKey_PageDown;
|
||||
case AKEYCODE_MOVE_HOME: return ImGuiKey_Home;
|
||||
case AKEYCODE_MOVE_END: return ImGuiKey_End;
|
||||
case AKEYCODE_INSERT: return ImGuiKey_Insert;
|
||||
case AKEYCODE_FORWARD_DEL: return ImGuiKey_Delete;
|
||||
case AKEYCODE_DEL: return ImGuiKey_Backspace;
|
||||
case AKEYCODE_SPACE: return ImGuiKey_Space;
|
||||
case AKEYCODE_ENTER: return ImGuiKey_Enter;
|
||||
case AKEYCODE_ESCAPE: return ImGuiKey_Escape;
|
||||
case AKEYCODE_APOSTROPHE: return ImGuiKey_Apostrophe;
|
||||
case AKEYCODE_COMMA: return ImGuiKey_Comma;
|
||||
case AKEYCODE_MINUS: return ImGuiKey_Minus;
|
||||
case AKEYCODE_PERIOD: return ImGuiKey_Period;
|
||||
case AKEYCODE_SLASH: return ImGuiKey_Slash;
|
||||
case AKEYCODE_SEMICOLON: return ImGuiKey_Semicolon;
|
||||
case AKEYCODE_EQUALS: return ImGuiKey_Equal;
|
||||
case AKEYCODE_LEFT_BRACKET: return ImGuiKey_LeftBracket;
|
||||
case AKEYCODE_BACKSLASH: return ImGuiKey_Backslash;
|
||||
case AKEYCODE_RIGHT_BRACKET: return ImGuiKey_RightBracket;
|
||||
case AKEYCODE_GRAVE: return ImGuiKey_GraveAccent;
|
||||
case AKEYCODE_CAPS_LOCK: return ImGuiKey_CapsLock;
|
||||
case AKEYCODE_SCROLL_LOCK: return ImGuiKey_ScrollLock;
|
||||
case AKEYCODE_NUM_LOCK: return ImGuiKey_NumLock;
|
||||
case AKEYCODE_SYSRQ: return ImGuiKey_PrintScreen;
|
||||
case AKEYCODE_BREAK: return ImGuiKey_Pause;
|
||||
case AKEYCODE_NUMPAD_0: return ImGuiKey_Keypad0;
|
||||
case AKEYCODE_NUMPAD_1: return ImGuiKey_Keypad1;
|
||||
case AKEYCODE_NUMPAD_2: return ImGuiKey_Keypad2;
|
||||
case AKEYCODE_NUMPAD_3: return ImGuiKey_Keypad3;
|
||||
case AKEYCODE_NUMPAD_4: return ImGuiKey_Keypad4;
|
||||
case AKEYCODE_NUMPAD_5: return ImGuiKey_Keypad5;
|
||||
case AKEYCODE_NUMPAD_6: return ImGuiKey_Keypad6;
|
||||
case AKEYCODE_NUMPAD_7: return ImGuiKey_Keypad7;
|
||||
case AKEYCODE_NUMPAD_8: return ImGuiKey_Keypad8;
|
||||
case AKEYCODE_NUMPAD_9: return ImGuiKey_Keypad9;
|
||||
case AKEYCODE_NUMPAD_DOT: return ImGuiKey_KeypadDecimal;
|
||||
case AKEYCODE_NUMPAD_DIVIDE: return ImGuiKey_KeypadDivide;
|
||||
case AKEYCODE_NUMPAD_MULTIPLY: return ImGuiKey_KeypadMultiply;
|
||||
case AKEYCODE_NUMPAD_SUBTRACT: return ImGuiKey_KeypadSubtract;
|
||||
case AKEYCODE_NUMPAD_ADD: return ImGuiKey_KeypadAdd;
|
||||
case AKEYCODE_NUMPAD_ENTER: return ImGuiKey_KeypadEnter;
|
||||
case AKEYCODE_NUMPAD_EQUALS: return ImGuiKey_KeypadEqual;
|
||||
case AKEYCODE_CTRL_LEFT: return ImGuiKey_LeftCtrl;
|
||||
case AKEYCODE_SHIFT_LEFT: return ImGuiKey_LeftShift;
|
||||
case AKEYCODE_ALT_LEFT: return ImGuiKey_LeftAlt;
|
||||
case AKEYCODE_META_LEFT: return ImGuiKey_LeftSuper;
|
||||
case AKEYCODE_CTRL_RIGHT: return ImGuiKey_RightCtrl;
|
||||
case AKEYCODE_SHIFT_RIGHT: return ImGuiKey_RightShift;
|
||||
case AKEYCODE_ALT_RIGHT: return ImGuiKey_RightAlt;
|
||||
case AKEYCODE_META_RIGHT: return ImGuiKey_RightSuper;
|
||||
case AKEYCODE_MENU: return ImGuiKey_Menu;
|
||||
case AKEYCODE_0: return ImGuiKey_0;
|
||||
case AKEYCODE_1: return ImGuiKey_1;
|
||||
case AKEYCODE_2: return ImGuiKey_2;
|
||||
case AKEYCODE_3: return ImGuiKey_3;
|
||||
case AKEYCODE_4: return ImGuiKey_4;
|
||||
case AKEYCODE_5: return ImGuiKey_5;
|
||||
case AKEYCODE_6: return ImGuiKey_6;
|
||||
case AKEYCODE_7: return ImGuiKey_7;
|
||||
case AKEYCODE_8: return ImGuiKey_8;
|
||||
case AKEYCODE_9: return ImGuiKey_9;
|
||||
case AKEYCODE_A: return ImGuiKey_A;
|
||||
case AKEYCODE_B: return ImGuiKey_B;
|
||||
case AKEYCODE_C: return ImGuiKey_C;
|
||||
case AKEYCODE_D: return ImGuiKey_D;
|
||||
case AKEYCODE_E: return ImGuiKey_E;
|
||||
case AKEYCODE_F: return ImGuiKey_F;
|
||||
case AKEYCODE_G: return ImGuiKey_G;
|
||||
case AKEYCODE_H: return ImGuiKey_H;
|
||||
case AKEYCODE_I: return ImGuiKey_I;
|
||||
case AKEYCODE_J: return ImGuiKey_J;
|
||||
case AKEYCODE_K: return ImGuiKey_K;
|
||||
case AKEYCODE_L: return ImGuiKey_L;
|
||||
case AKEYCODE_M: return ImGuiKey_M;
|
||||
case AKEYCODE_N: return ImGuiKey_N;
|
||||
case AKEYCODE_O: return ImGuiKey_O;
|
||||
case AKEYCODE_P: return ImGuiKey_P;
|
||||
case AKEYCODE_Q: return ImGuiKey_Q;
|
||||
case AKEYCODE_R: return ImGuiKey_R;
|
||||
case AKEYCODE_S: return ImGuiKey_S;
|
||||
case AKEYCODE_T: return ImGuiKey_T;
|
||||
case AKEYCODE_U: return ImGuiKey_U;
|
||||
case AKEYCODE_V: return ImGuiKey_V;
|
||||
case AKEYCODE_W: return ImGuiKey_W;
|
||||
case AKEYCODE_X: return ImGuiKey_X;
|
||||
case AKEYCODE_Y: return ImGuiKey_Y;
|
||||
case AKEYCODE_Z: return ImGuiKey_Z;
|
||||
case AKEYCODE_F1: return ImGuiKey_F1;
|
||||
case AKEYCODE_F2: return ImGuiKey_F2;
|
||||
case AKEYCODE_F3: return ImGuiKey_F3;
|
||||
case AKEYCODE_F4: return ImGuiKey_F4;
|
||||
case AKEYCODE_F5: return ImGuiKey_F5;
|
||||
case AKEYCODE_F6: return ImGuiKey_F6;
|
||||
case AKEYCODE_F7: return ImGuiKey_F7;
|
||||
case AKEYCODE_F8: return ImGuiKey_F8;
|
||||
case AKEYCODE_F9: return ImGuiKey_F9;
|
||||
case AKEYCODE_F10: return ImGuiKey_F10;
|
||||
case AKEYCODE_F11: return ImGuiKey_F11;
|
||||
case AKEYCODE_F12: return ImGuiKey_F12;
|
||||
default: return ImGuiKey_None;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
int32_t event_type = AInputEvent_getType(input_event);
|
||||
switch (event_type)
|
||||
{
|
||||
case AINPUT_EVENT_TYPE_KEY:
|
||||
{
|
||||
int32_t event_key_code = AKeyEvent_getKeyCode(input_event);
|
||||
int32_t event_scan_code = AKeyEvent_getScanCode(input_event);
|
||||
int32_t event_action = AKeyEvent_getAction(input_event);
|
||||
int32_t event_meta_state = AKeyEvent_getMetaState(input_event);
|
||||
|
||||
io.AddKeyEvent(ImGuiKey_ModCtrl, (event_meta_state & AMETA_CTRL_ON) != 0);
|
||||
io.AddKeyEvent(ImGuiKey_ModShift, (event_meta_state & AMETA_SHIFT_ON) != 0);
|
||||
io.AddKeyEvent(ImGuiKey_ModAlt, (event_meta_state & AMETA_ALT_ON) != 0);
|
||||
io.AddKeyEvent(ImGuiKey_ModSuper, (event_meta_state & AMETA_META_ON) != 0);
|
||||
|
||||
switch (event_action)
|
||||
{
|
||||
// FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once as soon as a touch pointer
|
||||
// goes up from a key. We use a simple key event queue/ and process one event per key per frame in
|
||||
// ImGui_ImplAndroid_NewFrame()...or consider using IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787
|
||||
case AKEY_EVENT_ACTION_DOWN:
|
||||
case AKEY_EVENT_ACTION_UP:
|
||||
{
|
||||
ImGuiKey key = ImGui_ImplAndroid_KeyCodeToImGuiKey(event_key_code);
|
||||
if (key != ImGuiKey_None && (event_action == AKEY_EVENT_ACTION_DOWN || event_action == AKEY_EVENT_ACTION_UP))
|
||||
{
|
||||
io.AddKeyEvent(key, event_action == AKEY_EVENT_ACTION_DOWN);
|
||||
io.SetKeyEventNativeData(key, event_key_code, event_scan_code);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AINPUT_EVENT_TYPE_MOTION:
|
||||
{
|
||||
int32_t event_action = AMotionEvent_getAction(input_event);
|
||||
int32_t event_pointer_index = (event_action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
event_action &= AMOTION_EVENT_ACTION_MASK;
|
||||
switch (event_action)
|
||||
{
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
// Physical mouse buttons (and probably other physical devices) also invoke the actions AMOTION_EVENT_ACTION_DOWN/_UP,
|
||||
// but we have to process them separately to identify the actual button pressed. This is done below via
|
||||
// AMOTION_EVENT_ACTION_BUTTON_PRESS/_RELEASE. Here, we only process "FINGER" input (and "UNKNOWN", as a fallback).
|
||||
if((AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_FINGER)
|
||||
|| (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_STYLUS)
|
||||
|| (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_MOUSE)
|
||||
|| (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_UNKNOWN))
|
||||
{
|
||||
io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
|
||||
io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN);
|
||||
}
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_BUTTON_PRESS:
|
||||
case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
|
||||
{
|
||||
int32_t button_state = AMotionEvent_getButtonState(input_event);
|
||||
io.AddMouseButtonEvent(0, (button_state & AMOTION_EVENT_BUTTON_PRIMARY) != 0);
|
||||
io.AddMouseButtonEvent(1, (button_state & AMOTION_EVENT_BUTTON_SECONDARY) != 0);
|
||||
io.AddMouseButtonEvent(2, (button_state & AMOTION_EVENT_BUTTON_TERTIARY) != 0);
|
||||
}
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_HOVER_MOVE: // Hovering: Tool moves while NOT pressed (such as a physical mouse)
|
||||
case AMOTION_EVENT_ACTION_MOVE: // Touch pointer moves while DOWN
|
||||
io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_SCROLL:
|
||||
io.AddMouseWheelEvent(AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index), AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ImGui_ImplAndroid_Init(ANativeWindow* window)
|
||||
{
|
||||
g_Window = window;
|
||||
g_Time = 0.0;
|
||||
|
||||
// Setup backend capabilities flags
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.BackendPlatformName = "imgui_impl_android";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplAndroid_Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
void ImGui_ImplAndroid_NewFrame()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Setup display size (every frame to accommodate for window resizing)
|
||||
int32_t window_width = ANativeWindow_getWidth(g_Window);
|
||||
int32_t window_height = ANativeWindow_getHeight(g_Window);
|
||||
int display_width = window_width;
|
||||
int display_height = window_height;
|
||||
|
||||
io.DisplaySize = ImVec2((float)window_width, (float)window_height);
|
||||
if (window_width > 0 && window_height > 0)
|
||||
io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height);
|
||||
|
||||
// Setup time step
|
||||
struct timespec current_timespec;
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_timespec);
|
||||
double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0);
|
||||
io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
|
||||
g_Time = current_time;
|
||||
}
|
||||
28
core/backends/android/imgui/imgui_impl_android.h
Normal file
28
core/backends/android/imgui/imgui_impl_android.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// dear imgui: Platform Binding for Android native app
|
||||
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
|
||||
// Missing features:
|
||||
// [ ] Platform: Clipboard support.
|
||||
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
|
||||
// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
|
||||
// Important:
|
||||
// - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
|
||||
// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
|
||||
// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
|
||||
// Read online: https://github.com/ocornut/imgui/tree/master/docs
|
||||
|
||||
#pragma once
|
||||
|
||||
struct ANativeWindow;
|
||||
struct AInputEvent;
|
||||
|
||||
IMGUI_IMPL_API bool ImGui_ImplAndroid_Init(ANativeWindow* window);
|
||||
IMGUI_IMPL_API int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event);
|
||||
IMGUI_IMPL_API void ImGui_ImplAndroid_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame();
|
||||
305
core/backends/glfw/backend.cpp
Normal file
305
core/backends/glfw/backend.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#include <backend.h>
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <utils/flog.h>
|
||||
#include <utils/opengl_include_code.h>
|
||||
#include <version.h>
|
||||
#include <core.h>
|
||||
#include <filesystem>
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_resize.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
namespace backend {
|
||||
const char* OPENGL_VERSIONS_GLSL[] = {
|
||||
"#version 120",
|
||||
"#version 300 es",
|
||||
"#version 120"
|
||||
};
|
||||
|
||||
const int OPENGL_VERSIONS_MAJOR[] = {
|
||||
3,
|
||||
3,
|
||||
2
|
||||
};
|
||||
|
||||
const int OPENGL_VERSIONS_MINOR[] = {
|
||||
0,
|
||||
1,
|
||||
1
|
||||
};
|
||||
|
||||
const bool OPENGL_VERSIONS_IS_ES[] = {
|
||||
false,
|
||||
true,
|
||||
false
|
||||
};
|
||||
|
||||
#define OPENGL_VERSION_COUNT (sizeof(OPENGL_VERSIONS_GLSL) / sizeof(char*))
|
||||
|
||||
bool maximized = false;
|
||||
bool fullScreen = false;
|
||||
int winHeight;
|
||||
int winWidth;
|
||||
bool _maximized = maximized;
|
||||
int fsWidth, fsHeight, fsPosX, fsPosY;
|
||||
int _winWidth, _winHeight;
|
||||
GLFWwindow* window;
|
||||
GLFWmonitor* monitor;
|
||||
|
||||
static void glfw_error_callback(int error, const char* description) {
|
||||
flog::error("Glfw Error {0}: {1}", error, description);
|
||||
}
|
||||
|
||||
static void maximized_callback(GLFWwindow* window, int n) {
|
||||
if (n == GLFW_TRUE) {
|
||||
maximized = true;
|
||||
}
|
||||
else {
|
||||
maximized = false;
|
||||
}
|
||||
}
|
||||
|
||||
int init(std::string resDir) {
|
||||
// Load config
|
||||
core::configManager.acquire();
|
||||
winWidth = core::configManager.conf["windowSize"]["w"];
|
||||
winHeight = core::configManager.conf["windowSize"]["h"];
|
||||
maximized = core::configManager.conf["maximized"];
|
||||
fullScreen = core::configManager.conf["fullscreen"];
|
||||
core::configManager.release();
|
||||
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
// GL 3.2 + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
|
||||
// Create window with graphics context
|
||||
monitor = glfwGetPrimaryMonitor();
|
||||
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||
if (window == NULL)
|
||||
return 1;
|
||||
glfwMakeContextCurrent(window);
|
||||
#else
|
||||
const char* glsl_version = "#version 120";
|
||||
monitor = NULL;
|
||||
for (int i = 0; i < OPENGL_VERSION_COUNT; i++) {
|
||||
glsl_version = OPENGL_VERSIONS_GLSL[i];
|
||||
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
|
||||
|
||||
// Create window with graphics context
|
||||
monitor = glfwGetPrimaryMonitor();
|
||||
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||
if (window == NULL) {
|
||||
flog::info("OpenGL {0}.{1} {2}was not supported", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? "ES " : "");
|
||||
continue;
|
||||
}
|
||||
flog::info("Using OpenGL {0}.{1}{2}", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? " ES" : "");
|
||||
glfwMakeContextCurrent(window);
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Load app icon
|
||||
if (!std::filesystem::is_regular_file(resDir + "/icons/sdrpp.png")) {
|
||||
flog::error("Icon file '{0}' doesn't exist!", resDir + "/icons/sdrpp.png");
|
||||
return 1;
|
||||
}
|
||||
|
||||
GLFWimage icons[10];
|
||||
icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
|
||||
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4);
|
||||
icons[1].width = icons[1].height = 16;
|
||||
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4);
|
||||
icons[2].width = icons[2].height = 24;
|
||||
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4);
|
||||
icons[3].width = icons[3].height = 32;
|
||||
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4);
|
||||
icons[4].width = icons[4].height = 48;
|
||||
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4);
|
||||
icons[5].width = icons[5].height = 64;
|
||||
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4);
|
||||
icons[6].width = icons[6].height = 96;
|
||||
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4);
|
||||
icons[7].width = icons[7].height = 128;
|
||||
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4);
|
||||
icons[8].width = icons[8].height = 196;
|
||||
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4);
|
||||
icons[9].width = icons[9].height = 256;
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
|
||||
glfwSetWindowIcon(window, 10, icons);
|
||||
stbi_image_free(icons[0].pixels);
|
||||
for (int i = 1; i < 10; i++) {
|
||||
free(icons[i].pixels);
|
||||
}
|
||||
|
||||
// Add callback for max/min if GLFW supports it
|
||||
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
|
||||
if (maximized) {
|
||||
glfwMaximizeWindow(window);
|
||||
}
|
||||
|
||||
glfwSetWindowMaximizeCallback(window, maximized_callback);
|
||||
#endif
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
(void)io;
|
||||
io.IniFilename = NULL;
|
||||
|
||||
// Setup Platform/Renderer bindings
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
|
||||
if (!ImGui_ImplOpenGL3_Init(glsl_version)) {
|
||||
// If init fail, try to fall back on GLSL 1.2
|
||||
flog::warn("Could not init using OpenGL with normal GLSL version, falling back to GLSL 1.2");
|
||||
if (!ImGui_ImplOpenGL3_Init("#version 120")) {
|
||||
flog::error("Failed to initialize OpenGL with GLSL 1.2");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Set window size and fullscreen state
|
||||
glfwGetWindowSize(window, &_winWidth, &_winHeight);
|
||||
if (fullScreen) {
|
||||
flog::info("Fullscreen: ON");
|
||||
fsWidth = _winWidth;
|
||||
fsHeight = _winHeight;
|
||||
glfwGetWindowPos(window, &fsPosX, &fsPosY);
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
|
||||
}
|
||||
|
||||
// Everything went according to plan
|
||||
return 0;
|
||||
}
|
||||
|
||||
void beginFrame() {
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void render(bool vsync) {
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapInterval(vsync);
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
void getMouseScreenPos(double& x, double& y) {
|
||||
glfwGetCursorPos(window, &x, &y);
|
||||
}
|
||||
|
||||
void setMouseScreenPos(double x, double y) {
|
||||
// Tell GLFW to move the cursor and then manually fire the event
|
||||
glfwSetCursorPos(window, x, y);
|
||||
ImGui_ImplGlfw_CursorPosCallback(window, x, y);
|
||||
}
|
||||
|
||||
int renderLoop() {
|
||||
// Main loop
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
|
||||
beginFrame();
|
||||
|
||||
if (_maximized != maximized) {
|
||||
_maximized = maximized;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["maximized"] = _maximized;
|
||||
if (!maximized) {
|
||||
glfwSetWindowSize(window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
|
||||
}
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
glfwGetWindowSize(window, &_winWidth, &_winHeight);
|
||||
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
|
||||
fullScreen = !fullScreen;
|
||||
if (fullScreen) {
|
||||
flog::info("Fullscreen: ON");
|
||||
fsWidth = _winWidth;
|
||||
fsHeight = _winHeight;
|
||||
glfwGetWindowPos(window, &fsPosX, &fsPosY);
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fullscreen"] = true;
|
||||
core::configManager.release();
|
||||
}
|
||||
else {
|
||||
flog::info("Fullscreen: OFF");
|
||||
glfwSetWindowMonitor(window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fullscreen"] = false;
|
||||
core::configManager.release();
|
||||
}
|
||||
}
|
||||
|
||||
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
|
||||
winWidth = _winWidth;
|
||||
winHeight = _winHeight;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["windowSize"]["w"] = winWidth;
|
||||
core::configManager.conf["windowSize"]["h"] = winHeight;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (winWidth > 0 && winHeight > 0) {
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
|
||||
gui::mainWindow.draw();
|
||||
}
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int end() {
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
|
||||
return 0; // TODO: Int really needed?
|
||||
}
|
||||
}
|
||||
643
core/backends/glfw/imgui/imgui_impl_glfw.cpp
Normal file
643
core/backends/glfw/imgui/imgui_impl_glfw.cpp
Normal file
@@ -0,0 +1,643 @@
|
||||
// dear imgui: Platform Backend for GLFW
|
||||
// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
|
||||
// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
|
||||
// (Requires: GLFW 3.1+)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Platform: Clipboard support.
|
||||
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
|
||||
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
|
||||
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
|
||||
// Read online: https://github.com/ocornut/imgui/tree/master/docs
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2022-02-07: Added ImGui_ImplGlfw_InstallCallbacks()/ImGui_ImplGlfw_RestoreCallbacks() helpers to facilitate user installing callbacks after iniitializing backend.
|
||||
// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago)with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
|
||||
// 2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
|
||||
// 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
|
||||
// 2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates.
|
||||
// 2022-01-12: *BREAKING CHANGE*: Now using glfwSetCursorPosCallback(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetCursorPosCallback() and forward it to the backend via ImGui_ImplGlfw_CursorPosCallback().
|
||||
// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
|
||||
// 2022-01-05: Inputs: Converting GLFW untranslated keycodes back to translated keycodes (in the ImGui_ImplGlfw_KeyCallback() function) in order to match the behavior of every other backend, and facilitate the use of GLFW with lettered-shortcuts API.
|
||||
// 2021-08-17: *BREAKING CHANGE*: Now using glfwSetWindowFocusCallback() to calling io.AddFocusEvent(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() and forward it to the backend via ImGui_ImplGlfw_WindowFocusCallback().
|
||||
// 2021-07-29: *BREAKING CHANGE*: Now using glfwSetCursorEnterCallback(). MousePos is correctly reported when the host platform window is hovered but not focused. If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() callback and forward it to the backend via ImGui_ImplGlfw_CursorEnterCallback().
|
||||
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
|
||||
// 2020-01-17: Inputs: Disable error callback while assigning mouse cursors because some X11 setup don't have them and it generates errors.
|
||||
// 2019-12-05: Inputs: Added support for new mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor).
|
||||
// 2019-10-18: Misc: Previously installed user callbacks are now restored on shutdown.
|
||||
// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
|
||||
// 2019-05-11: Inputs: Don't filter value from character callback before calling AddInputCharacter().
|
||||
// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
|
||||
// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
|
||||
// 2018-11-07: Inputs: When installing our GLFW callbacks, we save user's previously installed ones - if any - and chain call them.
|
||||
// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
|
||||
// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
|
||||
// 2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old combined GLFW+OpenGL/Vulkan examples.
|
||||
// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
|
||||
// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value, passed to glfwSetCursor()).
|
||||
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
|
||||
// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
|
||||
// 2018-01-25: Inputs: Added gamepad support if ImGuiConfigFlags_NavEnableGamepad is set.
|
||||
// 2018-01-25: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).
|
||||
// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
|
||||
// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
|
||||
// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
|
||||
// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
|
||||
// Clang warnings with -Weverything
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
||||
#if __has_warning("-Wzero-as-null-pointer-constant")
|
||||
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// GLFW
|
||||
#include <GLFW/glfw3.h>
|
||||
#ifdef _WIN32
|
||||
#undef APIENTRY
|
||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||
#include <GLFW/glfw3native.h> // for glfwGetWin32Window
|
||||
#endif
|
||||
#ifdef GLFW_RESIZE_NESW_CURSOR // Let's be nice to people who pulled GLFW between 2019-04-16 (3.4 define) and 2019-11-29 (cursors defines) // FIXME: Remove when GLFW 3.4 is released?
|
||||
#define GLFW_HAS_NEW_CURSORS (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3400) // 3.4+ GLFW_RESIZE_ALL_CURSOR, GLFW_RESIZE_NESW_CURSOR, GLFW_RESIZE_NWSE_CURSOR, GLFW_NOT_ALLOWED_CURSOR
|
||||
#else
|
||||
#define GLFW_HAS_NEW_CURSORS (0)
|
||||
#endif
|
||||
#define GLFW_HAS_GAMEPAD_API (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwGetGamepadState() new api
|
||||
#define GLFW_HAS_GET_KEY_NAME (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ glfwGetKeyName()
|
||||
|
||||
// GLFW data
|
||||
enum GlfwClientApi
|
||||
{
|
||||
GlfwClientApi_Unknown,
|
||||
GlfwClientApi_OpenGL,
|
||||
GlfwClientApi_Vulkan
|
||||
};
|
||||
|
||||
struct ImGui_ImplGlfw_Data
|
||||
{
|
||||
GLFWwindow* Window;
|
||||
GlfwClientApi ClientApi;
|
||||
double Time;
|
||||
GLFWwindow* MouseWindow;
|
||||
GLFWcursor* MouseCursors[ImGuiMouseCursor_COUNT];
|
||||
ImVec2 LastValidMousePos;
|
||||
bool InstalledCallbacks;
|
||||
|
||||
// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
|
||||
GLFWwindowfocusfun PrevUserCallbackWindowFocus;
|
||||
GLFWcursorposfun PrevUserCallbackCursorPos;
|
||||
GLFWcursorenterfun PrevUserCallbackCursorEnter;
|
||||
GLFWmousebuttonfun PrevUserCallbackMousebutton;
|
||||
GLFWscrollfun PrevUserCallbackScroll;
|
||||
GLFWkeyfun PrevUserCallbackKey;
|
||||
GLFWcharfun PrevUserCallbackChar;
|
||||
GLFWmonitorfun PrevUserCallbackMonitor;
|
||||
|
||||
ImGui_ImplGlfw_Data() { memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
|
||||
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
|
||||
// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
|
||||
// - Because glfwPollEvents() process all windows and some events may be called outside of it, you will need to register your own callbacks
|
||||
// (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set the current dear imgui context and then call our callbacks.
|
||||
// - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and handle this in the backend, adding a little bit of extra complexity to it.
|
||||
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
|
||||
static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData()
|
||||
{
|
||||
return ImGui::GetCurrentContext() ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData : NULL;
|
||||
}
|
||||
|
||||
// Functions
|
||||
static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data)
|
||||
{
|
||||
return glfwGetClipboardString((GLFWwindow*)user_data);
|
||||
}
|
||||
|
||||
static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text)
|
||||
{
|
||||
glfwSetClipboardString((GLFWwindow*)user_data, text);
|
||||
}
|
||||
|
||||
static ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case GLFW_KEY_TAB: return ImGuiKey_Tab;
|
||||
case GLFW_KEY_LEFT: return ImGuiKey_LeftArrow;
|
||||
case GLFW_KEY_RIGHT: return ImGuiKey_RightArrow;
|
||||
case GLFW_KEY_UP: return ImGuiKey_UpArrow;
|
||||
case GLFW_KEY_DOWN: return ImGuiKey_DownArrow;
|
||||
case GLFW_KEY_PAGE_UP: return ImGuiKey_PageUp;
|
||||
case GLFW_KEY_PAGE_DOWN: return ImGuiKey_PageDown;
|
||||
case GLFW_KEY_HOME: return ImGuiKey_Home;
|
||||
case GLFW_KEY_END: return ImGuiKey_End;
|
||||
case GLFW_KEY_INSERT: return ImGuiKey_Insert;
|
||||
case GLFW_KEY_DELETE: return ImGuiKey_Delete;
|
||||
case GLFW_KEY_BACKSPACE: return ImGuiKey_Backspace;
|
||||
case GLFW_KEY_SPACE: return ImGuiKey_Space;
|
||||
case GLFW_KEY_ENTER: return ImGuiKey_Enter;
|
||||
case GLFW_KEY_ESCAPE: return ImGuiKey_Escape;
|
||||
case GLFW_KEY_APOSTROPHE: return ImGuiKey_Apostrophe;
|
||||
case GLFW_KEY_COMMA: return ImGuiKey_Comma;
|
||||
case GLFW_KEY_MINUS: return ImGuiKey_Minus;
|
||||
case GLFW_KEY_PERIOD: return ImGuiKey_Period;
|
||||
case GLFW_KEY_SLASH: return ImGuiKey_Slash;
|
||||
case GLFW_KEY_SEMICOLON: return ImGuiKey_Semicolon;
|
||||
case GLFW_KEY_EQUAL: return ImGuiKey_Equal;
|
||||
case GLFW_KEY_LEFT_BRACKET: return ImGuiKey_LeftBracket;
|
||||
case GLFW_KEY_BACKSLASH: return ImGuiKey_Backslash;
|
||||
case GLFW_KEY_RIGHT_BRACKET: return ImGuiKey_RightBracket;
|
||||
case GLFW_KEY_GRAVE_ACCENT: return ImGuiKey_GraveAccent;
|
||||
case GLFW_KEY_CAPS_LOCK: return ImGuiKey_CapsLock;
|
||||
case GLFW_KEY_SCROLL_LOCK: return ImGuiKey_ScrollLock;
|
||||
case GLFW_KEY_NUM_LOCK: return ImGuiKey_NumLock;
|
||||
case GLFW_KEY_PRINT_SCREEN: return ImGuiKey_PrintScreen;
|
||||
case GLFW_KEY_PAUSE: return ImGuiKey_Pause;
|
||||
case GLFW_KEY_KP_0: return ImGuiKey_Keypad0;
|
||||
case GLFW_KEY_KP_1: return ImGuiKey_Keypad1;
|
||||
case GLFW_KEY_KP_2: return ImGuiKey_Keypad2;
|
||||
case GLFW_KEY_KP_3: return ImGuiKey_Keypad3;
|
||||
case GLFW_KEY_KP_4: return ImGuiKey_Keypad4;
|
||||
case GLFW_KEY_KP_5: return ImGuiKey_Keypad5;
|
||||
case GLFW_KEY_KP_6: return ImGuiKey_Keypad6;
|
||||
case GLFW_KEY_KP_7: return ImGuiKey_Keypad7;
|
||||
case GLFW_KEY_KP_8: return ImGuiKey_Keypad8;
|
||||
case GLFW_KEY_KP_9: return ImGuiKey_Keypad9;
|
||||
case GLFW_KEY_KP_DECIMAL: return ImGuiKey_KeypadDecimal;
|
||||
case GLFW_KEY_KP_DIVIDE: return ImGuiKey_KeypadDivide;
|
||||
case GLFW_KEY_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
|
||||
case GLFW_KEY_KP_SUBTRACT: return ImGuiKey_KeypadSubtract;
|
||||
case GLFW_KEY_KP_ADD: return ImGuiKey_KeypadAdd;
|
||||
case GLFW_KEY_KP_ENTER: return ImGuiKey_KeypadEnter;
|
||||
case GLFW_KEY_KP_EQUAL: return ImGuiKey_KeypadEqual;
|
||||
case GLFW_KEY_LEFT_SHIFT: return ImGuiKey_LeftShift;
|
||||
case GLFW_KEY_LEFT_CONTROL: return ImGuiKey_LeftCtrl;
|
||||
case GLFW_KEY_LEFT_ALT: return ImGuiKey_LeftAlt;
|
||||
case GLFW_KEY_LEFT_SUPER: return ImGuiKey_LeftSuper;
|
||||
case GLFW_KEY_RIGHT_SHIFT: return ImGuiKey_RightShift;
|
||||
case GLFW_KEY_RIGHT_CONTROL: return ImGuiKey_RightCtrl;
|
||||
case GLFW_KEY_RIGHT_ALT: return ImGuiKey_RightAlt;
|
||||
case GLFW_KEY_RIGHT_SUPER: return ImGuiKey_RightSuper;
|
||||
case GLFW_KEY_MENU: return ImGuiKey_Menu;
|
||||
case GLFW_KEY_0: return ImGuiKey_0;
|
||||
case GLFW_KEY_1: return ImGuiKey_1;
|
||||
case GLFW_KEY_2: return ImGuiKey_2;
|
||||
case GLFW_KEY_3: return ImGuiKey_3;
|
||||
case GLFW_KEY_4: return ImGuiKey_4;
|
||||
case GLFW_KEY_5: return ImGuiKey_5;
|
||||
case GLFW_KEY_6: return ImGuiKey_6;
|
||||
case GLFW_KEY_7: return ImGuiKey_7;
|
||||
case GLFW_KEY_8: return ImGuiKey_8;
|
||||
case GLFW_KEY_9: return ImGuiKey_9;
|
||||
case GLFW_KEY_A: return ImGuiKey_A;
|
||||
case GLFW_KEY_B: return ImGuiKey_B;
|
||||
case GLFW_KEY_C: return ImGuiKey_C;
|
||||
case GLFW_KEY_D: return ImGuiKey_D;
|
||||
case GLFW_KEY_E: return ImGuiKey_E;
|
||||
case GLFW_KEY_F: return ImGuiKey_F;
|
||||
case GLFW_KEY_G: return ImGuiKey_G;
|
||||
case GLFW_KEY_H: return ImGuiKey_H;
|
||||
case GLFW_KEY_I: return ImGuiKey_I;
|
||||
case GLFW_KEY_J: return ImGuiKey_J;
|
||||
case GLFW_KEY_K: return ImGuiKey_K;
|
||||
case GLFW_KEY_L: return ImGuiKey_L;
|
||||
case GLFW_KEY_M: return ImGuiKey_M;
|
||||
case GLFW_KEY_N: return ImGuiKey_N;
|
||||
case GLFW_KEY_O: return ImGuiKey_O;
|
||||
case GLFW_KEY_P: return ImGuiKey_P;
|
||||
case GLFW_KEY_Q: return ImGuiKey_Q;
|
||||
case GLFW_KEY_R: return ImGuiKey_R;
|
||||
case GLFW_KEY_S: return ImGuiKey_S;
|
||||
case GLFW_KEY_T: return ImGuiKey_T;
|
||||
case GLFW_KEY_U: return ImGuiKey_U;
|
||||
case GLFW_KEY_V: return ImGuiKey_V;
|
||||
case GLFW_KEY_W: return ImGuiKey_W;
|
||||
case GLFW_KEY_X: return ImGuiKey_X;
|
||||
case GLFW_KEY_Y: return ImGuiKey_Y;
|
||||
case GLFW_KEY_Z: return ImGuiKey_Z;
|
||||
case GLFW_KEY_F1: return ImGuiKey_F1;
|
||||
case GLFW_KEY_F2: return ImGuiKey_F2;
|
||||
case GLFW_KEY_F3: return ImGuiKey_F3;
|
||||
case GLFW_KEY_F4: return ImGuiKey_F4;
|
||||
case GLFW_KEY_F5: return ImGuiKey_F5;
|
||||
case GLFW_KEY_F6: return ImGuiKey_F6;
|
||||
case GLFW_KEY_F7: return ImGuiKey_F7;
|
||||
case GLFW_KEY_F8: return ImGuiKey_F8;
|
||||
case GLFW_KEY_F9: return ImGuiKey_F9;
|
||||
case GLFW_KEY_F10: return ImGuiKey_F10;
|
||||
case GLFW_KEY_F11: return ImGuiKey_F11;
|
||||
case GLFW_KEY_F12: return ImGuiKey_F12;
|
||||
default: return ImGuiKey_None;
|
||||
}
|
||||
}
|
||||
|
||||
static void ImGui_ImplGlfw_UpdateKeyModifiers(int mods)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.AddKeyEvent(ImGuiKey_ModCtrl, (mods & GLFW_MOD_CONTROL) != 0);
|
||||
io.AddKeyEvent(ImGuiKey_ModShift, (mods & GLFW_MOD_SHIFT) != 0);
|
||||
io.AddKeyEvent(ImGuiKey_ModAlt, (mods & GLFW_MOD_ALT) != 0);
|
||||
io.AddKeyEvent(ImGuiKey_ModSuper, (mods & GLFW_MOD_SUPER) != 0);
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->PrevUserCallbackMousebutton != NULL && window == bd->Window)
|
||||
bd->PrevUserCallbackMousebutton(window, button, action, mods);
|
||||
|
||||
ImGui_ImplGlfw_UpdateKeyModifiers(mods);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (button >= 0 && button < ImGuiMouseButton_COUNT)
|
||||
io.AddMouseButtonEvent(button, action == GLFW_PRESS);
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->PrevUserCallbackScroll != NULL && window == bd->Window)
|
||||
bd->PrevUserCallbackScroll(window, xoffset, yoffset);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.AddMouseWheelEvent((float)xoffset, (float)yoffset);
|
||||
}
|
||||
|
||||
static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode)
|
||||
{
|
||||
#if GLFW_HAS_GET_KEY_NAME && !defined(__EMSCRIPTEN__)
|
||||
// GLFW 3.1+ attempts to "untranslate" keys, which goes the opposite of what every other framework does, making using lettered shortcuts difficult.
|
||||
// (It had reasons to do so: namely GLFW is/was more likely to be used for WASD-type game controls rather than lettered shortcuts, but IHMO the 3.1 change could have been done differently)
|
||||
// See https://github.com/glfw/glfw/issues/1502 for details.
|
||||
// Adding a workaround to undo this (so our keys are translated->untranslated->translated, likely a lossy process).
|
||||
// This won't cover edge cases but this is at least going to cover common cases.
|
||||
if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_EQUAL)
|
||||
return key;
|
||||
const char* key_name = glfwGetKeyName(key, scancode);
|
||||
if (key_name && key_name[0] != 0 && key_name[1] == 0)
|
||||
{
|
||||
const char char_names[] = "`-=[]\\,;\'./";
|
||||
const int char_keys[] = { GLFW_KEY_GRAVE_ACCENT, GLFW_KEY_MINUS, GLFW_KEY_EQUAL, GLFW_KEY_LEFT_BRACKET, GLFW_KEY_RIGHT_BRACKET, GLFW_KEY_BACKSLASH, GLFW_KEY_COMMA, GLFW_KEY_SEMICOLON, GLFW_KEY_APOSTROPHE, GLFW_KEY_PERIOD, GLFW_KEY_SLASH, 0 };
|
||||
IM_ASSERT(IM_ARRAYSIZE(char_names) == IM_ARRAYSIZE(char_keys));
|
||||
if (key_name[0] >= '0' && key_name[0] <= '9') { key = GLFW_KEY_0 + (key_name[0] - '0'); }
|
||||
else if (key_name[0] >= 'A' && key_name[0] <= 'Z') { key = GLFW_KEY_A + (key_name[0] - 'A'); }
|
||||
else if (const char* p = strchr(char_names, key_name[0])) { key = char_keys[p - char_names]; }
|
||||
}
|
||||
// if (action == GLFW_PRESS) printf("key %d scancode %d name '%s'\n", key, scancode, key_name);
|
||||
#else
|
||||
IM_UNUSED(scancode);
|
||||
#endif
|
||||
return key;
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, int action, int mods)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->PrevUserCallbackKey != NULL && window == bd->Window)
|
||||
bd->PrevUserCallbackKey(window, keycode, scancode, action, mods);
|
||||
|
||||
if (action != GLFW_PRESS && action != GLFW_RELEASE)
|
||||
return;
|
||||
|
||||
ImGui_ImplGlfw_UpdateKeyModifiers(mods);
|
||||
|
||||
keycode = ImGui_ImplGlfw_TranslateUntranslatedKey(keycode, scancode);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiKey imgui_key = ImGui_ImplGlfw_KeyToImGuiKey(keycode);
|
||||
io.AddKeyEvent(imgui_key, (action == GLFW_PRESS));
|
||||
io.SetKeyEventNativeData(imgui_key, keycode, scancode); // To support legacy indexing (<1.87 user code)
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->PrevUserCallbackWindowFocus != NULL && window == bd->Window)
|
||||
bd->PrevUserCallbackWindowFocus(window, focused);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.AddFocusEvent(focused != 0);
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->PrevUserCallbackCursorPos != NULL && window == bd->Window)
|
||||
bd->PrevUserCallbackCursorPos(window, x, y);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.AddMousePosEvent((float)x, (float)y);
|
||||
bd->LastValidMousePos = ImVec2((float)x, (float)y);
|
||||
}
|
||||
|
||||
// Workaround: X11 seems to send spurious Leave/Enter events which would make us lose our position,
|
||||
// so we back it up and restore on Leave/Enter (see https://github.com/ocornut/imgui/issues/4984)
|
||||
void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->PrevUserCallbackCursorEnter != NULL && window == bd->Window)
|
||||
bd->PrevUserCallbackCursorEnter(window, entered);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (entered)
|
||||
{
|
||||
bd->MouseWindow = window;
|
||||
io.AddMousePosEvent(bd->LastValidMousePos.x, bd->LastValidMousePos.y);
|
||||
}
|
||||
else if (!entered && bd->MouseWindow == window)
|
||||
{
|
||||
bd->LastValidMousePos = io.MousePos;
|
||||
bd->MouseWindow = NULL;
|
||||
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->PrevUserCallbackChar != NULL && window == bd->Window)
|
||||
bd->PrevUserCallbackChar(window, c);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.AddInputCharacter(c);
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int)
|
||||
{
|
||||
// Unused in 'master' branch but 'docking' branch will use this, so we declare it ahead of it so if you have to install callbacks you can install this one too.
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
IM_ASSERT(bd->InstalledCallbacks == false && "Callbacks already installed!");
|
||||
IM_ASSERT(bd->Window == window);
|
||||
|
||||
bd->PrevUserCallbackWindowFocus = glfwSetWindowFocusCallback(window, ImGui_ImplGlfw_WindowFocusCallback);
|
||||
bd->PrevUserCallbackCursorEnter = glfwSetCursorEnterCallback(window, ImGui_ImplGlfw_CursorEnterCallback);
|
||||
bd->PrevUserCallbackCursorPos = glfwSetCursorPosCallback(window, ImGui_ImplGlfw_CursorPosCallback);
|
||||
bd->PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback);
|
||||
bd->PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback);
|
||||
bd->PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback);
|
||||
bd->PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
|
||||
bd->PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback);
|
||||
bd->InstalledCallbacks = true;
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window)
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
IM_ASSERT(bd->InstalledCallbacks == true && "Callbacks not installed!");
|
||||
IM_ASSERT(bd->Window == window);
|
||||
|
||||
glfwSetWindowFocusCallback(window, bd->PrevUserCallbackWindowFocus);
|
||||
glfwSetCursorEnterCallback(window, bd->PrevUserCallbackCursorEnter);
|
||||
glfwSetCursorPosCallback(window, bd->PrevUserCallbackCursorPos);
|
||||
glfwSetMouseButtonCallback(window, bd->PrevUserCallbackMousebutton);
|
||||
glfwSetScrollCallback(window, bd->PrevUserCallbackScroll);
|
||||
glfwSetKeyCallback(window, bd->PrevUserCallbackKey);
|
||||
glfwSetCharCallback(window, bd->PrevUserCallbackChar);
|
||||
glfwSetMonitorCallback(bd->PrevUserCallbackMonitor);
|
||||
bd->InstalledCallbacks = false;
|
||||
bd->PrevUserCallbackWindowFocus = NULL;
|
||||
bd->PrevUserCallbackCursorEnter = NULL;
|
||||
bd->PrevUserCallbackCursorPos = NULL;
|
||||
bd->PrevUserCallbackMousebutton = NULL;
|
||||
bd->PrevUserCallbackScroll = NULL;
|
||||
bd->PrevUserCallbackKey = NULL;
|
||||
bd->PrevUserCallbackChar = NULL;
|
||||
bd->PrevUserCallbackMonitor = NULL;
|
||||
}
|
||||
|
||||
static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!");
|
||||
|
||||
// Setup backend capabilities flags
|
||||
ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)();
|
||||
io.BackendPlatformUserData = (void*)bd;
|
||||
io.BackendPlatformName = "imgui_impl_glfw";
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
|
||||
|
||||
bd->Window = window;
|
||||
bd->Time = 0.0;
|
||||
|
||||
io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
|
||||
io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
|
||||
io.ClipboardUserData = bd->Window;
|
||||
|
||||
// Set platform dependent data in viewport
|
||||
#if defined(_WIN32)
|
||||
ImGui::GetMainViewport()->PlatformHandleRaw = (void*)glfwGetWin32Window(bd->Window);
|
||||
#endif
|
||||
|
||||
// Create mouse cursors
|
||||
// (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist,
|
||||
// GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting.
|
||||
// Missing cursors will return NULL and our _UpdateMouseCursor() function will use the Arrow cursor instead.)
|
||||
GLFWerrorfun prev_error_callback = glfwSetErrorCallback(NULL);
|
||||
bd->MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
|
||||
#if GLFW_HAS_NEW_CURSORS
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
|
||||
#else
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||
bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||
#endif
|
||||
glfwSetErrorCallback(prev_error_callback);
|
||||
|
||||
// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
|
||||
if (install_callbacks)
|
||||
ImGui_ImplGlfw_InstallCallbacks(window);
|
||||
|
||||
bd->ClientApi = client_api;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks)
|
||||
{
|
||||
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL);
|
||||
}
|
||||
|
||||
bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks)
|
||||
{
|
||||
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan);
|
||||
}
|
||||
|
||||
bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks)
|
||||
{
|
||||
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown);
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_Shutdown()
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
IM_ASSERT(bd != NULL && "No platform backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
if (bd->InstalledCallbacks)
|
||||
ImGui_ImplGlfw_RestoreCallbacks(bd->Window);
|
||||
|
||||
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
|
||||
glfwDestroyCursor(bd->MouseCursors[cursor_n]);
|
||||
|
||||
io.BackendPlatformName = NULL;
|
||||
io.BackendPlatformUserData = NULL;
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
static void ImGui_ImplGlfw_UpdateMouseData()
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
const bool is_app_focused = true;
|
||||
#else
|
||||
const bool is_app_focused = glfwGetWindowAttrib(bd->Window, GLFW_FOCUSED) != 0;
|
||||
#endif
|
||||
if (is_app_focused)
|
||||
{
|
||||
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
|
||||
if (io.WantSetMousePos)
|
||||
glfwSetCursorPos(bd->Window, (double)io.MousePos.x, (double)io.MousePos.y);
|
||||
|
||||
// (Optional) Fallback to provide mouse position when focused (ImGui_ImplGlfw_CursorPosCallback already provides this when hovered or captured)
|
||||
if (is_app_focused && bd->MouseWindow == NULL)
|
||||
{
|
||||
double mouse_x, mouse_y;
|
||||
glfwGetCursorPos(bd->Window, &mouse_x, &mouse_y);
|
||||
io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
|
||||
bd->LastValidMousePos = ImVec2((float)mouse_x, (float)mouse_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ImGui_ImplGlfw_UpdateMouseCursor()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
|
||||
return;
|
||||
|
||||
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
|
||||
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
|
||||
{
|
||||
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
|
||||
glfwSetInputMode(bd->Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show OS mouse cursor
|
||||
// FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here.
|
||||
glfwSetCursor(bd->Window, bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]);
|
||||
glfwSetInputMode(bd->Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Update gamepad inputs
|
||||
static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; }
|
||||
static void ImGui_ImplGlfw_UpdateGamepads()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
|
||||
return;
|
||||
|
||||
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
|
||||
#if GLFW_HAS_GAMEPAD_API
|
||||
GLFWgamepadstate gamepad;
|
||||
if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &gamepad))
|
||||
return;
|
||||
#define MAP_BUTTON(KEY_NO, BUTTON_NO, _UNUSED) do { io.AddKeyEvent(KEY_NO, gamepad.buttons[BUTTON_NO] != 0); } while (0)
|
||||
#define MAP_ANALOG(KEY_NO, AXIS_NO, _UNUSED, V0, V1) do { float v = gamepad.axes[AXIS_NO]; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
|
||||
#else
|
||||
int axes_count = 0, buttons_count = 0;
|
||||
const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count);
|
||||
const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
|
||||
if (axes_count == 0 || buttons_count == 0)
|
||||
return;
|
||||
#define MAP_BUTTON(KEY_NO, _UNUSED, BUTTON_NO) do { io.AddKeyEvent(KEY_NO, (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS)); } while (0)
|
||||
#define MAP_ANALOG(KEY_NO, _UNUSED, AXIS_NO, V0, V1) do { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
|
||||
#endif
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||
MAP_BUTTON(ImGuiKey_GamepadStart, GLFW_GAMEPAD_BUTTON_START, 7);
|
||||
MAP_BUTTON(ImGuiKey_GamepadBack, GLFW_GAMEPAD_BUTTON_BACK, 6);
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceDown, GLFW_GAMEPAD_BUTTON_A, 0); // Xbox A, PS Cross
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceRight, GLFW_GAMEPAD_BUTTON_B, 1); // Xbox B, PS Circle
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceLeft, GLFW_GAMEPAD_BUTTON_X, 2); // Xbox X, PS Square
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceUp, GLFW_GAMEPAD_BUTTON_Y, 3); // Xbox Y, PS Triangle
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadLeft, GLFW_GAMEPAD_BUTTON_DPAD_LEFT, 13);
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadRight, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, 11);
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadUp, GLFW_GAMEPAD_BUTTON_DPAD_UP, 10);
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadDown, GLFW_GAMEPAD_BUTTON_DPAD_DOWN, 12);
|
||||
MAP_BUTTON(ImGuiKey_GamepadL1, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, 4);
|
||||
MAP_BUTTON(ImGuiKey_GamepadR1, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, 5);
|
||||
MAP_ANALOG(ImGuiKey_GamepadL2, GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, 4, -0.75f, +1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadR2, GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, 5, -0.75f, +1.0f);
|
||||
MAP_BUTTON(ImGuiKey_GamepadL3, GLFW_GAMEPAD_BUTTON_LEFT_THUMB, 8);
|
||||
MAP_BUTTON(ImGuiKey_GamepadR3, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, 9);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickLeft, GLFW_GAMEPAD_AXIS_LEFT_X, 0, -0.25f, -1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickRight, GLFW_GAMEPAD_AXIS_LEFT_X, 0, +0.25f, +1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickUp, GLFW_GAMEPAD_AXIS_LEFT_Y, 1, -0.25f, -1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickDown, GLFW_GAMEPAD_AXIS_LEFT_Y, 1, +0.25f, +1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickLeft, GLFW_GAMEPAD_AXIS_RIGHT_X, 2, -0.25f, -1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickRight, GLFW_GAMEPAD_AXIS_RIGHT_X, 2, +0.25f, +1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickUp, GLFW_GAMEPAD_AXIS_RIGHT_Y, 3, -0.25f, -1.0f);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickDown, GLFW_GAMEPAD_AXIS_RIGHT_Y, 3, +0.25f, +1.0f);
|
||||
#undef MAP_BUTTON
|
||||
#undef MAP_ANALOG
|
||||
}
|
||||
|
||||
void ImGui_ImplGlfw_NewFrame()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
IM_ASSERT(bd != NULL && "Did you call ImGui_ImplGlfw_InitForXXX()?");
|
||||
|
||||
// Setup display size (every frame to accommodate for window resizing)
|
||||
int w, h;
|
||||
int display_w, display_h;
|
||||
glfwGetWindowSize(bd->Window, &w, &h);
|
||||
glfwGetFramebufferSize(bd->Window, &display_w, &display_h);
|
||||
io.DisplaySize = ImVec2((float)w, (float)h);
|
||||
if (w > 0 && h > 0)
|
||||
io.DisplayFramebufferScale = ImVec2((float)display_w / (float)w, (float)display_h / (float)h);
|
||||
|
||||
// Setup time step
|
||||
double current_time = glfwGetTime();
|
||||
io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f);
|
||||
bd->Time = current_time;
|
||||
|
||||
ImGui_ImplGlfw_UpdateMouseData();
|
||||
ImGui_ImplGlfw_UpdateMouseCursor();
|
||||
|
||||
// Update game controllers (if enabled and available)
|
||||
ImGui_ImplGlfw_UpdateGamepads();
|
||||
}
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
// Implemented features:
|
||||
// [X] Platform: Clipboard support.
|
||||
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
|
||||
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
|
||||
// [x] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: 3 cursors types are missing from GLFW.
|
||||
// [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE).
|
||||
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
|
||||
|
||||
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
|
||||
// Read online: https://github.com/ocornut/imgui/tree/master/docs
|
||||
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "imgui.h" // IMGUI_IMPL_API
|
||||
|
||||
struct GLFWwindow;
|
||||
struct GLFWmonitor;
|
||||
|
||||
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks);
|
||||
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks);
|
||||
@@ -27,10 +29,18 @@ IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool ins
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame();
|
||||
|
||||
// GLFW callbacks
|
||||
// - When calling Init with 'install_callbacks=true': GLFW callbacks will be installed for you. They will call user's previously installed callbacks, if any.
|
||||
// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call those function yourself from your own GLFW callbacks.
|
||||
// GLFW callbacks (installer)
|
||||
// - When calling Init with 'install_callbacks=true': ImGui_ImplGlfw_InstallCallbacks() is called. GLFW callbacks will be installed for you. They will chain-call user's previously installed callbacks, if any.
|
||||
// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call individual function yourself from your own GLFW callbacks.
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window);
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window);
|
||||
|
||||
// GLFW callbacks (individual callbacks to call if you didn't install callbacks)
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused); // Since 1.84
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered); // Since 1.84
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y); // Since 1.87
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c);
|
||||
IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event);
|
||||
@@ -17,8 +17,14 @@ if(COMPILER_SUPPORTS_WPEDANTIC)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wpedantic")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -O0 -fsanitize=address")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie,")
|
||||
# On android, keep optimisations and don't use asan
|
||||
if (ANDROID)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -O3")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,")
|
||||
else()
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -O0 -fsanitize=address")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie,")
|
||||
endif()
|
||||
else()
|
||||
if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
|
||||
@@ -85,9 +91,9 @@ add_subdirectory(tests)
|
||||
add_subdirectory(tools)
|
||||
# add_subdirectory(benchmarks)
|
||||
|
||||
install(TARGETS correct correct_static
|
||||
DESTINATION lib)
|
||||
install(FILES ${INSTALL_HEADERS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include")
|
||||
# install(TARGETS correct correct_static
|
||||
# DESTINATION lib)
|
||||
# install(FILES ${INSTALL_HEADERS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include")
|
||||
|
||||
add_library(fec_shim_static EXCLUDE_FROM_ALL src/fec_shim.c ${correct_obj_files})
|
||||
set_target_properties(fec_shim_static PROPERTIES OUTPUT_NAME "fec")
|
||||
@@ -96,7 +102,7 @@ set_target_properties(fec_shim_shared PROPERTIES OUTPUT_NAME "fec")
|
||||
add_custom_target(fec-shim-h COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/include/fec_shim.h ${PROJECT_BINARY_DIR}/include/fec.h)
|
||||
add_custom_target(shim DEPENDS fec_shim_static fec_shim_shared fec-shim-h)
|
||||
|
||||
install(TARGETS fec_shim_static fec_shim_shared
|
||||
DESTINATION lib
|
||||
OPTIONAL)
|
||||
install(FILES ${PROJECT_BINARY_DIR}/include/fec.h DESTINATION "${CMAKE_INSTALL_PREFIX}/include" OPTIONAL)
|
||||
# install(TARGETS fec_shim_static fec_shim_shared
|
||||
# DESTINATION lib
|
||||
# OPTIONAL)
|
||||
# install(FILES ${PROJECT_BINARY_DIR}/include/fec.h DESTINATION "${CMAKE_INSTALL_PREFIX}/include" OPTIONAL)
|
||||
|
||||
@@ -45,7 +45,7 @@ uint8_t *history_buffer_get_slice(history_buffer *buf) { return buf->history[buf
|
||||
|
||||
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
|
||||
unsigned int search_every) {
|
||||
shift_register_t bestpath;
|
||||
shift_register_t bestpath = 0;
|
||||
distance_t leasterror = USHRT_MAX;
|
||||
// search for a state with the least error
|
||||
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {
|
||||
|
||||
12
core/src/backend.h
Normal file
12
core/src/backend.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace backend {
|
||||
int init(std::string resDir = "");
|
||||
void beginFrame();
|
||||
void render(bool vsync = true);
|
||||
void getMouseScreenPos(double& x, double& y);
|
||||
void setMouseScreenPos(double x, double y);
|
||||
int renderLoop();
|
||||
int end();
|
||||
}
|
||||
124
core/src/command_args.cpp
Normal file
124
core/src/command_args.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "command_args.h"
|
||||
#include <filesystem>
|
||||
|
||||
void CommandArgsParser::defineAll() {
|
||||
#if defined(_WIN32)
|
||||
std::string root = ".";
|
||||
define('c', "con", "Show console on Windows");
|
||||
#elif defined(IS_MACOS_BUNDLE)
|
||||
std::string root = (std::string)getenv("HOME") + "/Library/Application Support/sdrpp";
|
||||
#elif defined(__ANDROID__)
|
||||
std::string root = "/storage/self/primary/sdrpp";
|
||||
#else
|
||||
std::string root = (std::string)getenv("HOME") + "/.config/sdrpp";
|
||||
#endif
|
||||
|
||||
define('a', "addr", "Server mode address", "0.0.0.0");
|
||||
define('h', "help", "Show help");
|
||||
define('p', "port", "Server mode port", 5259);
|
||||
define('r', "root", "Root directory, where all config files are stored", std::filesystem::absolute(root).string());
|
||||
define('s', "server", "Run in server mode");
|
||||
define('\0', "autostart", "Automatically start the SDR after loading");
|
||||
}
|
||||
|
||||
int CommandArgsParser::parse(int argc, char* argv[]) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
// Check for long and short name arguments
|
||||
if (!arg.rfind("--", 0)) {
|
||||
arg = arg.substr(2);
|
||||
}
|
||||
else if (!arg.rfind("-", 0)) {
|
||||
if (aliases.find(arg[1]) == aliases.end()) {
|
||||
printf("Unknown argument\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
}
|
||||
arg = aliases[arg[1]];
|
||||
}
|
||||
else {
|
||||
printf("Invalid argument\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Make sure the argument exists
|
||||
if (args.find(arg) == args.end()) {
|
||||
printf("Unknown argument\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse depending on type
|
||||
CLIArg& carg = args[arg];
|
||||
|
||||
// If not void, make sure an argument is available and retrieve it
|
||||
if (carg.type != CLI_ARG_TYPE_VOID && i + 1 >= argc) {
|
||||
printf("Missing argument\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse void since arg won't be needed
|
||||
if (carg.type == CLI_ARG_TYPE_VOID) {
|
||||
carg.bval = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse types that require parsing
|
||||
arg = argv[++i];
|
||||
if (carg.type == CLI_ARG_TYPE_BOOL) {
|
||||
// Enforce lower case
|
||||
for (int i = 0; i < arg.size(); i++) { arg[i] = std::tolower(arg[i]); }
|
||||
|
||||
if (arg == "true" || arg == "on" || arg == "1") {
|
||||
carg.bval = true;
|
||||
}
|
||||
else if (arg == "false" || arg == "off" || arg == "0") {
|
||||
carg.bval = true;
|
||||
}
|
||||
else {
|
||||
printf("Invalid argument, expected bool (true, false, on, off, 1, 0)\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (carg.type == CLI_ARG_TYPE_INT) {
|
||||
try {
|
||||
carg.ival = std::stoi(arg);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
printf("Invalid argument, failed to parse integer\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (carg.type == CLI_ARG_TYPE_FLOAT) {
|
||||
try {
|
||||
carg.fval = std::stod(arg);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
printf("Invalid argument, failed to parse float\n");
|
||||
showHelp();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (carg.type == CLI_ARG_TYPE_STRING) {
|
||||
carg.sval = arg;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CommandArgsParser::showHelp() {
|
||||
for (auto const& [ln, arg] : args) {
|
||||
if (arg.alias) {
|
||||
printf("-%c --%s\t\t%s\n", arg.alias, ln.c_str(), arg.description.c_str());
|
||||
}
|
||||
else {
|
||||
printf(" --%s\t\t%s\n", ln.c_str(), arg.description.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
153
core/src/command_args.h
Normal file
153
core/src/command_args.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
enum CLIArgType {
|
||||
CLI_ARG_TYPE_INVALID,
|
||||
CLI_ARG_TYPE_VOID,
|
||||
CLI_ARG_TYPE_BOOL,
|
||||
CLI_ARG_TYPE_INT,
|
||||
CLI_ARG_TYPE_FLOAT,
|
||||
CLI_ARG_TYPE_STRING
|
||||
};
|
||||
|
||||
class CommandArgsParser;
|
||||
|
||||
class CLIArg {
|
||||
public:
|
||||
CLIArg() {
|
||||
type = CLI_ARG_TYPE_INVALID;
|
||||
}
|
||||
|
||||
CLIArg(char al, std::string desc) {
|
||||
alias = al;
|
||||
description = desc;
|
||||
type = CLI_ARG_TYPE_VOID;
|
||||
bval = false;
|
||||
}
|
||||
|
||||
CLIArg(char al, std::string desc, bool b) {
|
||||
alias = al;
|
||||
description = desc;
|
||||
type = CLI_ARG_TYPE_BOOL;
|
||||
bval = b;
|
||||
}
|
||||
|
||||
CLIArg(char al, std::string desc, int i) {
|
||||
alias = al;
|
||||
description = desc;
|
||||
type = CLI_ARG_TYPE_INT;
|
||||
ival = i;
|
||||
}
|
||||
|
||||
CLIArg(char al, std::string desc, double f) {
|
||||
alias = al;
|
||||
description = desc;
|
||||
type = CLI_ARG_TYPE_FLOAT;
|
||||
fval = f;
|
||||
}
|
||||
|
||||
CLIArg(char al, std::string desc, std::string s) {
|
||||
alias = al;
|
||||
description = desc;
|
||||
type = CLI_ARG_TYPE_STRING;
|
||||
sval = s;
|
||||
}
|
||||
|
||||
CLIArg(char al, std::string desc, const char* s) {
|
||||
alias = al;
|
||||
description = desc;
|
||||
type = CLI_ARG_TYPE_STRING;
|
||||
sval = s;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
if (type != CLI_ARG_TYPE_BOOL && type != CLI_ARG_TYPE_VOID) { throw std::runtime_error("Not a bool"); }
|
||||
return bval;
|
||||
}
|
||||
|
||||
operator int() const {
|
||||
if (type != CLI_ARG_TYPE_INT) { throw std::runtime_error("Not an int"); }
|
||||
return ival;
|
||||
}
|
||||
|
||||
operator float() const {
|
||||
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
|
||||
return (float)fval;
|
||||
}
|
||||
|
||||
operator double() const {
|
||||
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
|
||||
return fval;
|
||||
}
|
||||
|
||||
operator std::string() const {
|
||||
if (type != CLI_ARG_TYPE_STRING) { throw std::runtime_error("Not a string"); }
|
||||
return sval;
|
||||
}
|
||||
|
||||
bool b() {
|
||||
if (type != CLI_ARG_TYPE_BOOL && type != CLI_ARG_TYPE_VOID) { throw std::runtime_error("Not a bool"); }
|
||||
return bval;
|
||||
}
|
||||
|
||||
int i() {
|
||||
if (type != CLI_ARG_TYPE_INT) { throw std::runtime_error("Not an int"); }
|
||||
return ival;
|
||||
}
|
||||
|
||||
float f() {
|
||||
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
|
||||
return (float)fval;
|
||||
}
|
||||
|
||||
double d() {
|
||||
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
|
||||
return fval;
|
||||
}
|
||||
|
||||
const std::string& s() {
|
||||
if (type != CLI_ARG_TYPE_STRING) { throw std::runtime_error("Not a string"); }
|
||||
return sval;
|
||||
}
|
||||
|
||||
friend CommandArgsParser;
|
||||
|
||||
CLIArgType type;
|
||||
char alias;
|
||||
std::string description;
|
||||
|
||||
private:
|
||||
bool bval;
|
||||
int ival;
|
||||
std::string sval;
|
||||
double fval;
|
||||
};
|
||||
|
||||
class CommandArgsParser {
|
||||
public:
|
||||
void define(char shortName, std::string name, std::string desc) {
|
||||
args[name] = CLIArg(shortName, desc);
|
||||
aliases[shortName] = name;
|
||||
}
|
||||
|
||||
void defineAll();
|
||||
|
||||
template<class T>
|
||||
void define(char shortName, std::string name, std::string desc, T defValue) {
|
||||
args[name] = CLIArg(shortName, desc, defValue);
|
||||
aliases[shortName] = name;
|
||||
}
|
||||
|
||||
int parse(int argc, char* argv[]);
|
||||
void showHelp();
|
||||
|
||||
CLIArg operator[](std::string name) {
|
||||
return args[name];
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, CLIArg> args;
|
||||
std::map<char, std::string> aliases;
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
#include <config.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <utils/flog.h>
|
||||
#include <fstream>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
ConfigManager::ConfigManager() {
|
||||
|
||||
}
|
||||
|
||||
ConfigManager::~ConfigManager() {
|
||||
@@ -13,32 +12,32 @@ ConfigManager::~ConfigManager() {
|
||||
}
|
||||
|
||||
void ConfigManager::setPath(std::string file) {
|
||||
path = file;
|
||||
path = std::filesystem::absolute(file).string();
|
||||
}
|
||||
|
||||
void ConfigManager::load(json def, bool lock) {
|
||||
if (lock) { mtx.lock(); }
|
||||
if (path == "") {
|
||||
spdlog::error("Config manager tried to load file with no path specified");
|
||||
flog::error("Config manager tried to load file with no path specified");
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::warn("Config file '{0}' does not exist, creating it", path);
|
||||
flog::warn("Config file '{0}' does not exist, creating it", path);
|
||||
conf = def;
|
||||
save(false);
|
||||
}
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
spdlog::error("Config file '{0}' isn't a file", path);
|
||||
flog::error("Config file '{0}' isn't a file", path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
std::ifstream file(path.c_str());
|
||||
file >> conf;
|
||||
file.close();
|
||||
}
|
||||
catch (std::exception e) {
|
||||
spdlog::error("Config file '{0}' is corrupted, resetting it", path);
|
||||
catch (const std::exception& e) {
|
||||
flog::error("Config file '{}' is corrupted, resetting it: {}", path, e.what());
|
||||
conf = def;
|
||||
save(false);
|
||||
}
|
||||
@@ -83,7 +82,7 @@ void ConfigManager::release(bool modified) {
|
||||
void ConfigManager::autoSaveWorker() {
|
||||
while (autoSaveEnabled) {
|
||||
if (!mtx.try_lock()) {
|
||||
spdlog::warn("ConfigManager locked, waiting...");
|
||||
flog::warn("ConfigManager locked, waiting...");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
continue;
|
||||
}
|
||||
@@ -96,7 +95,7 @@ void ConfigManager::autoSaveWorker() {
|
||||
// Sleep but listen for wakeup call
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(termMtx);
|
||||
termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; } );
|
||||
termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
void release(bool modified = false);
|
||||
|
||||
json conf;
|
||||
|
||||
|
||||
private:
|
||||
void autoSaveWorker();
|
||||
|
||||
@@ -33,5 +33,4 @@ private:
|
||||
std::mutex termMtx;
|
||||
std::condition_variable termCond;
|
||||
volatile bool termFlag = false;
|
||||
|
||||
};
|
||||
@@ -1,24 +1,19 @@
|
||||
#include <server.h>
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <stdio.h>
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/icons.h>
|
||||
#include <version.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <utils/flog.h>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <stb_image.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
#include <glfw_window.h>
|
||||
#include <options.h>
|
||||
#include <filesystem>
|
||||
#include <gui/menus/theme.h>
|
||||
#include <server.h>
|
||||
#include <backend.h>
|
||||
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include <stb_image_resize.h>
|
||||
@@ -30,97 +25,81 @@
|
||||
#endif
|
||||
|
||||
#ifndef INSTALL_PREFIX
|
||||
#ifdef __APPLE__
|
||||
#define INSTALL_PREFIX "/usr/local"
|
||||
#else
|
||||
#define INSTALL_PREFIX "/usr"
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#define INSTALL_PREFIX "/usr/local"
|
||||
#else
|
||||
#define INSTALL_PREFIX "/usr"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const char* OPENGL_VERSIONS_GLSL[] = {
|
||||
"#version 120",
|
||||
"#version 300 es",
|
||||
"#version 120"
|
||||
};
|
||||
|
||||
const int OPENGL_VERSIONS_MAJOR[] = {
|
||||
3,
|
||||
3,
|
||||
2
|
||||
};
|
||||
|
||||
const int OPENGL_VERSIONS_MINOR[] = {
|
||||
0,
|
||||
1,
|
||||
1
|
||||
};
|
||||
|
||||
const bool OPENGL_VERSIONS_IS_ES[] = {
|
||||
false,
|
||||
true,
|
||||
false
|
||||
};
|
||||
|
||||
#define OPENGL_VERSION_COUNT (sizeof(OPENGL_VERSIONS_GLSL) / sizeof(char*))
|
||||
|
||||
namespace core {
|
||||
ConfigManager configManager;
|
||||
ModuleManager moduleManager;
|
||||
ModuleComManager modComManager;
|
||||
GLFWwindow* window;
|
||||
CommandArgsParser args;
|
||||
|
||||
void setInputSampleRate(double samplerate) {
|
||||
sigpath::signalPath.sourceSampleRate = samplerate;
|
||||
double effectiveSr = samplerate / ((double)(1 << sigpath::signalPath.decimation));
|
||||
// NOTE: Zoom controls won't work
|
||||
spdlog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate);
|
||||
// Forward this to the server
|
||||
if (args["server"].b()) { server::setInputSampleRate(samplerate); return; }
|
||||
|
||||
// Update IQ frontend input samplerate and get effective samplerate
|
||||
sigpath::iqFrontEnd.setSampleRate(samplerate);
|
||||
double effectiveSr = sigpath::iqFrontEnd.getEffectiveSamplerate();
|
||||
|
||||
// Reset zoom
|
||||
gui::waterfall.setBandwidth(effectiveSr);
|
||||
gui::waterfall.setViewOffset(0);
|
||||
gui::waterfall.setViewBandwidth(effectiveSr);
|
||||
sigpath::signalPath.setSampleRate(effectiveSr);
|
||||
gui::mainWindow.setViewBandwidthSlider(1.0);
|
||||
|
||||
// Debug logs
|
||||
flog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate);
|
||||
}
|
||||
};
|
||||
|
||||
bool maximized = false;
|
||||
bool fullScreen = false;
|
||||
|
||||
static void glfw_error_callback(int error, const char* description) {
|
||||
spdlog::error("Glfw Error {0}: {1}", error, description);
|
||||
}
|
||||
|
||||
static void maximized_callback(GLFWwindow* window, int n) {
|
||||
if (n == GLFW_TRUE) {
|
||||
maximized = true;
|
||||
}
|
||||
else {
|
||||
maximized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// main
|
||||
int sdrpp_main(int argc, char *argv[]) {
|
||||
spdlog::info("SDR++ v" VERSION_STR);
|
||||
int sdrpp_main(int argc, char* argv[]) {
|
||||
flog::info("SDR++ v" VERSION_STR);
|
||||
|
||||
// Load default options and parse command line
|
||||
options::loadDefaults();
|
||||
if (!options::parse(argc, argv)) { return -1; }
|
||||
#ifdef IS_MACOS_BUNDLE
|
||||
// If this is a MacOS .app, CD to the correct directory
|
||||
auto execPath = std::filesystem::absolute(argv[0]);
|
||||
chdir(execPath.parent_path().string().c_str());
|
||||
#endif
|
||||
|
||||
// Define command line options and parse arguments
|
||||
core::args.defineAll();
|
||||
if (core::args.parse(argc, argv) < 0) { return -1; }
|
||||
|
||||
// Show help and exit if requested
|
||||
if (core::args["help"].b()) {
|
||||
core::args.showHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool serverMode = (bool)core::args["server"];
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!options::opts.showConsole) { FreeConsole(); }
|
||||
// Free console if the user hasn't asked for a console and not in server mode
|
||||
if (!core::args["con"].b() && !serverMode) { FreeConsole(); }
|
||||
|
||||
// Set error mode to avoid abnoxious popups
|
||||
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS);
|
||||
#endif
|
||||
|
||||
// Check root directory
|
||||
if (!std::filesystem::exists(options::opts.root)) {
|
||||
spdlog::warn("Root directory {0} does not exist, creating it", options::opts.root);
|
||||
if (!std::filesystem::create_directory(options::opts.root)) {
|
||||
spdlog::error("Could not create root directory {0}", options::opts.root);
|
||||
std::string root = (std::string)core::args["root"];
|
||||
if (!std::filesystem::exists(root)) {
|
||||
flog::warn("Root directory {0} does not exist, creating it", root);
|
||||
if (!std::filesystem::create_directories(root)) {
|
||||
flog::error("Could not create root directory {0}", root);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(options::opts.root)) {
|
||||
spdlog::error("{0} is not a directory", options::opts.root);
|
||||
// Check that the path actually is a directory
|
||||
if (!std::filesystem::is_directory(root)) {
|
||||
flog::error("{0} is not a directory", root);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -136,15 +115,22 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
defConfig["bandPlanPos"] = 0;
|
||||
defConfig["centerTuning"] = false;
|
||||
defConfig["colorMap"] = "Classic";
|
||||
defConfig["fftHold"] = false;
|
||||
defConfig["fftHoldSpeed"] = 60;
|
||||
defConfig["fftSmoothing"] = false;
|
||||
defConfig["fftSmoothingSpeed"] = 100;
|
||||
defConfig["snrSmoothing"] = false;
|
||||
defConfig["snrSmoothingSpeed"] = 20;
|
||||
defConfig["fastFFT"] = false;
|
||||
defConfig["fftHeight"] = 300;
|
||||
defConfig["fftRate"] = 20;
|
||||
defConfig["fftSize"] = 65536;
|
||||
defConfig["fftWindow"] = 1;
|
||||
defConfig["fftWindow"] = 2;
|
||||
defConfig["frequency"] = 100000000.0;
|
||||
defConfig["fullWaterfallUpdate"] = false;
|
||||
defConfig["max"] = 0.0;
|
||||
defConfig["maximized"] = false;
|
||||
defConfig["fullscreen"] = false;
|
||||
|
||||
// Menu
|
||||
defConfig["menuElements"] = json::array();
|
||||
@@ -167,9 +153,6 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
defConfig["menuElements"][4]["name"] = "VFO Color";
|
||||
defConfig["menuElements"][4]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][5]["name"] = "Scripting";
|
||||
defConfig["menuElements"][5]["open"] = false;
|
||||
|
||||
defConfig["menuElements"][6]["name"] = "Band Plan";
|
||||
defConfig["menuElements"][6]["open"] = true;
|
||||
|
||||
@@ -184,26 +167,44 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
defConfig["moduleInstances"]["Airspy Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["AirspyHF+ Source"]["module"] = "airspyhf_source";
|
||||
defConfig["moduleInstances"]["AirspyHF+ Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Audio Source"]["module"] = "audio_source";
|
||||
defConfig["moduleInstances"]["Audio Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source";
|
||||
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
|
||||
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source";
|
||||
defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
|
||||
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source";
|
||||
defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
|
||||
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
|
||||
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source";
|
||||
defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
|
||||
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
|
||||
defConfig["moduleInstances"]["RTL-SDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["RTL-TCP Source"]["module"] = "rtl_tcp_source";
|
||||
defConfig["moduleInstances"]["RTL-TCP Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SDRplay Source"]["module"] = "sdrplay_source";
|
||||
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source";
|
||||
defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
|
||||
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source";
|
||||
defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
|
||||
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
|
||||
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["USRP Source"]["module"] = "usrp_source";
|
||||
defConfig["moduleInstances"]["USRP Source"]["enabled"] = true;
|
||||
|
||||
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
|
||||
defConfig["moduleInstances"]["Network Sink"] = "network_sink";
|
||||
@@ -213,12 +214,22 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager";
|
||||
defConfig["moduleInstances"]["Recorder"] = "recorder";
|
||||
defConfig["moduleInstances"]["Rigctl Server"] = "rigctl_server";
|
||||
|
||||
// defConfig["moduleInstances"]["Rigctl Client"] = "rigctl_client";
|
||||
// TODO: Enable rigctl_client when ready
|
||||
// defConfig["moduleInstances"]["Scanner"] = "scanner";
|
||||
// TODO: Enable scanner when ready
|
||||
|
||||
|
||||
// Themes
|
||||
defConfig["theme"] = "Dark";
|
||||
#ifdef __ANDROID__
|
||||
defConfig["uiScale"] = 3.0f;
|
||||
#else
|
||||
defConfig["uiScale"] = 1.0f;
|
||||
#endif
|
||||
|
||||
defConfig["modules"] = json::array();
|
||||
|
||||
defConfig["offsetMode"] = (int)0; // Off
|
||||
defConfig["offset"] = 0.0;
|
||||
defConfig["showMenu"] = true;
|
||||
@@ -226,6 +237,7 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
defConfig["source"] = "";
|
||||
defConfig["decimationPower"] = 0;
|
||||
defConfig["iqCorrection"] = false;
|
||||
defConfig["invertIQ"] = false;
|
||||
|
||||
defConfig["streams"]["Radio"]["muted"] = false;
|
||||
defConfig["streams"]["Radio"]["sink"] = "Audio";
|
||||
@@ -238,26 +250,66 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
|
||||
defConfig["vfoColors"]["Radio"] = "#FFFFFF";
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef __ANDROID__
|
||||
defConfig["lockMenuOrder"] = true;
|
||||
#else
|
||||
defConfig["lockMenuOrder"] = false;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
defConfig["modulesDirectory"] = "./modules";
|
||||
defConfig["resourcesDirectory"] = "./res";
|
||||
#elif defined(IS_MACOS_BUNDLE)
|
||||
defConfig["modulesDirectory"] = "../Plugins";
|
||||
defConfig["resourcesDirectory"] = "../Resources";
|
||||
#elif defined(__ANDROID__)
|
||||
defConfig["modulesDirectory"] = root + "/modules";
|
||||
defConfig["resourcesDirectory"] = root + "/res";
|
||||
#else
|
||||
defConfig["modulesDirectory"] = INSTALL_PREFIX "/lib/sdrpp/plugins";
|
||||
defConfig["resourcesDirectory"] = INSTALL_PREFIX "/share/sdrpp";
|
||||
#endif
|
||||
|
||||
// Load config
|
||||
spdlog::info("Loading config");
|
||||
core::configManager.setPath(options::opts.root + "/config.json");
|
||||
flog::info("Loading config");
|
||||
core::configManager.setPath(root + "/config.json");
|
||||
core::configManager.load(defConfig);
|
||||
core::configManager.enableAutoSave();
|
||||
|
||||
|
||||
core::configManager.acquire();
|
||||
|
||||
// Android can't load just any .so file. This means we have to hardcode the name of the modules
|
||||
#ifdef __ANDROID__
|
||||
int modCount = 0;
|
||||
core::configManager.conf["modules"] = json::array();
|
||||
|
||||
core::configManager.conf["modules"][modCount++] = "airspy_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "airspyhf_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "hackrf_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "hermes_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "plutosdr_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_tcp_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "sdrpp_server_source.so";
|
||||
core::configManager.conf["modules"][modCount++] = "spyserver_source.so";
|
||||
|
||||
core::configManager.conf["modules"][modCount++] = "network_sink.so";
|
||||
core::configManager.conf["modules"][modCount++] = "audio_sink.so";
|
||||
|
||||
core::configManager.conf["modules"][modCount++] = "m17_decoder.so";
|
||||
core::configManager.conf["modules"][modCount++] = "meteor_demodulator.so";
|
||||
core::configManager.conf["modules"][modCount++] = "radio.so";
|
||||
|
||||
core::configManager.conf["modules"][modCount++] = "frequency_manager.so";
|
||||
core::configManager.conf["modules"][modCount++] = "recorder.so";
|
||||
core::configManager.conf["modules"][modCount++] = "rigctl_server.so";
|
||||
core::configManager.conf["modules"][modCount++] = "scanner.so";
|
||||
#endif
|
||||
|
||||
// Fix missing elements in config
|
||||
for (auto const& item : defConfig.items()) {
|
||||
if (!core::configManager.conf.contains(item.key())) {
|
||||
spdlog::info("Missing key in config {0}, repairing", item.key());
|
||||
flog::info("Missing key in config {0}, repairing", item.key());
|
||||
core::configManager.conf[item.key()] = defConfig[item.key()];
|
||||
}
|
||||
}
|
||||
@@ -266,7 +318,7 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
auto items = core::configManager.conf.items();
|
||||
for (auto const& item : items) {
|
||||
if (!defConfig.contains(item.key())) {
|
||||
spdlog::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());
|
||||
}
|
||||
}
|
||||
@@ -281,242 +333,71 @@ int sdrpp_main(int argc, char *argv[]) {
|
||||
core::configManager.conf["moduleInstances"][_name] = newMod;
|
||||
}
|
||||
|
||||
// Load UI scaling
|
||||
style::uiScale = core::configManager.conf["uiScale"];
|
||||
|
||||
core::configManager.release(true);
|
||||
|
||||
if (options::opts.serverMode) { return server_main(); }
|
||||
if (serverMode) { return server::main(); }
|
||||
|
||||
core::configManager.acquire();
|
||||
int winWidth = core::configManager.conf["windowSize"]["w"];
|
||||
int winHeight = core::configManager.conf["windowSize"]["h"];
|
||||
maximized = core::configManager.conf["maximized"];
|
||||
std::string resDir = core::configManager.conf["resourcesDirectory"];
|
||||
json bandColors = core::configManager.conf["bandColors"];
|
||||
core::configManager.release();
|
||||
|
||||
// Assert that the resource directory is absolute and check existence
|
||||
resDir = std::filesystem::absolute(resDir).string();
|
||||
if (!std::filesystem::is_directory(resDir)) {
|
||||
spdlog::error("Resource directory doesn't exist! Please make sure that you've configured it correctly in config.json (check readme for details)");
|
||||
flog::error("Resource directory doesn't exist! Please make sure that you've configured it correctly in config.json (check readme for details)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize backend
|
||||
int biRes = backend::init(resDir);
|
||||
if (biRes < 0) { return biRes; }
|
||||
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
// GL 3.2 + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
|
||||
// Create window with graphics context
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
core::window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||
if (core::window == NULL)
|
||||
return 1;
|
||||
glfwMakeContextCurrent(core::window);
|
||||
#else
|
||||
const char* glsl_version = "#version 120";
|
||||
GLFWmonitor* monitor = NULL;
|
||||
for (int i = 0; i < OPENGL_VERSION_COUNT; i++) {
|
||||
glsl_version = OPENGL_VERSIONS_GLSL[i];
|
||||
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
|
||||
|
||||
// Create window with graphics context
|
||||
monitor = glfwGetPrimaryMonitor();
|
||||
core::window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
|
||||
if (core::window == NULL) {
|
||||
spdlog::info("OpenGL {0}.{1} {2}was not supported", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? "ES ": "");
|
||||
continue;
|
||||
}
|
||||
spdlog::info("Using OpenGL {0}.{1}{2}", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? " ES": "");
|
||||
glfwMakeContextCurrent(core::window);
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Add callback for max/min if GLFW supports it
|
||||
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
|
||||
if (maximized) {
|
||||
glfwMaximizeWindow(core::window);
|
||||
}
|
||||
|
||||
glfwSetWindowMaximizeCallback(core::window, maximized_callback);
|
||||
#endif
|
||||
|
||||
// Load app icon
|
||||
if (!std::filesystem::is_regular_file(resDir + "/icons/sdrpp.png")) {
|
||||
spdlog::error("Icon file '{0}' doesn't exist!", resDir + "/icons/sdrpp.png");
|
||||
return 1;
|
||||
}
|
||||
|
||||
GLFWimage icons[10];
|
||||
icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
|
||||
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16;
|
||||
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24;
|
||||
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32;
|
||||
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48;
|
||||
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64;
|
||||
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96;
|
||||
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128;
|
||||
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196;
|
||||
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256;
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
|
||||
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
|
||||
glfwSetWindowIcon(core::window, 10, icons);
|
||||
stbi_image_free(icons[0].pixels);
|
||||
for (int i = 1; i < 10; i++) {
|
||||
free(icons[i].pixels);
|
||||
}
|
||||
|
||||
if (glewInit() != GLEW_OK) {
|
||||
spdlog::error("Failed to initialize OpenGL loader!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
io.IniFilename = NULL;
|
||||
|
||||
// Setup Platform/Renderer bindings
|
||||
ImGui_ImplGlfw_InitForOpenGL(core::window, true);
|
||||
|
||||
if (!ImGui_ImplOpenGL3_Init(glsl_version)) {
|
||||
// If init fail, try to fall back on GLSL 1.2
|
||||
spdlog::warn("Could not init using OpenGL with normal GLSL version, falling back to GLSL 1.2");
|
||||
if (!ImGui_ImplOpenGL3_Init("#version 120")) {
|
||||
spdlog::error("Failed to initialize OpenGL with GLSL 1.2");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Initialize SmGui in normal mode
|
||||
SmGui::init(false);
|
||||
|
||||
if (!style::loadFonts(resDir)) { return -1; }
|
||||
thememenu::init(resDir);
|
||||
|
||||
LoadingScreen::setWindow(core::window);
|
||||
LoadingScreen::init();
|
||||
|
||||
LoadingScreen::show("Loading icons");
|
||||
spdlog::info("Loading icons");
|
||||
flog::info("Loading icons");
|
||||
if (!icons::load(resDir)) { return -1; }
|
||||
|
||||
LoadingScreen::show("Loading band plans");
|
||||
spdlog::info("Loading band plans");
|
||||
flog::info("Loading band plans");
|
||||
bandplan::loadFromDir(resDir + "/bandplans");
|
||||
|
||||
LoadingScreen::show("Loading band plan colors");
|
||||
spdlog::info("Loading band plans color table");
|
||||
flog::info("Loading band plans color table");
|
||||
bandplan::loadColorTable(bandColors);
|
||||
|
||||
gui::mainWindow.init();
|
||||
|
||||
spdlog::info("Ready.");
|
||||
flog::info("Ready.");
|
||||
|
||||
bool _maximized = maximized;
|
||||
int fsWidth, fsHeight, fsPosX, fsPosY;
|
||||
|
||||
// Main loop
|
||||
while (!glfwWindowShouldClose(core::window)) {
|
||||
glfwPollEvents();
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
//ImGui::ShowDemoWindow();
|
||||
|
||||
if (_maximized != maximized) {
|
||||
_maximized = maximized;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["maximized"]= _maximized;
|
||||
if (!maximized) {
|
||||
glfwSetWindowSize(core::window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
|
||||
}
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
int _winWidth, _winHeight;
|
||||
glfwGetWindowSize(core::window, &_winWidth, &_winHeight);
|
||||
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
|
||||
fullScreen = !fullScreen;
|
||||
if (fullScreen) {
|
||||
spdlog::info("Fullscreen: ON");
|
||||
fsWidth = _winWidth;
|
||||
fsHeight = _winHeight;
|
||||
glfwGetWindowPos(core::window, &fsPosX, &fsPosY);
|
||||
const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
glfwSetWindowMonitor(core::window, monitor, 0, 0, mode->width, mode->height, 0);
|
||||
}
|
||||
else {
|
||||
spdlog::info("Fullscreen: OFF");
|
||||
glfwSetWindowMonitor(core::window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
|
||||
winWidth = _winWidth;
|
||||
winHeight = _winHeight;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["windowSize"]["w"] = winWidth;
|
||||
core::configManager.conf["windowSize"]["h"] = winHeight;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (winWidth > 0 && winHeight > 0) {
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
|
||||
gui::mainWindow.draw();
|
||||
}
|
||||
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(core::window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
//glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f);
|
||||
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapInterval(1); // Enable vsync
|
||||
glfwSwapBuffers(core::window);
|
||||
}
|
||||
// Run render loop (TODO: CHECK RETURN VALUE)
|
||||
backend::renderLoop();
|
||||
|
||||
// On android, none of this shutdown should happen due to the way the UI works
|
||||
#ifndef __ANDROID__
|
||||
// Shut down all modules
|
||||
for (auto& [name, mod] : core::moduleManager.modules) {
|
||||
mod.end();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
// Terminate backend (TODO: CHECK RETURN VALUE)
|
||||
backend::end();
|
||||
|
||||
glfwDestroyWindow(core::window);
|
||||
glfwTerminate();
|
||||
|
||||
sigpath::signalPath.stop();
|
||||
sigpath::iqFrontEnd.stop();
|
||||
|
||||
core::configManager.disableAutoSave();
|
||||
core::configManager.save();
|
||||
#endif
|
||||
|
||||
flog::info("Exiting successfully");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#pragma once
|
||||
#include <config.h>
|
||||
#include <module.h>
|
||||
#include <module.h>
|
||||
#include <module_com.h>
|
||||
#include "command_args.h"
|
||||
|
||||
namespace core {
|
||||
SDRPP_EXPORT ConfigManager configManager;
|
||||
SDRPP_EXPORT ModuleManager moduleManager;
|
||||
SDRPP_EXPORT ModuleComManager modComManager;
|
||||
SDRPP_EXPORT CommandArgsParser args;
|
||||
|
||||
void setInputSampleRate(double samplerate);
|
||||
};
|
||||
|
||||
int sdrpp_main(int argc, char *argv[]);
|
||||
int sdrpp_main(int argc, char* argv[]);
|
||||
@@ -10,7 +10,9 @@ namespace sdrpp_credits {
|
||||
"Cropinghigh",
|
||||
"Fred F4EED",
|
||||
"Howard0su",
|
||||
"John Donkersley",
|
||||
"Joshua Kimsey",
|
||||
"Manawyrm",
|
||||
"Martin Hauke",
|
||||
"Marvin Sinister",
|
||||
"Maxime Biette",
|
||||
@@ -20,41 +22,80 @@ namespace sdrpp_credits {
|
||||
"Shuyuan Liu",
|
||||
"Syne Ardwin (WI9SYN)",
|
||||
"Szymon Zakrent",
|
||||
"Tobias Mädel",
|
||||
"Youssef Touil",
|
||||
"Zimm"
|
||||
};
|
||||
|
||||
const char* libraries[] = {
|
||||
"Dear ImGui (ocornut)",
|
||||
"fftw3 (fftw.org)",
|
||||
"glew (Nigel Stewart)",
|
||||
"glfw (Camilla Löwy)",
|
||||
"json (nlohmann)",
|
||||
"spdlog (gabime)",
|
||||
"Portable File Dialogs"
|
||||
};
|
||||
|
||||
const char* hardwareDonators[] = {
|
||||
"Aaronia AG",
|
||||
"Airspy",
|
||||
"Alex 4Z5LV",
|
||||
"Analog Devices",
|
||||
"CaribouLabs",
|
||||
"Deepace",
|
||||
"Ettus Research",
|
||||
"Harogic",
|
||||
"Howard Su",
|
||||
"MicroPhase",
|
||||
"Microtelecom",
|
||||
"MyriadRF",
|
||||
"Nuand",
|
||||
"RFNM",
|
||||
"RFspace",
|
||||
"RigExpert",
|
||||
"RTL-SDRblog",
|
||||
"SDRplay"
|
||||
};
|
||||
|
||||
const char* patrons[] = {
|
||||
"Bob Logan",
|
||||
"Christian Häusler",
|
||||
"Croccydile",
|
||||
"Dale L Puckett (K0HYD)",
|
||||
"Daniele D'Agnelli",
|
||||
"David Taylor (GM8ARV)",
|
||||
"D. Jones",
|
||||
"Dexruus",
|
||||
"EB3FRN",
|
||||
"Eric Johnson",
|
||||
"Ernest Murphy (NH7L)",
|
||||
"Flinger Films",
|
||||
"Frank Werner (HB9FXQ)",
|
||||
"gringogrigio",
|
||||
"Jeff Moe",
|
||||
"Joe Cupano",
|
||||
"KD1SQ",
|
||||
"Kezza",
|
||||
"Krys Kamieniecki",
|
||||
"Lee Donaghy",
|
||||
"Lee KD1SQ",
|
||||
".lozenge. (Hank Hill)",
|
||||
"Martin Herren (HB9FXX)",
|
||||
"ON4MU",
|
||||
"Passion-Radio.com",
|
||||
"Paul Maine",
|
||||
"Peter Betz",
|
||||
"Scanner School",
|
||||
"Scott Palmer",
|
||||
"SignalsEverywhere",
|
||||
"Syne Ardwin (WI9SYN)",
|
||||
"W4IPA"
|
||||
"W4IPA",
|
||||
"William Arcand (W1WRA)",
|
||||
"William Pitchford",
|
||||
"Yves Rougy",
|
||||
"Zipper"
|
||||
};
|
||||
|
||||
const int contributorCount = sizeof(contributors) / sizeof(char*);
|
||||
const int libraryCount = sizeof(libraries) / sizeof(char*);
|
||||
const int hardwareDonatorCount = sizeof(hardwareDonators) / sizeof(char*);
|
||||
const int patronCount = sizeof(patrons) / sizeof(char*);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
namespace sdrpp_credits {
|
||||
SDRPP_EXPORT const char* contributors[];
|
||||
SDRPP_EXPORT const char* libraries[];
|
||||
SDRPP_EXPORT const char* hardwareDonators[];
|
||||
SDRPP_EXPORT const char* patrons[];
|
||||
SDRPP_EXPORT const int contributorCount;
|
||||
SDRPP_EXPORT const int libraryCount;
|
||||
SDRPP_EXPORT const int hardwareDonatorCount;
|
||||
SDRPP_EXPORT const int patronCount;
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
|
||||
namespace dsp {
|
||||
class MonoToStereo : public generic_block<MonoToStereo> {
|
||||
public:
|
||||
MonoToStereo() {}
|
||||
|
||||
MonoToStereo(stream<float>* in) { init(in); }
|
||||
|
||||
void init(stream<float>* in) {
|
||||
_in = in;
|
||||
generic_block<MonoToStereo>::registerInput(_in);
|
||||
generic_block<MonoToStereo>::registerOutput(&out);
|
||||
generic_block<MonoToStereo>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<MonoToStereo>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<MonoToStereo>::ctrlMtx);
|
||||
generic_block<MonoToStereo>::tempStop();
|
||||
generic_block<MonoToStereo>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<MonoToStereo>::registerInput(_in);
|
||||
generic_block<MonoToStereo>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, _in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ChannelsToStereo : public generic_block<ChannelsToStereo> {
|
||||
public:
|
||||
ChannelsToStereo() {}
|
||||
|
||||
ChannelsToStereo(stream<float>* in_left, stream<float>* in_right) { init(in_left, in_right); }
|
||||
|
||||
void init(stream<float>* in_left, stream<float>* in_right) {
|
||||
_in_left = in_left;
|
||||
_in_right = in_right;
|
||||
nullbuf = new float[STREAM_BUFFER_SIZE];
|
||||
for (int i = 0; i < STREAM_BUFFER_SIZE; i++) { nullbuf[i] = 0; }
|
||||
generic_block<ChannelsToStereo>::registerInput(_in_left);
|
||||
generic_block<ChannelsToStereo>::registerInput(_in_right);
|
||||
generic_block<ChannelsToStereo>::registerOutput(&out);
|
||||
generic_block<ChannelsToStereo>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in_left, stream<float>* in_right) {
|
||||
assert(generic_block<ChannelsToStereo>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ChannelsToStereo>::ctrlMtx);
|
||||
generic_block<ChannelsToStereo>::tempStop();
|
||||
generic_block<ChannelsToStereo>::unregisterInput(_in_left);
|
||||
generic_block<ChannelsToStereo>::unregisterInput(_in_right);
|
||||
_in_left = in_left;
|
||||
_in_right = in_right;
|
||||
generic_block<ChannelsToStereo>::registerInput(_in_left);
|
||||
generic_block<ChannelsToStereo>::registerInput(_in_right);
|
||||
generic_block<ChannelsToStereo>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count_l = _in_left->read();
|
||||
if (count_l < 0) { return -1; }
|
||||
int count_r = _in_right->read();
|
||||
if (count_r < 0) { return -1; }
|
||||
|
||||
if (count_l != count_r) {
|
||||
spdlog::warn("ChannelsToStereo block size mismatch");
|
||||
}
|
||||
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in_left->readBuf, _in_right->readBuf, count_l);
|
||||
|
||||
_in_left->flush();
|
||||
_in_right->flush();
|
||||
if (!out.swap(count_l)) { return -1; }
|
||||
return count_l;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
stream<float>* _in_left;
|
||||
stream<float>* _in_right;
|
||||
|
||||
float* nullbuf;
|
||||
|
||||
};
|
||||
|
||||
class StereoToMono : public generic_block<StereoToMono> {
|
||||
public:
|
||||
StereoToMono() {}
|
||||
|
||||
StereoToMono(stream<stereo_t>* in) { init(in); }
|
||||
|
||||
~StereoToMono() {
|
||||
if (!generic_block<StereoToMono>::_block_init) { return; }
|
||||
generic_block<StereoToMono>::stop();
|
||||
delete[] l_buf;
|
||||
delete[] r_buf;
|
||||
generic_block<StereoToMono>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<stereo_t>* in) {
|
||||
_in = in;
|
||||
l_buf = new float[STREAM_BUFFER_SIZE];
|
||||
r_buf = new float[STREAM_BUFFER_SIZE];
|
||||
generic_block<StereoToMono>::registerInput(_in);
|
||||
generic_block<StereoToMono>::registerOutput(&out);
|
||||
generic_block<StereoToMono>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<stereo_t>* in) {
|
||||
assert(generic_block<StereoToMono>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<StereoToMono>::ctrlMtx);
|
||||
generic_block<StereoToMono>::tempStop();
|
||||
generic_block<StereoToMono>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<StereoToMono>::registerInput(_in);
|
||||
generic_block<StereoToMono>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] = (_in->readBuf[i].l + _in->readBuf[i].r) * 0.5f;
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
float* l_buf, *r_buf;
|
||||
stream<stereo_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class StereoToChannels : public generic_block<StereoToChannels> {
|
||||
public:
|
||||
StereoToChannels() {}
|
||||
|
||||
StereoToChannels(stream<stereo_t>* in) { init(in); }
|
||||
|
||||
void init(stream<stereo_t>* in) {
|
||||
_in = in;
|
||||
generic_block<StereoToChannels>::registerInput(_in);
|
||||
generic_block<StereoToChannels>::registerOutput(&out_left);
|
||||
generic_block<StereoToChannels>::registerOutput(&out_right);
|
||||
generic_block<StereoToChannels>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<stereo_t>* in) {
|
||||
assert(generic_block<StereoToChannels>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<StereoToChannels>::ctrlMtx);
|
||||
generic_block<StereoToChannels>::tempStop();
|
||||
generic_block<StereoToChannels>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<StereoToChannels>::registerInput(_in);
|
||||
generic_block<StereoToChannels>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_deinterleave_32f_x2(out_left.writeBuf, out_right.writeBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out_left.swap(count)) { return -1; }
|
||||
if (!out_right.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out_left;
|
||||
stream<float> out_right;
|
||||
|
||||
private:
|
||||
stream<stereo_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
58
core/src/dsp/audio/volume.h
Normal file
58
core/src/dsp/audio/volume.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
// TODO: This block is useless and weird, get rid of it
|
||||
namespace dsp::audio {
|
||||
class Volume : public Processor<stereo_t, stereo_t> {
|
||||
using base_type = Processor<stereo_t, stereo_t>;
|
||||
public:
|
||||
Volume() {}
|
||||
|
||||
Volume(stream<stereo_t>* in, double volume, bool muted) { init(in, volume, muted); }
|
||||
|
||||
void init(stream<stereo_t>* in, double volume, bool muted) {
|
||||
_volume = powf(volume, 2);
|
||||
_muted = muted;
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setVolume(double volume) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_volume = powf(volume, 2);
|
||||
}
|
||||
|
||||
void setMuted(bool muted) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_muted = muted;
|
||||
}
|
||||
|
||||
bool getMuted() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
return _muted;
|
||||
}
|
||||
|
||||
inline int process(int count, const stereo_t* in, stereo_t* out) {
|
||||
volk_32f_s32f_multiply_32f((float*)out, (float*)in, _muted ? 0.0f : _volume, count * 2);
|
||||
return count;
|
||||
}
|
||||
|
||||
virtual 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:
|
||||
float _volume;
|
||||
bool _muted;
|
||||
|
||||
};
|
||||
}
|
||||
72
core/src/dsp/bench/peak_level_meter.h
Normal file
72
core/src/dsp/bench/peak_level_meter.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include "../sink.h"
|
||||
|
||||
namespace dsp::bench {
|
||||
template<class T>
|
||||
class PeakLevelMeter : public Sink<T> {
|
||||
using base_type = Sink<T>;
|
||||
public:
|
||||
PeakLevelMeter() {}
|
||||
|
||||
PeakLevelMeter(stream<T>* in) { init(in); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
level = 0.0f;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
level = { 0.0f, 0.0f };
|
||||
}
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
T getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
void resetLevel() {
|
||||
assert(base_type::_block_init);
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
level = 0.0f;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
level = { 0.0f, 0.0f };
|
||||
}
|
||||
}
|
||||
|
||||
int process(int count, T* in) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
float lvl = fabsf(in[i]);
|
||||
if (lvl > level) { level = lvl; }
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
float lvlre = fabsf(in[i].re);
|
||||
float lvlim = fabsf(in[i].im);
|
||||
if (lvlre > level.re) { level.re = lvlre; }
|
||||
if (lvlim > level.im) { level.im = lvlim; }
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
float lvll = fabsf(in[i].l);
|
||||
float lvlr = fabsf(in[i].r);
|
||||
if (lvll > level.l) { level.l = lvll; }
|
||||
if (lvlr > level.r) { level.r = lvlr; }
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
process(count, base_type::_in->readBuf);
|
||||
|
||||
base_type::_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
T level;
|
||||
};
|
||||
}
|
||||
105
core/src/dsp/bench/speed_tester.h
Normal file
105
core/src/dsp/bench/speed_tester.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <assert.h>
|
||||
#include "../stream.h"
|
||||
#include "../types.h"
|
||||
|
||||
namespace dsp::bench {
|
||||
template<class I, class O>
|
||||
class SpeedTester {
|
||||
public:
|
||||
SpeedTester() {}
|
||||
|
||||
SpeedTester(stream<I>* in, stream<O>* out) { init(in, out); }
|
||||
|
||||
void init(stream<I>* in, stream<O>* out) {
|
||||
_in = in;
|
||||
_out = out;
|
||||
_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<I>* in) {
|
||||
assert(_init);
|
||||
_in = in;
|
||||
}
|
||||
|
||||
void setOutput(stream<O>* out) {
|
||||
assert(_init);
|
||||
_out = out;
|
||||
}
|
||||
|
||||
double benchmark(int durationMs, int bufferSize) {
|
||||
assert(_init);
|
||||
|
||||
// Allocate and fill buffer
|
||||
inCount = bufferSize;
|
||||
randBuf = buffer::alloc<I>(inCount);
|
||||
for (int i = 0; i < inCount; i++) {
|
||||
if constexpr (std::is_same_v<I, complex_t>) {
|
||||
randBuf[i].re = (2.0f * (float)rand() / (float)RAND_MAX) - 1.0f;
|
||||
randBuf[i].im = (2.0f * (float)rand() / (float)RAND_MAX) - 1.0f;
|
||||
}
|
||||
else if constexpr (std::is_same_v<I, float>) {
|
||||
randBuf[i] = (2.0f * (float)rand() / (float)RAND_MAX) - 1.0f;
|
||||
}
|
||||
else {
|
||||
randBuf[i] = rand();
|
||||
}
|
||||
}
|
||||
|
||||
// Run test
|
||||
start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(durationMs));
|
||||
stop();
|
||||
buffer::free(randBuf);
|
||||
return (double)sampCount * 1000.0 / (double)durationMs;
|
||||
}
|
||||
|
||||
protected:
|
||||
void start() {
|
||||
if (running) { return; }
|
||||
running = true;
|
||||
sampCount = 0;
|
||||
wthr = std::thread(&SpeedTester::writeWorker, this);
|
||||
rthr = std::thread(&SpeedTester::readWorker, this);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running) { return; }
|
||||
running = false;
|
||||
_in->stopWriter();
|
||||
_out->stopReader();
|
||||
if (wthr.joinable()) { wthr.join(); }
|
||||
if (rthr.joinable()) { rthr.join(); }
|
||||
_in->clearWriteStop();
|
||||
_out->clearReadStop();
|
||||
}
|
||||
|
||||
void writeWorker() {
|
||||
while (true) {
|
||||
memcpy(_in->writeBuf, randBuf, inCount * sizeof(I));
|
||||
if (!_in->swap(inCount)) { return; }
|
||||
sampCount += inCount;
|
||||
}
|
||||
}
|
||||
|
||||
void readWorker() {
|
||||
while (true) {
|
||||
int count = _out->read();
|
||||
_out->flush();
|
||||
if (count < 0) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
bool _init = false;
|
||||
bool running = false;
|
||||
int inCount;
|
||||
stream<I>* _in;
|
||||
stream<O>* _out;
|
||||
I* randBuf;
|
||||
std::thread wthr;
|
||||
std::thread rthr;
|
||||
uint64_t sampCount;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,29 +1,23 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <assert.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "stream.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace dsp {
|
||||
|
||||
class generic_unnamed_block {
|
||||
class generic_block {
|
||||
public:
|
||||
virtual ~generic_block() {}
|
||||
virtual void start() {}
|
||||
virtual void stop() {}
|
||||
virtual int calcOutSize(int inSize) { return inSize; }
|
||||
virtual int run() { return -1; }
|
||||
};
|
||||
|
||||
template <class BLOCK>
|
||||
class generic_block : public generic_unnamed_block {
|
||||
public:
|
||||
virtual void init() {}
|
||||
|
||||
virtual ~generic_block() {
|
||||
class block : public generic_block {
|
||||
public:
|
||||
virtual ~block() {
|
||||
if (!_block_init) { return; }
|
||||
stop();
|
||||
_block_init = false;
|
||||
@@ -31,7 +25,7 @@ namespace dsp {
|
||||
|
||||
virtual void start() {
|
||||
assert(_block_init);
|
||||
std::lock_guard<std::mutex> lck(ctrlMtx);
|
||||
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
@@ -41,7 +35,7 @@ namespace dsp {
|
||||
|
||||
virtual void stop() {
|
||||
assert(_block_init);
|
||||
std::lock_guard<std::mutex> lck(ctrlMtx);
|
||||
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
@@ -51,6 +45,7 @@ namespace dsp {
|
||||
|
||||
void tempStart() {
|
||||
assert(_block_init);
|
||||
if (!tempStopDepth || --tempStopDepth) { return; }
|
||||
if (tempStopped) {
|
||||
doStart();
|
||||
tempStopped = false;
|
||||
@@ -59,26 +54,45 @@ namespace dsp {
|
||||
|
||||
void tempStop() {
|
||||
assert(_block_init);
|
||||
if (tempStopDepth++) { return; }
|
||||
if (running && !tempStopped) {
|
||||
doStop();
|
||||
tempStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
virtual int calcOutSize(int inSize) {
|
||||
assert(_block_init);
|
||||
return inSize;
|
||||
}
|
||||
|
||||
virtual int run() = 0;
|
||||
|
||||
friend BLOCK;
|
||||
|
||||
private:
|
||||
void workerLoop() {
|
||||
while (run() >= 0);
|
||||
protected:
|
||||
void workerLoop() {
|
||||
while (run() >= 0) {}
|
||||
}
|
||||
|
||||
virtual void doStart() {
|
||||
workerThread = std::thread(&block::workerLoop, this);
|
||||
}
|
||||
|
||||
virtual void doStop() {
|
||||
for (auto& in : inputs) {
|
||||
in->stopReader();
|
||||
}
|
||||
for (auto& out : outputs) {
|
||||
out->stopWriter();
|
||||
}
|
||||
|
||||
// TODO: Make sure this isn't needed, I don't know why it stops
|
||||
if (workerThread.joinable()) {
|
||||
workerThread.join();
|
||||
}
|
||||
|
||||
for (auto& in : inputs) {
|
||||
in->clearReadStop();
|
||||
}
|
||||
for (auto& out : outputs) {
|
||||
out->clearWriteStop();
|
||||
}
|
||||
}
|
||||
|
||||
void acquire() {
|
||||
ctrlMtx.lock();
|
||||
}
|
||||
@@ -103,127 +117,16 @@ namespace dsp {
|
||||
outputs.erase(std::remove(outputs.begin(), outputs.end(), outStream), outputs.end());
|
||||
}
|
||||
|
||||
virtual void doStart() {
|
||||
workerThread = std::thread(&generic_block<BLOCK>::workerLoop, this);
|
||||
}
|
||||
|
||||
virtual void doStop() {
|
||||
for (auto& in : inputs) {
|
||||
in->stopReader();
|
||||
}
|
||||
for (auto& out : outputs) {
|
||||
out->stopWriter();
|
||||
}
|
||||
|
||||
// TODO: Make sure this isn't needed, I don't know why it stops
|
||||
if (workerThread.joinable()) {
|
||||
workerThread.join();
|
||||
}
|
||||
|
||||
for (auto& in : inputs) {
|
||||
in->clearReadStop();
|
||||
}
|
||||
for (auto& out : outputs) {
|
||||
out->clearWriteStop();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool _block_init = false;
|
||||
|
||||
std::mutex ctrlMtx;
|
||||
std::recursive_mutex ctrlMtx;
|
||||
|
||||
std::vector<untyped_stream*> inputs;
|
||||
std::vector<untyped_stream*> outputs;
|
||||
|
||||
bool running = false;
|
||||
bool tempStopped = false;
|
||||
int tempStopDepth = 0;
|
||||
std::thread workerThread;
|
||||
|
||||
};
|
||||
|
||||
template <class BLOCK>
|
||||
class generic_hier_block {
|
||||
public:
|
||||
virtual void init() {}
|
||||
|
||||
virtual ~generic_hier_block() {
|
||||
if (!_block_init) { return; }
|
||||
stop();
|
||||
_block_init = false;
|
||||
}
|
||||
|
||||
virtual void start() {
|
||||
assert(_block_init);
|
||||
std::lock_guard<std::mutex> lck(ctrlMtx);
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
doStart();
|
||||
}
|
||||
|
||||
virtual void stop() {
|
||||
assert(_block_init);
|
||||
std::lock_guard<std::mutex> lck(ctrlMtx);
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
doStop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void tempStart() {
|
||||
assert(_block_init);
|
||||
if (tempStopped) {
|
||||
doStart();
|
||||
tempStopped = false;
|
||||
}
|
||||
}
|
||||
|
||||
void tempStop() {
|
||||
assert(_block_init);
|
||||
if (running && !tempStopped) {
|
||||
doStop();
|
||||
tempStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
virtual int calcOutSize(int inSize) {
|
||||
assert(_block_init);
|
||||
return inSize;
|
||||
}
|
||||
|
||||
friend BLOCK;
|
||||
|
||||
private:
|
||||
void registerBlock(generic_unnamed_block* block) {
|
||||
blocks.push_back(block);
|
||||
}
|
||||
|
||||
void unregisterBlock(generic_unnamed_block* block) {
|
||||
blocks.erase(std::remove(blocks.begin(), blocks.end(), block), blocks.end());
|
||||
}
|
||||
|
||||
virtual void doStart() {
|
||||
for (auto& block : blocks) {
|
||||
block->start();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void doStop() {
|
||||
for (auto& block : blocks) {
|
||||
block->stop();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<generic_unnamed_block*> blocks;
|
||||
bool tempStopped = false;
|
||||
bool running = false;
|
||||
|
||||
protected:
|
||||
bool _block_init = false;
|
||||
std::mutex ctrlMtx;
|
||||
|
||||
};
|
||||
}
|
||||
19
core/src/dsp/buffer/buffer.h
Normal file
19
core/src/dsp/buffer/buffer.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <volk/volk.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp::buffer {
|
||||
template<class T>
|
||||
inline T* alloc(int count) {
|
||||
return (T*)volk_malloc(count * sizeof(T), volk_get_alignment());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void clear(T* buffer, int count, int offset = 0) {
|
||||
memset(&buffer[offset], 0, count * sizeof(T));
|
||||
}
|
||||
|
||||
inline void free(void* buffer) {
|
||||
volk_free(buffer);
|
||||
}
|
||||
}
|
||||
132
core/src/dsp/buffer/frame_buffer.h
Normal file
132
core/src/dsp/buffer/frame_buffer.h
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
#include "../block.h"
|
||||
#define TEST_BUFFER_SIZE 32
|
||||
|
||||
// IMPORTANT: THIS IS TRASH AND MUST BE REWRITTEN IN THE FUTURE
|
||||
|
||||
namespace dsp::buffer {
|
||||
template <class T>
|
||||
class SampleFrameBuffer : public block {
|
||||
using base_type = block;
|
||||
public:
|
||||
SampleFrameBuffer() {}
|
||||
|
||||
SampleFrameBuffer(stream<T>* in) { init(in); }
|
||||
|
||||
~SampleFrameBuffer() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
for (int i = 0; i < TEST_BUFFER_SIZE; i++) {
|
||||
buffer::free(buffers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
|
||||
for (int i = 0; i < TEST_BUFFER_SIZE; i++) {
|
||||
buffers[i] = buffer::alloc<T>(STREAM_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
base_type::registerInput(in);
|
||||
base_type::registerOutput(&out);
|
||||
base_type::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
base_type::unregisterInput(_in);
|
||||
_in = in;
|
||||
base_type::registerInput(_in);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void flush() {
|
||||
std::unique_lock lck(bufMtx);
|
||||
readCur = writeCur;
|
||||
}
|
||||
|
||||
int run() {
|
||||
// Wait for data
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (bypass) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(T));
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
// Push it on the ring buffer
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(bufMtx);
|
||||
memcpy(buffers[writeCur], _in->readBuf, count * sizeof(T));
|
||||
sizes[writeCur] = count;
|
||||
writeCur++;
|
||||
writeCur = ((writeCur) % TEST_BUFFER_SIZE);
|
||||
}
|
||||
cnd.notify_all();
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
void worker() {
|
||||
while (true) {
|
||||
// Wait for data
|
||||
std::unique_lock lck(bufMtx);
|
||||
cnd.wait(lck, [this]() { return (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) > 0) || stopWorker; });
|
||||
if (stopWorker) { break; }
|
||||
|
||||
// Write one to output buffer and unlock in preparation to swap buffers
|
||||
int count = sizes[readCur];
|
||||
memcpy(out.writeBuf, buffers[readCur], count * sizeof(T));
|
||||
readCur++;
|
||||
readCur = ((readCur) % TEST_BUFFER_SIZE);
|
||||
lck.unlock();
|
||||
|
||||
// Swap
|
||||
if (!out.swap(count)) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
int writeCur = 0;
|
||||
int readCur = 0;
|
||||
|
||||
bool bypass = false;
|
||||
|
||||
private:
|
||||
void doStart() {
|
||||
base_type::workerThread = std::thread(&SampleFrameBuffer<T>::workerLoop, this);
|
||||
readWorkerThread = std::thread(&SampleFrameBuffer<T>::worker, this);
|
||||
}
|
||||
|
||||
void doStop() {
|
||||
_in->stopReader();
|
||||
out.stopWriter();
|
||||
stopWorker = true;
|
||||
cnd.notify_all();
|
||||
|
||||
if (base_type::workerThread.joinable()) { base_type::workerThread.join(); }
|
||||
if (readWorkerThread.joinable()) { readWorkerThread.join(); }
|
||||
|
||||
_in->clearReadStop();
|
||||
out.clearWriteStop();
|
||||
stopWorker = false;
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
|
||||
std::thread readWorkerThread;
|
||||
std::mutex bufMtx;
|
||||
std::condition_variable cnd;
|
||||
T* buffers[TEST_BUFFER_SIZE];
|
||||
int sizes[TEST_BUFFER_SIZE];
|
||||
|
||||
bool stopWorker = false;
|
||||
};
|
||||
}
|
||||
69
core/src/dsp/buffer/packer.h
Normal file
69
core/src/dsp/buffer/packer.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "../block.h"
|
||||
|
||||
|
||||
namespace dsp::buffer {
|
||||
template <class T>
|
||||
class Packer : public block {
|
||||
public:
|
||||
Packer() {}
|
||||
|
||||
Packer(stream<T>* in, int count) { init(in, count); }
|
||||
|
||||
void init(stream<T>* in, int count) {
|
||||
_in = in;
|
||||
samples = count;
|
||||
block::registerInput(_in);
|
||||
block::registerOutput(&out);
|
||||
block::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(block::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(block::ctrlMtx);
|
||||
block::tempStop();
|
||||
block::unregisterInput(_in);
|
||||
_in = in;
|
||||
block::registerInput(_in);
|
||||
block::tempStart();
|
||||
}
|
||||
|
||||
void setSampleCount(int count) {
|
||||
assert(block::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(block::ctrlMtx);
|
||||
block::tempStop();
|
||||
samples = count;
|
||||
block::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) {
|
||||
read = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[read++] = _in->readBuf[i];
|
||||
if (read >= samples) {
|
||||
read = 0;
|
||||
if (!out.swap(samples)) {
|
||||
_in->flush();
|
||||
read = 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
int samples = 1;
|
||||
int read = 0;
|
||||
stream<T>* _in;
|
||||
};
|
||||
}
|
||||
137
core/src/dsp/buffer/reshaper.h
Normal file
137
core/src/dsp/buffer/reshaper.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
#include "../block.h"
|
||||
#include "ring_buffer.h"
|
||||
|
||||
// IMPORTANT: THIS IS TRASH AND MUST BE REWRITTEN IN THE FUTURE
|
||||
|
||||
namespace dsp::buffer {
|
||||
// NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works...
|
||||
template <class T>
|
||||
class Reshaper : public block {
|
||||
using base_type = block;
|
||||
public:
|
||||
Reshaper() {}
|
||||
|
||||
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
|
||||
|
||||
// NOTE: For some reason, the base class destructor doesn't get called.... this is a temporary fix I guess
|
||||
// I also don't check for _block_init for the exact sample reason, something's weird
|
||||
~Reshaper() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
}
|
||||
|
||||
void init(stream<T>* in, int keep, int skip) {
|
||||
_in = in;
|
||||
_keep = keep;
|
||||
_skip = skip;
|
||||
ringBuf.init(keep * 2);
|
||||
base_type::registerInput(_in);
|
||||
base_type::registerOutput(&out);
|
||||
base_type::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
base_type::unregisterInput(_in);
|
||||
_in = in;
|
||||
base_type::registerInput(_in);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setKeep(int keep) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_keep = keep;
|
||||
ringBuf.setMaxLatency(keep * 2);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setSkip(int skip) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_skip = skip;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
ringBuf.write(_in->readBuf, count);
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
void doStart() override {
|
||||
workThread = std::thread(&Reshaper<T>::loop, this);
|
||||
bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while (run() >= 0)
|
||||
;
|
||||
}
|
||||
|
||||
void doStop() override {
|
||||
_in->stopReader();
|
||||
ringBuf.stopReader();
|
||||
out.stopWriter();
|
||||
ringBuf.stopWriter();
|
||||
|
||||
if (workThread.joinable()) {
|
||||
workThread.join();
|
||||
}
|
||||
if (bufferWorkerThread.joinable()) {
|
||||
bufferWorkerThread.join();
|
||||
}
|
||||
|
||||
_in->clearReadStop();
|
||||
ringBuf.clearReadStop();
|
||||
out.clearWriteStop();
|
||||
ringBuf.clearWriteStop();
|
||||
}
|
||||
|
||||
void bufferWorker() {
|
||||
T* buf = new T[_keep];
|
||||
bool delay = _skip < 0;
|
||||
|
||||
int readCount = std::min<int>(_keep + _skip, _keep);
|
||||
int skip = std::max<int>(_skip, 0);
|
||||
int delaySize = (-_skip) * sizeof(T);
|
||||
int delayCount = (-_skip);
|
||||
|
||||
T* start = &buf[std::max<int>(-_skip, 0)];
|
||||
T* delayStart = &buf[_keep + _skip];
|
||||
|
||||
while (true) {
|
||||
if (delay) {
|
||||
memmove(buf, delayStart, delaySize);
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
for (int i = 0; i < delayCount; i++) {
|
||||
buf[i].re /= 10.0f;
|
||||
buf[i].im /= 10.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ringBuf.readAndSkip(start, readCount, skip) < 0) { break; };
|
||||
memcpy(out.writeBuf, buf, _keep * sizeof(T));
|
||||
if (!out.swap(_keep)) { break; }
|
||||
}
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
int _outBlockSize;
|
||||
RingBuffer<T> ringBuf;
|
||||
std::thread bufferWorkerThread;
|
||||
std::thread workThread;
|
||||
int _keep, _skip;
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <string.h>
|
||||
#include "buffer.h"
|
||||
|
||||
#define RING_BUF_SZ 1000000
|
||||
|
||||
namespace dsp {
|
||||
// IMPORTANT: THIS IS TRASH AND MUST BE REWRITTEN IN THE FUTURE
|
||||
|
||||
namespace dsp::buffer {
|
||||
template <class T>
|
||||
class RingBuffer {
|
||||
public:
|
||||
@@ -14,13 +15,12 @@ namespace dsp {
|
||||
|
||||
~RingBuffer() {
|
||||
if (!_init) { return; }
|
||||
delete _buffer;
|
||||
buffer::free(_buffer);
|
||||
_init = false;
|
||||
}
|
||||
|
||||
void init(int maxLatency) {
|
||||
size = RING_BUF_SZ;
|
||||
_buffer = new T[size];
|
||||
_stopReader = false;
|
||||
_stopWriter = false;
|
||||
this->maxLatency = maxLatency;
|
||||
@@ -28,7 +28,8 @@ namespace dsp {
|
||||
readc = 0;
|
||||
readable = 0;
|
||||
writable = size;
|
||||
memset(_buffer, 0, size * sizeof(T));
|
||||
_buffer = buffer::alloc<T>(size);
|
||||
buffer::clear(_buffer, size);
|
||||
_init = true;
|
||||
}
|
||||
|
||||
@@ -47,7 +48,7 @@ namespace dsp {
|
||||
else {
|
||||
memcpy(&data[dataRead], &_buffer[readc], toRead * sizeof(T));
|
||||
}
|
||||
|
||||
|
||||
dataRead += toRead;
|
||||
|
||||
_readable_mtx.lock();
|
||||
@@ -114,7 +115,7 @@ namespace dsp {
|
||||
int _r = getReadable();
|
||||
if (_r != 0) { return _r; }
|
||||
std::unique_lock<std::mutex> lck(_readable_mtx);
|
||||
canReadVar.wait(lck, [=](){ return ((this->getReadable(false) > 0) || this->getReadStop()); });
|
||||
canReadVar.wait(lck, [=]() { return ((this->getReadable(false) > 0) || this->getReadStop()); });
|
||||
if (_stopReader) { return -1; }
|
||||
return getReadable(false);
|
||||
}
|
||||
@@ -152,7 +153,7 @@ namespace dsp {
|
||||
writable -= toWrite;
|
||||
_writable_mtx.unlock();
|
||||
writec = (writec + toWrite) % size;
|
||||
|
||||
|
||||
canReadVar.notify_one();
|
||||
}
|
||||
return len;
|
||||
@@ -164,7 +165,7 @@ namespace dsp {
|
||||
int _w = getWritable();
|
||||
if (_w != 0) { return _w; }
|
||||
std::unique_lock<std::mutex> lck(_writable_mtx);
|
||||
canWriteVar.wait(lck, [=](){ return ((this->getWritable(false) > 0) || this->getWriteStop()); });
|
||||
canWriteVar.wait(lck, [=]() { return ((this->getWritable(false) > 0) || this->getWriteStop()); });
|
||||
if (_stopWriter) { return -1; }
|
||||
return getWritable(false);
|
||||
}
|
||||
@@ -173,7 +174,10 @@ namespace dsp {
|
||||
assert(_init);
|
||||
if (lock) { _writable_mtx.lock(); };
|
||||
int _w = writable;
|
||||
if (lock) { _writable_mtx.unlock(); _readable_mtx.lock(); };
|
||||
if (lock) {
|
||||
_writable_mtx.unlock();
|
||||
_readable_mtx.lock();
|
||||
};
|
||||
int _r = readable;
|
||||
if (lock) { _readable_mtx.unlock(); };
|
||||
return std::max<int>(std::min<int>(_w, maxLatency - _r), 0);
|
||||
@@ -232,128 +236,4 @@ namespace dsp {
|
||||
std::condition_variable canReadVar;
|
||||
std::condition_variable canWriteVar;
|
||||
};
|
||||
|
||||
#define TEST_BUFFER_SIZE 32
|
||||
|
||||
template <class T>
|
||||
class SampleFrameBuffer : public generic_block<SampleFrameBuffer<T>> {
|
||||
public:
|
||||
SampleFrameBuffer() {}
|
||||
|
||||
SampleFrameBuffer(stream<T>* in) { init(in); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
|
||||
for (int i = 0; i < TEST_BUFFER_SIZE; i++) {
|
||||
buffers[i] = new T[STREAM_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
generic_block<SampleFrameBuffer<T>>::registerInput(in);
|
||||
generic_block<SampleFrameBuffer<T>>::registerOutput(&out);
|
||||
generic_block<SampleFrameBuffer<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<SampleFrameBuffer<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<SampleFrameBuffer<T>>::ctrlMtx);
|
||||
generic_block<SampleFrameBuffer<T>>::tempStop();
|
||||
generic_block<SampleFrameBuffer<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<SampleFrameBuffer<T>>::registerInput(_in);
|
||||
generic_block<SampleFrameBuffer<T>>::tempStart();
|
||||
}
|
||||
|
||||
void flush() {
|
||||
std::unique_lock lck(bufMtx);
|
||||
readCur = writeCur;
|
||||
}
|
||||
|
||||
int run() {
|
||||
// Wait for data
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (bypass) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(T));
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
// Push it on the ring buffer
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(bufMtx);
|
||||
memcpy(buffers[writeCur], _in->readBuf, count * sizeof(T));
|
||||
uintptr_t ptr = (uintptr_t)buffers[writeCur];
|
||||
sizes[writeCur] = count;
|
||||
writeCur++;
|
||||
writeCur = ((writeCur) % TEST_BUFFER_SIZE);
|
||||
|
||||
// if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) {
|
||||
// spdlog::warn("Overflow");
|
||||
// }
|
||||
}
|
||||
cnd.notify_all();
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
void worker() {
|
||||
while (true) {
|
||||
// Wait for data
|
||||
std::unique_lock lck(bufMtx);
|
||||
cnd.wait(lck, [this](){ return (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) > 0) || stopWorker; });
|
||||
if (stopWorker) { break; }
|
||||
|
||||
// Write one to output buffer and unlock in preparation to swap buffers
|
||||
int count = sizes[readCur];
|
||||
memcpy(out.writeBuf, buffers[readCur], count * sizeof(T));
|
||||
readCur++;
|
||||
readCur = ((readCur) % TEST_BUFFER_SIZE);
|
||||
lck.unlock();
|
||||
|
||||
// Swap
|
||||
if (!out.swap(count)) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
int writeCur = 0;
|
||||
int readCur = 0;
|
||||
|
||||
bool bypass = false;
|
||||
|
||||
private:
|
||||
void doStart() {
|
||||
generic_block<SampleFrameBuffer<T>>::workerThread = std::thread(&generic_block<SampleFrameBuffer<T>>::workerLoop, this);
|
||||
readWorkerThread = std::thread(&SampleFrameBuffer<T>::worker, this);
|
||||
}
|
||||
|
||||
void doStop() {
|
||||
_in->stopReader();
|
||||
out.stopWriter();
|
||||
stopWorker = true;
|
||||
cnd.notify_all();
|
||||
|
||||
if (generic_block<SampleFrameBuffer<T>>::workerThread.joinable()) { generic_block<SampleFrameBuffer<T>>::workerThread.join(); }
|
||||
if (readWorkerThread.joinable()) { readWorkerThread.join(); }
|
||||
|
||||
_in->clearReadStop();
|
||||
out.clearWriteStop();
|
||||
stopWorker = false;
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
|
||||
std::thread readWorkerThread;
|
||||
std::mutex bufMtx;
|
||||
std::condition_variable cnd;
|
||||
T* buffers[TEST_BUFFER_SIZE];
|
||||
int sizes[TEST_BUFFER_SIZE];
|
||||
|
||||
bool stopWorker = false;
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
195
core/src/dsp/chain.h
Normal file
195
core/src/dsp/chain.h
Normal file
@@ -0,0 +1,195 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "processor.h"
|
||||
|
||||
namespace dsp {
|
||||
template<class T>
|
||||
class chain {
|
||||
public:
|
||||
chain() {}
|
||||
|
||||
chain(stream<T>* in) { init(in); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
out = _in;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void setInput(stream<T>* in, Func onOutputChange) {
|
||||
_in = in;
|
||||
for (auto& ln : links) {
|
||||
if (states[ln]) {
|
||||
ln->setInput(_in);
|
||||
return;
|
||||
}
|
||||
}
|
||||
out = _in;
|
||||
onOutputChange(out);
|
||||
}
|
||||
|
||||
void addBlock(Processor<T, T>* block, bool enabled) {
|
||||
// Check if block is already part of the chain
|
||||
if (blockExists(block)) {
|
||||
throw std::runtime_error("[chain] Tried to add a block that is already part of the chain");
|
||||
}
|
||||
|
||||
// Add to the list
|
||||
links.push_back(block);
|
||||
states[block] = false;
|
||||
|
||||
// Enable if needed
|
||||
if (enabled) { enableBlock(block, [](stream<T>* out){}); }
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void removeBlock(Processor<T, T>* block, Func onOutputChange) {
|
||||
// Check if block is part of the chain
|
||||
if (!blockExists(block)) {
|
||||
throw std::runtime_error("[chain] Tried to remove a block that is not part of the chain");
|
||||
}
|
||||
|
||||
// Disable the block
|
||||
disableBlock(block, onOutputChange);
|
||||
|
||||
// Remove block from the list
|
||||
states.erase(block);
|
||||
links.erase(std::find(links.begin(), links.end(), block));
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void enableBlock(Processor<T, T>* block, Func onOutputChange) {
|
||||
// Check that the block is part of the chain
|
||||
if (!blockExists(block)) {
|
||||
throw std::runtime_error("[chain] Tried to enable a block that isn't part of the chain");
|
||||
}
|
||||
|
||||
// If already enable, don't do anything
|
||||
if (states[block]) { return; }
|
||||
|
||||
// Gather blocks before and after the block to enable
|
||||
Processor<T, T>* before = blockBefore(block);
|
||||
Processor<T, T>* after = blockAfter(block);
|
||||
|
||||
// Update input of next block or output
|
||||
if (after) {
|
||||
after->setInput(&block->out);
|
||||
}
|
||||
else {
|
||||
out = &block->out;
|
||||
onOutputChange(out);
|
||||
}
|
||||
|
||||
// Set input of the new block
|
||||
block->setInput(before ? &before->out : _in);
|
||||
|
||||
// Start new block
|
||||
if (running) { block->start(); }
|
||||
states[block] = true;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
|
||||
// Check that the block is part of the chain
|
||||
if (!blockExists(block)) {
|
||||
throw std::runtime_error("[chain] Tried to disable a block that isn't part of the chain");
|
||||
}
|
||||
|
||||
// If already disabled, don't do anything
|
||||
if (!states[block]) { return; }
|
||||
|
||||
// Stop disabled block
|
||||
block->stop();
|
||||
states[block] = false;
|
||||
|
||||
// Gather blocks before and after the block to disable
|
||||
Processor<T, T>* before = blockBefore(block);
|
||||
Processor<T, T>* after = blockAfter(block);
|
||||
|
||||
// Update input of next block or output
|
||||
if (after) {
|
||||
after->setInput(before ? &before->out : _in);
|
||||
}
|
||||
else {
|
||||
out = before ? &before->out : _in;
|
||||
onOutputChange(out);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void setBlockEnabled(Processor<T, T>* block, bool enabled, Func onOutputChange) {
|
||||
if (enabled) {
|
||||
enableBlock(block, onOutputChange);
|
||||
}
|
||||
else {
|
||||
disableBlock(block, onOutputChange);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void enableAllBlocks(Func onOutputChange) {
|
||||
for (auto& ln : links) {
|
||||
enableBlock(ln, onOutputChange);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void disableAllBlocks(Func onOutputChange) {
|
||||
for (auto& ln : links) {
|
||||
disableBlock(ln, onOutputChange);
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running) { return; }
|
||||
for (auto& ln : links) {
|
||||
if (!states[ln]) { continue; }
|
||||
ln->start();
|
||||
}
|
||||
running = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running) { return; }
|
||||
for (auto& ln : links) {
|
||||
if (!states[ln]) { continue; }
|
||||
ln->stop();
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
|
||||
stream<T>* out;
|
||||
|
||||
private:
|
||||
Processor<T, T>* blockBefore(Processor<T, T>* block) {
|
||||
// TODO: This is wrong and must be fixed when I get more time
|
||||
for (auto& ln : links) {
|
||||
if (ln == block) { return NULL; }
|
||||
if (states[ln]) { return ln; }
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Processor<T, T>* blockAfter(Processor<T, T>* block) {
|
||||
bool blockFound = false;
|
||||
for (auto& ln : links) {
|
||||
if (ln == block) {
|
||||
blockFound = true;
|
||||
continue;
|
||||
}
|
||||
if (states[ln] && blockFound) { return ln; }
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool blockExists(Processor<T, T>* block) {
|
||||
return states.find(block) != states.end();
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
std::vector<Processor<T, T>*> links;
|
||||
std::map<Processor<T, T>*, bool> states;
|
||||
bool running = false;
|
||||
};
|
||||
}
|
||||
67
core/src/dsp/channel/frequency_xlator.h
Normal file
67
core/src/dsp/channel/frequency_xlator.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../math/hz_to_rads.h"
|
||||
|
||||
namespace dsp::channel {
|
||||
class FrequencyXlator : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t>;
|
||||
public:
|
||||
FrequencyXlator() {}
|
||||
|
||||
FrequencyXlator(stream<complex_t>* in, double offset) { init(in, offset); }
|
||||
|
||||
FrequencyXlator(stream<complex_t>* in, double offset, double samplerate) { init(in, offset, samplerate); }
|
||||
|
||||
void init(stream<complex_t>* in, double offset) {
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
phaseDelta = lv_cmake(cos(offset), sin(offset));
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, double offset, double samplerate) {
|
||||
init(in, math::hzToRads(offset, samplerate));
|
||||
}
|
||||
|
||||
void setOffset(double offset) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
phaseDelta = lv_cmake(cos(offset), sin(offset));
|
||||
}
|
||||
|
||||
void setOffset(double offset, double samplerate) {
|
||||
setOffset(math::hzToRads(offset, samplerate));
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
tempStop();
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const complex_t* in, complex_t* out) {
|
||||
#if VOLK_VERSION >= 030100
|
||||
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, &phaseDelta, &phase, count);
|
||||
#else
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count);
|
||||
#endif
|
||||
return count;
|
||||
}
|
||||
|
||||
virtual 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:
|
||||
lv_32fc_t phase;
|
||||
lv_32fc_t phaseDelta;
|
||||
};
|
||||
}
|
||||
136
core/src/dsp/channel/rx_vfo.h
Normal file
136
core/src/dsp/channel/rx_vfo.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
#include "frequency_xlator.h"
|
||||
#include "../multirate/rational_resampler.h"
|
||||
|
||||
namespace dsp::channel {
|
||||
class RxVFO : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t>;
|
||||
public:
|
||||
RxVFO() {}
|
||||
|
||||
RxVFO(stream<complex_t>* in, double inSamplerate, double outSamplerate, double bandwidth, double offset) { init(in, inSamplerate, outSamplerate, bandwidth, offset); }
|
||||
|
||||
~RxVFO() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
taps::free(ftaps);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, double inSamplerate, double outSamplerate, double bandwidth, double offset) {
|
||||
_inSamplerate = inSamplerate;
|
||||
_outSamplerate = outSamplerate;
|
||||
_bandwidth = bandwidth;
|
||||
_offset = offset;
|
||||
filterNeeded = (_bandwidth != _outSamplerate);
|
||||
ftaps.taps = NULL;
|
||||
|
||||
xlator.init(NULL, -_offset, _inSamplerate);
|
||||
resamp.init(NULL, _inSamplerate, _outSamplerate);
|
||||
generateTaps();
|
||||
filter.init(NULL, ftaps);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setInSamplerate(double inSamplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_inSamplerate = inSamplerate;
|
||||
xlator.setOffset(-_offset, _inSamplerate);
|
||||
resamp.setInSamplerate(_inSamplerate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setOutSamplerate(double outSamplerate, double bandwidth) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_outSamplerate = outSamplerate;
|
||||
_bandwidth = bandwidth;
|
||||
filterNeeded = (_bandwidth != _outSamplerate);
|
||||
resamp.setOutSamplerate(_outSamplerate);
|
||||
if (filterNeeded) {
|
||||
generateTaps();
|
||||
filter.setTaps(ftaps);
|
||||
}
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
std::lock_guard<std::mutex> lck2(filterMtx);
|
||||
_bandwidth = bandwidth;
|
||||
filterNeeded = (_bandwidth != _outSamplerate);
|
||||
if (filterNeeded) {
|
||||
generateTaps();
|
||||
filter.setTaps(ftaps);
|
||||
}
|
||||
}
|
||||
|
||||
void setOffset(double offset) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_offset = offset;
|
||||
xlator.setOffset(-_offset, _inSamplerate);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
xlator.reset();
|
||||
resamp.reset();
|
||||
filter.reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const complex_t* in, complex_t* out) {
|
||||
xlator.process(count, in, out);
|
||||
if (!filterNeeded) {
|
||||
return resamp.process(count, out, out);
|
||||
}
|
||||
count = resamp.process(count, out, out);
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(filterMtx);
|
||||
filter.process(count, out, out);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
protected:
|
||||
void generateTaps() {
|
||||
taps::free(ftaps);
|
||||
double filterWidth = _bandwidth / 2.0;
|
||||
ftaps = taps::lowPass(filterWidth, filterWidth * 0.1, _outSamplerate);
|
||||
}
|
||||
|
||||
FrequencyXlator xlator;
|
||||
multirate::RationalResampler<complex_t> resamp;
|
||||
filter::FIR<complex_t, float> filter;
|
||||
tap<float> ftaps;
|
||||
bool filterNeeded;
|
||||
|
||||
double _inSamplerate;
|
||||
double _outSamplerate;
|
||||
double _bandwidth;
|
||||
double _offset;
|
||||
|
||||
std::mutex filterMtx;
|
||||
};
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/utils/macros.h>
|
||||
#include <dsp/interpolation_taps.h>
|
||||
|
||||
namespace dsp {
|
||||
class EdgeTrigClockRecovery : public generic_block<EdgeTrigClockRecovery> {
|
||||
public:
|
||||
EdgeTrigClockRecovery() {}
|
||||
|
||||
EdgeTrigClockRecovery(stream<float>* in, int omega) { init(in, omega); }
|
||||
|
||||
void init(stream<float>* in, int omega) {
|
||||
_in = in;
|
||||
samplesPerSymbol = omega;
|
||||
generic_block<EdgeTrigClockRecovery>::registerInput(_in);
|
||||
generic_block<EdgeTrigClockRecovery>::registerOutput(&out);
|
||||
generic_block<EdgeTrigClockRecovery>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<EdgeTrigClockRecovery>::_block_init);
|
||||
generic_block<EdgeTrigClockRecovery>::tempStop();
|
||||
generic_block<EdgeTrigClockRecovery>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<EdgeTrigClockRecovery>::registerInput(_in);
|
||||
generic_block<EdgeTrigClockRecovery>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = 0;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (DSP_SIGN(lastVal) != DSP_SIGN(_in->readBuf[i])) {
|
||||
counter = samplesPerSymbol / 2;
|
||||
lastVal = _in->readBuf[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (counter >= samplesPerSymbol) {
|
||||
counter = 0;
|
||||
out.writeBuf[outCount] = _in->readBuf[i];
|
||||
outCount++;
|
||||
}
|
||||
else {
|
||||
counter++;
|
||||
}
|
||||
|
||||
lastVal = _in->readBuf[i];
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (outCount > 0 && !out.swap(outCount)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
int samplesPerSymbol = 1;
|
||||
int counter = 0;
|
||||
float lastVal = 0;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class MMClockRecovery : public generic_block<MMClockRecovery<T>> {
|
||||
public:
|
||||
MMClockRecovery() {}
|
||||
|
||||
MMClockRecovery(stream<T>* in, float omega, float gainOmega, float muGain, float omegaRelLimit) {
|
||||
init(in, omega, gainOmega, muGain, omegaRelLimit);
|
||||
}
|
||||
|
||||
void init(stream<T>* in, float omega, float gainOmega, float muGain, float omegaRelLimit) {
|
||||
_in = in;
|
||||
_omega = omega;
|
||||
_muGain = muGain;
|
||||
_gainOmega = gainOmega;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
|
||||
omegaMin = _omega - (_omega * _omegaRelLimit);
|
||||
omegaMax = _omega + (_omega * _omegaRelLimit);
|
||||
_dynOmega = _omega;
|
||||
|
||||
memset(delay, 0, 1024 * sizeof(T));
|
||||
|
||||
generic_block<MMClockRecovery<T>>::registerInput(_in);
|
||||
generic_block<MMClockRecovery<T>>::registerOutput(&out);
|
||||
generic_block<MMClockRecovery<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setOmega(float omega, float omegaRelLimit) {
|
||||
assert(generic_block<MMClockRecovery<T>>::_block_init);
|
||||
generic_block<MMClockRecovery<T>>::tempStop();
|
||||
omegaMin = _omega - (_omega * _omegaRelLimit);
|
||||
omegaMax = _omega + (_omega * _omegaRelLimit);
|
||||
_omega = omega;
|
||||
_dynOmega = _omega;
|
||||
generic_block<MMClockRecovery<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setGains(float omegaGain, float muGain) {
|
||||
assert(generic_block<MMClockRecovery<T>>::_block_init);
|
||||
generic_block<MMClockRecovery<T>>::tempStop();
|
||||
_gainOmega = omegaGain;
|
||||
_muGain = muGain;
|
||||
generic_block<MMClockRecovery<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(float omegaRelLimit) {
|
||||
assert(generic_block<MMClockRecovery<T>>::_block_init);
|
||||
generic_block<MMClockRecovery<T>>::tempStop();
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
omegaMin = _omega - (_omega * _omegaRelLimit);
|
||||
omegaMax = _omega + (_omega * _omegaRelLimit);
|
||||
generic_block<MMClockRecovery<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<MMClockRecovery<T>>::_block_init);
|
||||
generic_block<MMClockRecovery<T>>::tempStop();
|
||||
generic_block<MMClockRecovery<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<MMClockRecovery<T>>::registerInput(_in);
|
||||
generic_block<MMClockRecovery<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = 0;
|
||||
float outVal;
|
||||
float phaseError;
|
||||
float roundedStep;
|
||||
int maxOut = 2.0f * _omega * (float)count;
|
||||
|
||||
// Copy the first 7 values to the delay buffer for fast computing
|
||||
memcpy(&delay[7], _in->readBuf, 7 * sizeof(T));
|
||||
|
||||
int i = nextOffset;
|
||||
for (; i < count && outCount < maxOut;) {
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
// Calculate output value
|
||||
// If we still need to use the old values, calculate using delay buf
|
||||
// Otherwise, use normal buffer
|
||||
if (i < 7) {
|
||||
volk_32f_x2_dot_prod_32f(&outVal, &delay[i], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
|
||||
}
|
||||
else {
|
||||
volk_32f_x2_dot_prod_32f(&outVal, &_in->readBuf[i - 7], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
|
||||
}
|
||||
out.writeBuf[outCount++] = outVal;
|
||||
|
||||
// Cursed phase detect approximation (don't ask me how this approximation works)
|
||||
phaseError = (DSP_STEP(lastOutput)*outVal) - (lastOutput*DSP_STEP(outVal));
|
||||
lastOutput = outVal;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
// Propagate delay
|
||||
_p_2T = _p_1T;
|
||||
_p_1T = _p_0T;
|
||||
|
||||
_c_2T = _c_1T;
|
||||
_c_1T = _c_0T;
|
||||
|
||||
// Perform interpolation the same way as for float values
|
||||
if (i < 7) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&_p_0T, (lv_32fc_t*)&delay[i], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
|
||||
}
|
||||
else {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&_p_0T, (lv_32fc_t*)&_in->readBuf[i - 7], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
|
||||
}
|
||||
out.writeBuf[outCount++] = _p_0T;
|
||||
|
||||
// Slice output value
|
||||
_c_0T = DSP_STEP_CPLX(_p_0T);
|
||||
|
||||
// Cursed math to calculate the phase error
|
||||
phaseError = (((_p_0T - _p_2T) * _c_1T.conj()) - ((_c_0T - _c_2T) * _p_1T.conj())).re;
|
||||
}
|
||||
|
||||
// Clamp phase error
|
||||
if (phaseError > 1.0f) { phaseError = 1.0f; }
|
||||
if (phaseError < -1.0f) { phaseError = -1.0f; }
|
||||
|
||||
// Adjust the symbol rate using the phase error approximation and clamp
|
||||
// TODO: Branchless clamp
|
||||
_dynOmega = _dynOmega + (_gainOmega * phaseError);
|
||||
if (_dynOmega > omegaMax) { _dynOmega = omegaMax; }
|
||||
else if (_dynOmega < omegaMin) { _dynOmega = omegaMin; }
|
||||
|
||||
// Adjust the symbol phase according to the phase error approximation
|
||||
// It will now contain the phase delta needed to jump to the next symbol
|
||||
// Rounded step will contain the rounded number of symbols
|
||||
_mu = _mu + _dynOmega + (_muGain * phaseError);
|
||||
roundedStep = floor(_mu);
|
||||
|
||||
// Step to where the next symbol should be, and check for bogus input
|
||||
i += (int)roundedStep;
|
||||
if (i < 0) { i = 0; }
|
||||
|
||||
// Now that we've stepped to the next symbol, keep only the offset inside the symbol
|
||||
_mu -= roundedStep;
|
||||
}
|
||||
|
||||
nextOffset = i - count;
|
||||
|
||||
// Save the last 7 values for the next round
|
||||
memcpy(delay, &_in->readBuf[count - 7], 7 * sizeof(T));
|
||||
|
||||
_in->flush();
|
||||
if (outCount > 0 && !out.swap(outCount)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
|
||||
// Delay buffer
|
||||
T delay[1024];
|
||||
int nextOffset = 0;
|
||||
|
||||
// Configuration
|
||||
float _omega = 1.0f;
|
||||
float _muGain = 1.0f;
|
||||
float _gainOmega = 0.001f;
|
||||
float _omegaRelLimit = 0.005;
|
||||
|
||||
// Precalculated values
|
||||
float omegaMin = _omega + (_omega * _omegaRelLimit);
|
||||
float omegaMax = _omega + (_omega * _omegaRelLimit);
|
||||
|
||||
// Runtime adjusted
|
||||
float _dynOmega = _omega;
|
||||
float _mu = 0.5f;
|
||||
float lastOutput = 0.0f;
|
||||
|
||||
// Cursed complex stuff
|
||||
complex_t _p_0T = {0,0}, _p_1T = {0,0}, _p_2T = {0,0};
|
||||
complex_t _c_0T = {0,0}, _c_1T = {0,0}, _c_2T = {0,0};
|
||||
|
||||
stream<T>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
189
core/src/dsp/clock_recovery/fd.h
Normal file
189
core/src/dsp/clock_recovery/fd.h
Normal file
@@ -0,0 +1,189 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../loop/phase_control_loop.h"
|
||||
#include "../taps/windowed_sinc.h"
|
||||
#include "../multirate/polyphase_bank.h"
|
||||
#include "../math/step.h"
|
||||
|
||||
namespace dsp::clock_recovery {
|
||||
class FD : public Processor<float, float> {
|
||||
using base_type = Processor<float, float> ;
|
||||
public:
|
||||
FD() {}
|
||||
|
||||
FD(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); }
|
||||
|
||||
~FD() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
buffer::free(buffer);
|
||||
}
|
||||
|
||||
void init(stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
|
||||
_omega = omega;
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
_interpPhaseCount = interpPhaseCount;
|
||||
_interpTapCount = interpTapCount;
|
||||
|
||||
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
|
||||
generateInterpTaps();
|
||||
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
|
||||
bufStart = &buffer[_interpTapCount - 1];
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setOmega(double omega) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_omega = omega;
|
||||
offset = 0;
|
||||
pcl.phase = 0.0f;
|
||||
pcl.freq = _omega;
|
||||
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setOmegaGain(double omegaGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_omegaGain = omegaGain;
|
||||
pcl.setCoefficients(_muGain, _omegaGain);
|
||||
}
|
||||
|
||||
void setMuGain(double muGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_muGain = muGain;
|
||||
pcl.setCoefficients(_muGain, _omegaGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(double omegaRelLimit) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
|
||||
}
|
||||
|
||||
void 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);
|
||||
buffer::free(buffer);
|
||||
generateInterpTaps();
|
||||
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
|
||||
bufStart = &buffer[_interpTapCount - 1];
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
offset = 0;
|
||||
pcl.phase = 0.0f;
|
||||
pcl.freq = _omega;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const float* in, float* out) {
|
||||
// Copy data to work buffer
|
||||
memcpy(bufStart, in, count * sizeof(float));
|
||||
|
||||
// Process all samples
|
||||
int outCount = 0;
|
||||
while (offset < count) {
|
||||
float error;
|
||||
float outVal;
|
||||
float dfdt;
|
||||
|
||||
// Calculate new output value
|
||||
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
|
||||
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
|
||||
out[outCount++] = outVal;
|
||||
|
||||
// Calculate derivative of the signal
|
||||
if (phase == 0) {
|
||||
float fT1;
|
||||
volk_32f_x2_dot_prod_32f(&fT1, &buffer[offset], interpBank.phases[phase+1], _interpTapCount);
|
||||
dfdt = fT1 - outVal;
|
||||
}
|
||||
else if (phase == _interpPhaseCount - 1) {
|
||||
float fT_1;
|
||||
volk_32f_x2_dot_prod_32f(&fT_1, &buffer[offset], interpBank.phases[phase-1], _interpTapCount);
|
||||
dfdt = outVal - fT_1;
|
||||
}
|
||||
else {
|
||||
float fT_1;
|
||||
float fT1;
|
||||
volk_32f_x2_dot_prod_32f(&fT_1, &buffer[offset], interpBank.phases[phase-1], _interpTapCount);
|
||||
volk_32f_x2_dot_prod_32f(&fT1, &buffer[offset], interpBank.phases[phase+1], _interpTapCount);
|
||||
dfdt = (fT1 - fT_1) * 0.5f;
|
||||
}
|
||||
|
||||
// Calculate error
|
||||
error = dfdt * math::step(outVal);
|
||||
|
||||
// Clamp symbol phase error
|
||||
if (error > 1.0f) { error = 1.0f; }
|
||||
if (error < -1.0f) { error = -1.0f; }
|
||||
|
||||
// Advance symbol offset and phase
|
||||
pcl.advance(error);
|
||||
float delta = floorf(pcl.phase);
|
||||
offset += delta;
|
||||
pcl.phase -= delta;
|
||||
}
|
||||
offset -= count;
|
||||
|
||||
// Update delay buffer
|
||||
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
loop::PhaseControlLoop<float, false> pcl;
|
||||
|
||||
protected:
|
||||
void generateInterpTaps() {
|
||||
double bw = 0.5 / (double)_interpPhaseCount;
|
||||
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
|
||||
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
|
||||
taps::free(lp);
|
||||
}
|
||||
|
||||
dsp::multirate::PolyphaseBank<float> interpBank;
|
||||
|
||||
double _omega;
|
||||
double _omegaGain;
|
||||
double _muGain;
|
||||
double _omegaRelLimit;
|
||||
int _interpPhaseCount;
|
||||
int _interpTapCount;
|
||||
|
||||
int offset = 0;
|
||||
float* buffer;
|
||||
float* bufStart;
|
||||
};
|
||||
}
|
||||
199
core/src/dsp/clock_recovery/mm.h
Normal file
199
core/src/dsp/clock_recovery/mm.h
Normal file
@@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../loop/phase_control_loop.h"
|
||||
#include "../taps/windowed_sinc.h"
|
||||
#include "../multirate/polyphase_bank.h"
|
||||
#include "../math/step.h"
|
||||
|
||||
namespace dsp::clock_recovery {
|
||||
template<class T>
|
||||
class MM : public Processor<T, T> {
|
||||
using base_type = Processor<T, T> ;
|
||||
public:
|
||||
MM() {}
|
||||
|
||||
MM(stream<T>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
|
||||
|
||||
~MM() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::multirate::freePolyphaseBank(interpBank);
|
||||
buffer::free(buffer);
|
||||
}
|
||||
|
||||
void init(stream<T>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
|
||||
_omega = omega;
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
_interpPhaseCount = interpPhaseCount;
|
||||
_interpTapCount = interpTapCount;
|
||||
|
||||
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
|
||||
generateInterpTaps();
|
||||
buffer = buffer::alloc<T>(STREAM_BUFFER_SIZE + _interpTapCount);
|
||||
bufStart = &buffer[_interpTapCount - 1];
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setOmega(double omega) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_omega = omega;
|
||||
offset = 0;
|
||||
pcl.phase = 0.0f;
|
||||
pcl.freq = _omega;
|
||||
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setOmegaGain(double omegaGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_omegaGain = omegaGain;
|
||||
pcl.setCoefficients(_muGain, _omegaGain);
|
||||
}
|
||||
|
||||
void setMuGain(double muGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_muGain = muGain;
|
||||
pcl.setCoefficients(_muGain, _omegaGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(double omegaRelLimit) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
|
||||
}
|
||||
|
||||
void 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);
|
||||
buffer::free(buffer);
|
||||
generateInterpTaps();
|
||||
buffer = buffer::alloc<T>(STREAM_BUFFER_SIZE + _interpTapCount);
|
||||
bufStart = &buffer[_interpTapCount - 1];
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
offset = 0;
|
||||
pcl.phase = 0.0f;
|
||||
pcl.freq = _omega;
|
||||
lastOut = 0.0f;
|
||||
_p_0T = { 0.0f, 0.0f }; _p_1T = { 0.0f, 0.0f }; _p_2T = { 0.0f, 0.0f };
|
||||
_c_0T = { 0.0f, 0.0f }; _c_1T = { 0.0f, 0.0f }; _c_2T = { 0.0f, 0.0f };
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const T* in, T* out) {
|
||||
// Copy data to work buffer
|
||||
memcpy(bufStart, in, count * sizeof(T));
|
||||
|
||||
// Process all samples
|
||||
int outCount = 0;
|
||||
while (offset < count) {
|
||||
float error;
|
||||
T outVal;
|
||||
|
||||
// Calculate new output value
|
||||
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&outVal, (lv_32fc_t*)&buffer[offset], interpBank.phases[phase], _interpTapCount);
|
||||
}
|
||||
out[outCount++] = outVal;
|
||||
|
||||
// Calculate symbol phase error
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
error = (math::step(lastOut) * outVal) - (lastOut * math::step(outVal));
|
||||
lastOut = outVal;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
// Propagate delay
|
||||
_p_2T = _p_1T;
|
||||
_p_1T = _p_0T;
|
||||
_c_2T = _c_1T;
|
||||
_c_1T = _c_0T;
|
||||
|
||||
// Update the T0 values
|
||||
_p_0T = outVal;
|
||||
_c_0T = math::step(outVal);
|
||||
|
||||
// Error
|
||||
error = (((_p_0T - _p_2T) * _c_1T.conj()) - ((_c_0T - _c_2T) * _p_1T.conj())).re;
|
||||
}
|
||||
|
||||
// Clamp symbol phase error
|
||||
if (error > 1.0f) { error = 1.0f; }
|
||||
if (error < -1.0f) { error = -1.0f; }
|
||||
|
||||
// Advance symbol offset and phase
|
||||
pcl.advance(error);
|
||||
float delta = floorf(pcl.phase);
|
||||
offset += delta;
|
||||
pcl.phase -= delta;
|
||||
}
|
||||
offset -= count;
|
||||
|
||||
// Update delay buffer
|
||||
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(T));
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
protected:
|
||||
void generateInterpTaps() {
|
||||
double bw = 0.5 / (double)_interpPhaseCount;
|
||||
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
|
||||
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
|
||||
taps::free(lp);
|
||||
}
|
||||
|
||||
dsp::multirate::PolyphaseBank<float> interpBank;
|
||||
loop::PhaseControlLoop<float, false> pcl;
|
||||
|
||||
double _omega;
|
||||
double _omegaGain;
|
||||
double _muGain;
|
||||
double _omegaRelLimit;
|
||||
int _interpPhaseCount;
|
||||
int _interpTapCount;
|
||||
|
||||
// Previous output storage
|
||||
float lastOut = 0.0f;
|
||||
complex_t _p_0T = { 0.0f, 0.0f }, _p_1T = { 0.0f, 0.0f }, _p_2T = { 0.0f, 0.0f };
|
||||
complex_t _c_0T = { 0.0f, 0.0f }, _c_1T = { 0.0f, 0.0f }, _c_2T = { 0.0f, 0.0f };
|
||||
|
||||
int offset = 0;
|
||||
T* buffer;
|
||||
T* bufStart;
|
||||
};
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
|
||||
namespace dsp {
|
||||
class DynamicRangeCompressor : public generic_block<DynamicRangeCompressor> {
|
||||
public:
|
||||
DynamicRangeCompressor() {}
|
||||
|
||||
enum PCMType {
|
||||
PCM_TYPE_I8,
|
||||
PCM_TYPE_I16,
|
||||
PCM_TYPE_F32
|
||||
};
|
||||
|
||||
DynamicRangeCompressor(stream<complex_t>* in, PCMType pcmType) { init(in, pcmType); }
|
||||
|
||||
void init(stream<complex_t>* in, PCMType pcmType) {
|
||||
_in = in;
|
||||
_pcmType = pcmType;
|
||||
generic_block<DynamicRangeCompressor>::registerInput(_in);
|
||||
generic_block<DynamicRangeCompressor>::registerOutput(&out);
|
||||
generic_block<DynamicRangeCompressor>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<DynamicRangeCompressor>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<DynamicRangeCompressor>::ctrlMtx);
|
||||
generic_block<DynamicRangeCompressor>::tempStop();
|
||||
generic_block<DynamicRangeCompressor>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<DynamicRangeCompressor>::registerInput(_in);
|
||||
generic_block<DynamicRangeCompressor>::tempStart();
|
||||
}
|
||||
|
||||
void setPCMType(PCMType pcmType) {
|
||||
assert(generic_block<DynamicRangeCompressor>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<DynamicRangeCompressor>::ctrlMtx);
|
||||
_pcmType = pcmType;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
float* scaler = (float*)out.writeBuf;
|
||||
void* dataBuf = &out.writeBuf[4];
|
||||
|
||||
// If no dynamic range compression is to be done, just pass the data to the output with a null scaler
|
||||
if (_pcmType == PCM_TYPE_F32) {
|
||||
*scaler = 0;
|
||||
memcpy(dataBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
_in->flush();
|
||||
if (!out.swap(4 + (count * sizeof(complex_t)))) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
// Find maximum value
|
||||
complex_t val;
|
||||
float absre;
|
||||
float absim;
|
||||
float maxVal = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
val = _in->readBuf[i];
|
||||
absre = fabsf(val.re);
|
||||
absim = fabsf(val.im);
|
||||
if (absre > maxVal) { maxVal = absre; }
|
||||
if (absim > maxVal) { maxVal = absim; }
|
||||
}
|
||||
|
||||
// Convert to the right type and send it out (sign bit determins pcm type)
|
||||
if (_pcmType == PCM_TYPE_I8) {
|
||||
*scaler = maxVal;
|
||||
volk_32f_s32f_convert_8i((int8_t*)dataBuf, (float*)_in->readBuf, 128.0f / maxVal, count * 2);
|
||||
_in->flush();
|
||||
if (!out.swap(4 + (count * sizeof(int8_t) * 2))) { return -1; }
|
||||
}
|
||||
else if (_pcmType == PCM_TYPE_I16) {
|
||||
*scaler = -maxVal;
|
||||
volk_32f_s32f_convert_16i((int16_t*)dataBuf, (float*)_in->readBuf, 32768.0f / maxVal, count * 2);
|
||||
_in->flush();
|
||||
if (!out.swap(4 + (count * sizeof(int16_t) * 2))) { return -1; }
|
||||
}
|
||||
else {
|
||||
_in->flush();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
PCMType _pcmType;
|
||||
|
||||
};
|
||||
|
||||
class DynamicRangeDecompressor : public generic_block<DynamicRangeDecompressor> {
|
||||
public:
|
||||
DynamicRangeDecompressor() {}
|
||||
|
||||
DynamicRangeDecompressor(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
generic_block<DynamicRangeDecompressor>::registerInput(_in);
|
||||
generic_block<DynamicRangeDecompressor>::registerOutput(&out);
|
||||
generic_block<DynamicRangeDecompressor>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<DynamicRangeDecompressor>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<DynamicRangeDecompressor>::ctrlMtx);
|
||||
generic_block<DynamicRangeDecompressor>::tempStop();
|
||||
generic_block<DynamicRangeDecompressor>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<DynamicRangeDecompressor>::registerInput(_in);
|
||||
generic_block<DynamicRangeDecompressor>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
float* scaler = (float*)_in->readBuf;
|
||||
void* dataBuf = &_in->readBuf[4];
|
||||
|
||||
// If the scaler is null, data is F32
|
||||
if (*scaler == 0) {
|
||||
memcpy(out.writeBuf, dataBuf, count - 4);
|
||||
_in->flush();
|
||||
if (!out.swap((count - 4) / sizeof(complex_t))) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
// Convert back to f32 from the pcm type
|
||||
float absScale = fabsf(*scaler);
|
||||
if (*scaler > 0) {
|
||||
spdlog::warn("{0}", absScale);
|
||||
int outCount = (count - 4) / (sizeof(int8_t) * 2);
|
||||
volk_8i_s32f_convert_32f((float*)out.writeBuf, (int8_t*)dataBuf, 128.0f / absScale, outCount * 2);
|
||||
_in->flush();
|
||||
if (!out.swap(outCount)) { return -1; }
|
||||
}
|
||||
else {
|
||||
int outCount = (count - 4) / (sizeof(int16_t) * 2);
|
||||
volk_16i_s32f_convert_32f((float*)out.writeBuf, (int16_t*)dataBuf, 32768.0f / absScale, outCount * 2);
|
||||
_in->flush();
|
||||
if (!out.swap(outCount)) { return -1; }
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
9
core/src/dsp/compression/pcm_type.h
Normal file
9
core/src/dsp/compression/pcm_type.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace dsp::compression {
|
||||
enum PCMType {
|
||||
PCM_TYPE_I8,
|
||||
PCM_TYPE_I16,
|
||||
PCM_TYPE_F32
|
||||
};
|
||||
}
|
||||
83
core/src/dsp/compression/sample_stream_compressor.h
Normal file
83
core/src/dsp/compression/sample_stream_compressor.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "pcm_type.h"
|
||||
|
||||
namespace dsp::compression {
|
||||
class SampleStreamCompressor : public Processor<complex_t, uint8_t> {
|
||||
using base_type = Processor<complex_t, uint8_t>;
|
||||
public:
|
||||
SampleStreamCompressor() {}
|
||||
|
||||
SampleStreamCompressor(stream<complex_t>* in, PCMType pcmType) { init(in, pcmType); }
|
||||
|
||||
void init(stream<complex_t>* in, PCMType pcmType) {
|
||||
_pcmType = pcmType;
|
||||
|
||||
// Set the output buffer size to the max size of a complex buffer + 8 bytes for the header
|
||||
out.setBufferSize(STREAM_BUFFER_SIZE*sizeof(complex_t) + 8);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setPCMType(PCMType pcmType) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_pcmType = pcmType;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline static int process(int count, PCMType pcmType, const complex_t* in, uint8_t* out) {
|
||||
uint16_t* compressionType = (uint16_t*)out;
|
||||
uint16_t* sampleType = (uint16_t*)&out[2];
|
||||
float* scaler = (float*)&out[4];
|
||||
void* dataBuf = &out[8];
|
||||
|
||||
// Write options and leave blank space for compression
|
||||
*compressionType = 0;
|
||||
*sampleType = pcmType;
|
||||
|
||||
// If type is float32, no compression is needed
|
||||
if (pcmType == PCMType::PCM_TYPE_F32) {
|
||||
*scaler = 0;
|
||||
memcpy(dataBuf, in, count * sizeof(complex_t));
|
||||
return 8 + (count * sizeof(complex_t));
|
||||
}
|
||||
|
||||
// Find maximum value
|
||||
uint32_t maxIdx;
|
||||
volk_32f_index_max_32u(&maxIdx, (float*)in, count * 2);
|
||||
float maxVal = ((float*)in)[maxIdx];
|
||||
*scaler = maxVal;
|
||||
|
||||
// Convert to the right type and send it out (sign bit determines pcm type)
|
||||
if (pcmType == PCMType::PCM_TYPE_I8) {
|
||||
volk_32f_s32f_convert_8i((int8_t*)dataBuf, (float*)in, 128.0f / maxVal, count * 2);
|
||||
return 8 + (count * sizeof(int8_t) * 2);
|
||||
}
|
||||
else if (pcmType == PCMType::PCM_TYPE_I16) {
|
||||
volk_32f_s32f_convert_16i((int16_t*)dataBuf, (float*)in, 32768.0f / maxVal, count * 2);
|
||||
return 8 + (count * sizeof(int16_t) * 2);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, _pcmType, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
protected:
|
||||
PCMType _pcmType;
|
||||
};
|
||||
}
|
||||
50
core/src/dsp/compression/sample_stream_decompressor.h
Normal file
50
core/src/dsp/compression/sample_stream_decompressor.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "pcm_type.h"
|
||||
|
||||
namespace dsp::compression {
|
||||
class SampleStreamDecompressor : public Processor<uint8_t, complex_t> {
|
||||
using base_type = Processor<uint8_t, complex_t>;
|
||||
public:
|
||||
SampleStreamDecompressor() {}
|
||||
|
||||
SampleStreamDecompressor(stream<uint8_t>* in) { base_type::init(in); }
|
||||
|
||||
inline int process(int count, const uint8_t* in, complex_t* out) {
|
||||
uint16_t sampleType = *(uint16_t*)&in[2];
|
||||
float scaler = *(float*)&in[4];
|
||||
const void* dataBuf = &in[8];
|
||||
|
||||
if (sampleType == PCMType::PCM_TYPE_F32) {
|
||||
memcpy(out, dataBuf, count - 8);
|
||||
return (count - 8) / sizeof(complex_t);
|
||||
}
|
||||
else if (sampleType == PCMType::PCM_TYPE_I16) {
|
||||
int outCount = (count - 8) / (sizeof(int16_t) * 2);
|
||||
volk_16i_s32f_convert_32f((float*)out, (int16_t*)dataBuf, 32768.0f / scaler, outCount * 2);
|
||||
return outCount;
|
||||
}
|
||||
else if (sampleType == PCMType::PCM_TYPE_I8) {
|
||||
int outCount = (count - 8) / (sizeof(int8_t) * 2);
|
||||
volk_8i_s32f_convert_32f((float*)out, (int8_t*)dataBuf, 128.0f / scaler, outCount * 2);
|
||||
return outCount;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
|
||||
namespace dsp {
|
||||
class ComplexToStereo : public generic_block<ComplexToStereo> {
|
||||
public:
|
||||
ComplexToStereo() {}
|
||||
|
||||
ComplexToStereo(stream<complex_t>* in) { init(in); }
|
||||
|
||||
static_assert(sizeof(complex_t) == sizeof(stereo_t));
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<ComplexToStereo>::registerInput(_in);
|
||||
generic_block<ComplexToStereo>::registerOutput(&out);
|
||||
generic_block<ComplexToStereo>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<ComplexToStereo>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexToStereo>::ctrlMtx);
|
||||
generic_block<ComplexToStereo>::tempStop();
|
||||
generic_block<ComplexToStereo>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexToStereo>::registerInput(_in);
|
||||
generic_block<ComplexToStereo>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ComplexToReal : public generic_block<ComplexToReal> {
|
||||
public:
|
||||
ComplexToReal() {}
|
||||
|
||||
ComplexToReal(stream<complex_t>* in) { init(in); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<ComplexToReal>::registerInput(_in);
|
||||
generic_block<ComplexToReal>::registerOutput(&out);
|
||||
generic_block<ComplexToReal>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<ComplexToReal>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexToReal>::ctrlMtx);
|
||||
generic_block<ComplexToReal>::tempStop();
|
||||
generic_block<ComplexToReal>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexToReal>::registerInput(_in);
|
||||
generic_block<ComplexToReal>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_deinterleave_real_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ComplexToImag : public generic_block<ComplexToImag> {
|
||||
public:
|
||||
ComplexToImag() {}
|
||||
|
||||
ComplexToImag(stream<complex_t>* in) { init(in); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<ComplexToImag>::registerInput(_in);
|
||||
generic_block<ComplexToImag>::registerOutput(&out);
|
||||
generic_block<ComplexToImag>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<ComplexToImag>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexToImag>::ctrlMtx);
|
||||
generic_block<ComplexToImag>::tempStop();
|
||||
generic_block<ComplexToImag>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexToImag>::registerInput(_in);
|
||||
generic_block<ComplexToImag>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_deinterleave_imag_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
if(!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class RealToComplex : public generic_block<RealToComplex> {
|
||||
public:
|
||||
RealToComplex() {}
|
||||
|
||||
RealToComplex(stream<float>* in) { init(in); }
|
||||
|
||||
~RealToComplex() {
|
||||
if (!generic_block<RealToComplex>::_block_init) { return; }
|
||||
generic_block<RealToComplex>::stop();
|
||||
delete[] nullBuffer;
|
||||
generic_block<RealToComplex>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<float>* in) {
|
||||
_in = in;
|
||||
nullBuffer = new float[STREAM_BUFFER_SIZE];
|
||||
memset(nullBuffer, 0, STREAM_BUFFER_SIZE * sizeof(float));
|
||||
generic_block<RealToComplex>::registerInput(_in);
|
||||
generic_block<RealToComplex>::registerOutput(&out);
|
||||
generic_block<RealToComplex>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<RealToComplex>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<RealToComplex>::ctrlMtx);
|
||||
generic_block<RealToComplex>::tempStop();
|
||||
generic_block<RealToComplex>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<RealToComplex>::registerInput(_in);
|
||||
generic_block<RealToComplex>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, nullBuffer, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
float* nullBuffer;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
class Int16CToComplex : public generic_block<Int16CToComplex> {
|
||||
public:
|
||||
Int16CToComplex() {}
|
||||
|
||||
Int16CToComplex(stream<int16_t>* in) { init(in); }
|
||||
|
||||
void init(stream<int16_t>* in) {
|
||||
_in = in;
|
||||
generic_block<Int16CToComplex>::registerInput(_in);
|
||||
generic_block<Int16CToComplex>::registerOutput(&out);
|
||||
generic_block<Int16CToComplex>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<int16_t>* in) {
|
||||
assert(generic_block<Int16CToComplex>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Int16CToComplex>::ctrlMtx);
|
||||
generic_block<Int16CToComplex>::tempStop();
|
||||
generic_block<Int16CToComplex>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Int16CToComplex>::registerInput(_in);
|
||||
generic_block<Int16CToComplex>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_16i_s32f_convert_32f((float*)out.writeBuf, _in->readBuf, 32768.0f, count * 2);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
stream<int16_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ComplexToInt16C : public generic_block<ComplexToInt16C> {
|
||||
public:
|
||||
ComplexToInt16C() {}
|
||||
|
||||
ComplexToInt16C(stream<complex_t>* in) { init(in); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<ComplexToInt16C>::registerInput(_in);
|
||||
generic_block<ComplexToInt16C>::registerOutput(&out);
|
||||
generic_block<ComplexToInt16C>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<ComplexToInt16C>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexToInt16C>::ctrlMtx);
|
||||
generic_block<ComplexToInt16C>::tempStop();
|
||||
generic_block<ComplexToInt16C>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexToInt16C>::registerInput(_in);
|
||||
generic_block<ComplexToInt16C>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32f_s32f_convert_16i(out.writeBuf, (float*)_in->readBuf, 32768.0f, count * 2);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<int16_t> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class Int16ToFloat : public generic_block<Int16ToFloat> {
|
||||
public:
|
||||
Int16ToFloat() {}
|
||||
|
||||
Int16ToFloat(stream<int16_t>* in) { init(in); }
|
||||
|
||||
void init(stream<int16_t>* in) {
|
||||
_in = in;
|
||||
generic_block<Int16ToFloat>::registerInput(_in);
|
||||
generic_block<Int16ToFloat>::registerOutput(&out);
|
||||
generic_block<Int16ToFloat>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<int16_t>* in) {
|
||||
assert(generic_block<Int16ToFloat>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Int16ToFloat>::ctrlMtx);
|
||||
generic_block<Int16ToFloat>::tempStop();
|
||||
generic_block<Int16ToFloat>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Int16ToFloat>::registerInput(_in);
|
||||
generic_block<Int16ToFloat>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_16i_s32f_convert_32f(out.writeBuf, _in->readBuf, 32768.0f, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
stream<int16_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class FloatToInt16 : public generic_block<FloatToInt16> {
|
||||
public:
|
||||
FloatToInt16() {}
|
||||
|
||||
FloatToInt16(stream<float>* in) { init(in); }
|
||||
|
||||
void init(stream<float>* in) {
|
||||
_in = in;
|
||||
generic_block<FloatToInt16>::registerInput(_in);
|
||||
generic_block<FloatToInt16>::registerOutput(&out);
|
||||
generic_block<FloatToInt16>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<FloatToInt16>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FloatToInt16>::ctrlMtx);
|
||||
generic_block<FloatToInt16>::tempStop();
|
||||
generic_block<FloatToInt16>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FloatToInt16>::registerInput(_in);
|
||||
generic_block<FloatToInt16>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32f_s32f_convert_16i(out.writeBuf, _in->readBuf, 32768.0f, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<int16_t> out;
|
||||
|
||||
private:
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
30
core/src/dsp/convert/complex_to_real.h
Normal file
30
core/src/dsp/convert/complex_to_real.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::convert {
|
||||
class ComplexToReal : public Processor<complex_t, float> {
|
||||
using base_type = Processor<complex_t, float>;
|
||||
public:
|
||||
ComplexToReal() {}
|
||||
|
||||
ComplexToReal(stream<complex_t>* in) { init(in); }
|
||||
|
||||
void init(stream<complex_t>* in) { base_type::init(in); }
|
||||
|
||||
inline static int process(int count, const complex_t* in, float* out) {
|
||||
volk_32fc_deinterleave_real_32f(out, (lv_32fc_t*)in, count);
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
25
core/src/dsp/convert/complex_to_stereo.h
Normal file
25
core/src/dsp/convert/complex_to_stereo.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::convert {
|
||||
class ComplexToStereo : public Processor<complex_t, stereo_t> {
|
||||
using base_type = Processor<complex_t, stereo_t>;
|
||||
public:
|
||||
ComplexToStereo() {}
|
||||
|
||||
ComplexToStereo(stream<complex_t>* in) { init(in); }
|
||||
|
||||
void init(stream<complex_t>* in) { base_type::init(in); }
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
memcpy(base_type::out.writeBuf, base_type::_in->readBuf, count * sizeof(complex_t));
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!base_type::out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
};
|
||||
}
|
||||
44
core/src/dsp/convert/l_r_to_stereo.h
Normal file
44
core/src/dsp/convert/l_r_to_stereo.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "../operator.h"
|
||||
|
||||
namespace dsp::convert {
|
||||
class LRToStereo : public Operator<float, float, stereo_t> {
|
||||
using base_type = Operator<float, float, stereo_t>;
|
||||
public:
|
||||
LRToStereo() {}
|
||||
|
||||
LRToStereo(stream<float>* l, stream<float>* r) { init(l, r); }
|
||||
|
||||
void init(stream<float>* l, stream<float>* r) { base_type::init(l, r); }
|
||||
|
||||
void setInputs(stream<float>* l, stream<float>* r) { base_type::setInputs(l, r); }
|
||||
|
||||
void setInputL(stream<float>* l) { base_type::setInputA(l); }
|
||||
|
||||
void setInputR(stream<float>* r) { base_type::setInputB(r); }
|
||||
|
||||
static inline int process(int count, const float* l, const float* r, stereo_t* out) {
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out, l, r, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int a_count = base_type::_a->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
int b_count = base_type::_b->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
base_type::_a->flush();
|
||||
base_type::_b->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
process(a_count, base_type::_a->readBuf, base_type::_b->readBuf, base_type::out.writeBuf);
|
||||
|
||||
base_type::_a->flush();
|
||||
base_type::_b->flush();
|
||||
if (!base_type::out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
};
|
||||
}
|
||||
28
core/src/dsp/convert/mono_to_stereo.h
Normal file
28
core/src/dsp/convert/mono_to_stereo.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::convert {
|
||||
class MonoToStereo : public Processor<float, stereo_t> {
|
||||
using base_type = Processor<float, stereo_t>;
|
||||
public:
|
||||
MonoToStereo() {}
|
||||
|
||||
MonoToStereo(stream<float>* in) { base_type::init(in); }
|
||||
|
||||
inline static int process(int count, const float* in, stereo_t* out) {
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out, in, in, count);
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
44
core/src/dsp/convert/real_to_complex.h
Normal file
44
core/src/dsp/convert/real_to_complex.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::convert {
|
||||
class RealToComplex : public Processor<float, complex_t> {
|
||||
using base_type = Processor<float, complex_t>;
|
||||
public:
|
||||
RealToComplex() {}
|
||||
|
||||
RealToComplex(stream<float>* in) { init(in); }
|
||||
|
||||
~RealToComplex() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
buffer::free(nullBuf);
|
||||
}
|
||||
|
||||
void init(stream<float>* in) {
|
||||
nullBuf = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||
buffer::clear(nullBuf, STREAM_BUFFER_SIZE);
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
inline int process(int count, const float* in, complex_t* out) {
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out, in, nullBuf, count);
|
||||
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:
|
||||
float* nullBuf;
|
||||
|
||||
};
|
||||
}
|
||||
30
core/src/dsp/convert/stereo_to_mono.h
Normal file
30
core/src/dsp/convert/stereo_to_mono.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::convert {
|
||||
class StereoToMono : public Processor<stereo_t, float> {
|
||||
using base_type = Processor<stereo_t, float>;
|
||||
public:
|
||||
StereoToMono() {}
|
||||
|
||||
StereoToMono(stream<stereo_t>* in) { base_type::init(in); }
|
||||
|
||||
inline int process(int count, const stereo_t* in, float* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = (in[i].l + in[i].r) / 2.0f;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/window.h>
|
||||
|
||||
namespace dsp {
|
||||
class IQCorrector : public generic_block<IQCorrector> {
|
||||
public:
|
||||
IQCorrector() {}
|
||||
|
||||
IQCorrector(stream<complex_t>* in, float rate) { init(in, rate); }
|
||||
|
||||
void init(stream<complex_t>* in, float rate) {
|
||||
_in = in;
|
||||
correctionRate = rate;
|
||||
offset.re = 0;
|
||||
offset.im = 0;
|
||||
generic_block<IQCorrector>::registerInput(_in);
|
||||
generic_block<IQCorrector>::registerOutput(&out);
|
||||
generic_block<IQCorrector>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<IQCorrector>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<IQCorrector>::ctrlMtx);
|
||||
generic_block<IQCorrector>::tempStop();
|
||||
generic_block<IQCorrector>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<IQCorrector>::registerInput(_in);
|
||||
generic_block<IQCorrector>::tempStart();
|
||||
}
|
||||
|
||||
void setCorrectionRate(float rate) {
|
||||
correctionRate = rate;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (bypass) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
|
||||
_in->flush();
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] = _in->readBuf[i] - offset;
|
||||
offset = offset + (out.writeBuf[i] * correctionRate);
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
// TEMPORARY FOR DEBUG PURPOSES
|
||||
bool bypass = false;
|
||||
complex_t offset;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
float correctionRate = 0.00001;
|
||||
|
||||
|
||||
};
|
||||
|
||||
class DCBlocker : public generic_block<DCBlocker> {
|
||||
public:
|
||||
DCBlocker() {}
|
||||
|
||||
DCBlocker(stream<float>* in, float rate) { init(in, rate); }
|
||||
|
||||
void init(stream<float>* in, float rate) {
|
||||
_in = in;
|
||||
correctionRate = rate;
|
||||
offset = 0;
|
||||
generic_block<DCBlocker>::registerInput(_in);
|
||||
generic_block<DCBlocker>::registerOutput(&out);
|
||||
generic_block<DCBlocker>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<DCBlocker>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<DCBlocker>::ctrlMtx);
|
||||
generic_block<DCBlocker>::tempStop();
|
||||
generic_block<DCBlocker>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<DCBlocker>::registerInput(_in);
|
||||
generic_block<DCBlocker>::tempStart();
|
||||
}
|
||||
|
||||
void setCorrectionRate(float rate) {
|
||||
correctionRate = rate;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (bypass) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
|
||||
_in->flush();
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] = _in->readBuf[i] - offset;
|
||||
offset = offset + (out.writeBuf[i] * correctionRate);
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
// TEMPORARY FOR DEBUG PURPOSES
|
||||
bool bypass = false;
|
||||
float offset;
|
||||
|
||||
private:
|
||||
stream<float>* _in;
|
||||
float correctionRate = 0.00001;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
77
core/src/dsp/correction/dc_blocker.h
Normal file
77
core/src/dsp/correction/dc_blocker.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::correction {
|
||||
template<class T>
|
||||
class DCBlocker : public Processor<T, T> {
|
||||
using base_type = Processor<T, T>;
|
||||
public:
|
||||
DCBlocker() {}
|
||||
|
||||
DCBlocker(stream<T>* in, double rate) { init(in, rate); }
|
||||
|
||||
DCBlocker(stream<T>* in, double rate, double samplerate) { init(in, rate, samplerate); }
|
||||
|
||||
void init(stream<T>* in, double rate) {
|
||||
_rate = rate;
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
offset = 0.0f;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
offset = { 0.0f, 0.0f };
|
||||
}
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void init(stream<T>* in, double rate, double samplerate) {
|
||||
init(in, rate / samplerate);
|
||||
}
|
||||
|
||||
void setRate(double rate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_rate = rate;
|
||||
}
|
||||
|
||||
void setRate(double rate, double samplerate) {
|
||||
setRate(rate / samplerate);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
offset = 0.0f;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
offset = { 0.0f, 0.0f };
|
||||
}
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
// TODO: Add back the const
|
||||
int process(int count, T* in, T* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = in[i] - offset;
|
||||
offset += out[i] * _rate;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
virtual 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 _rate;
|
||||
T offset;
|
||||
};
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/window.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class HalfDecimator : public generic_block<HalfDecimator<T>> {
|
||||
public:
|
||||
HalfDecimator() {}
|
||||
|
||||
HalfDecimator(stream<T>* in, dsp::filter_window::generic_window* window) { init(in, window); }
|
||||
|
||||
~HalfDecimator() {
|
||||
if (!generic_block<HalfDecimator<T>>::_block_init) { return; }
|
||||
generic_block<HalfDecimator<T>>::stop();
|
||||
volk_free(buffer);
|
||||
volk_free(taps);
|
||||
generic_block<HalfDecimator<T>>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<T>* in, dsp::filter_window::generic_window* window) {
|
||||
_in = in;
|
||||
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
window->createTaps(taps, tapCount);
|
||||
|
||||
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
|
||||
bufStart = &buffer[tapCount];
|
||||
generic_block<HalfDecimator<T>>::registerInput(_in);
|
||||
generic_block<HalfDecimator<T>>::registerOutput(&out);
|
||||
generic_block<HalfDecimator<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<HalfDecimator<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<HalfDecimator<T>>::ctrlMtx);
|
||||
generic_block<HalfDecimator<T>>::tempStop();
|
||||
generic_block<HalfDecimator<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<HalfDecimator<T>>::registerInput(_in);
|
||||
generic_block<HalfDecimator<T>>::tempStart();
|
||||
}
|
||||
|
||||
void updateWindow(dsp::filter_window::generic_window* window) {
|
||||
assert(generic_block<HalfDecimator<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<HalfDecimator<T>>::ctrlMtx);
|
||||
std::lock_guard<std::mutex> lck2(bufMtx);
|
||||
_window = window;
|
||||
volk_free(taps);
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
bufStart = &buffer[tapCount];
|
||||
window->createTaps(taps, tapCount);
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
memcpy(bufStart, _in->readBuf, count * sizeof(T));
|
||||
_in->flush();
|
||||
|
||||
int inIndex = _inIndex;
|
||||
int outIndex = 0;
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
while (inIndex < count) {
|
||||
volk_32f_x2_dot_prod_32f((float*)&out.writeBuf[outIndex], (float*)&buffer[inIndex+1], taps, tapCount);
|
||||
inIndex += 2;
|
||||
outIndex++;
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
while (inIndex < count) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex], (lv_32fc_t*)&buffer[inIndex+1], taps, tapCount);
|
||||
inIndex += 2;
|
||||
outIndex++;
|
||||
}
|
||||
}
|
||||
_inIndex = inIndex - count;
|
||||
|
||||
if (!out.swap(outIndex)) { return -1; }
|
||||
|
||||
memmove(buffer, &buffer[count], tapCount * sizeof(T));
|
||||
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
stream<T>* _in;
|
||||
|
||||
dsp::filter_window::generic_window* _window;
|
||||
std::mutex bufMtx;
|
||||
|
||||
T* bufStart;
|
||||
T* buffer;
|
||||
int tapCount;
|
||||
float* taps;
|
||||
int _inIndex = 0;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define DSP_SIGN(n) ((n) >= 0)
|
||||
#define DSP_STEP(n) (((n) > 0.0f) ? 1.0f : -1.0f)
|
||||
|
||||
namespace dsp {
|
||||
class Deframer : public generic_block<Deframer> {
|
||||
public:
|
||||
Deframer() {}
|
||||
|
||||
Deframer(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) { init(in, frameLen, syncWord, syncLen); }
|
||||
|
||||
~Deframer() {
|
||||
if (!generic_block<Deframer>::_block_init) { return; }
|
||||
generic_block<Deframer>::stop();
|
||||
generic_block<Deframer>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) {
|
||||
_in = in;
|
||||
_frameLen = frameLen;
|
||||
_syncword = new uint8_t[syncLen];
|
||||
_syncLen = syncLen;
|
||||
memcpy(_syncword, syncWord, syncLen);
|
||||
|
||||
buffer = new uint8_t[STREAM_BUFFER_SIZE + syncLen];
|
||||
memset(buffer, 0, syncLen);
|
||||
bufferStart = buffer + syncLen;
|
||||
|
||||
generic_block<Deframer>::registerInput(_in);
|
||||
generic_block<Deframer>::registerOutput(&out);
|
||||
generic_block<Deframer>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<Deframer>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Deframer>::ctrlMtx);
|
||||
generic_block<Deframer>::tempStop();
|
||||
generic_block<Deframer>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Deframer>::registerInput(_in);
|
||||
generic_block<Deframer>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Copy data into work buffer
|
||||
memcpy(bufferStart, _in->readBuf, count - 1);
|
||||
|
||||
// Iterate through all symbols
|
||||
for (int i = 0; i < count;) {
|
||||
|
||||
// If already in the process of reading bits
|
||||
if (bitsRead >= 0) {
|
||||
if ((bitsRead % 8) == 0) { out.writeBuf[bitsRead / 8] = 0; }
|
||||
out.writeBuf[bitsRead / 8] |= (buffer[i] << (7 - (bitsRead % 8)));
|
||||
i++;
|
||||
bitsRead++;
|
||||
|
||||
if (bitsRead >= _frameLen) {
|
||||
if (!out.swap((bitsRead / 8) + ((bitsRead % 8) > 0))) { return -1; }
|
||||
bitsRead = -1;
|
||||
if (allowSequential) { nextBitIsStartOfFrame = true; }
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Else, check for a header
|
||||
else if (memcmp(buffer + i, _syncword, _syncLen) == 0) {
|
||||
bitsRead = 0;
|
||||
//printf("Frame found!\n");
|
||||
badFrameCount = 0;
|
||||
continue;
|
||||
}
|
||||
else if (nextBitIsStartOfFrame) {
|
||||
nextBitIsStartOfFrame = false;
|
||||
|
||||
// try to save
|
||||
if (badFrameCount < 5) {
|
||||
badFrameCount++;
|
||||
//printf("Frame found!\n");
|
||||
bitsRead = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else { i++; }
|
||||
|
||||
nextBitIsStartOfFrame = false;
|
||||
|
||||
}
|
||||
|
||||
// Keep last _syncLen4 symbols
|
||||
memcpy(buffer, &_in->readBuf[count - _syncLen], _syncLen);
|
||||
|
||||
//printf("Block processed\n");
|
||||
callcount++;
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
bool allowSequential = true;
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
private:
|
||||
uint8_t* buffer;
|
||||
uint8_t* bufferStart;
|
||||
uint8_t* _syncword;
|
||||
int count;
|
||||
int _frameLen;
|
||||
int _syncLen;
|
||||
int bitsRead = -1;
|
||||
|
||||
int badFrameCount = 5;
|
||||
bool nextBitIsStartOfFrame = false;
|
||||
|
||||
int callcount = 0;
|
||||
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
inline int MachesterHammingDistance(float* data, uint8_t* syncBits, int n) {
|
||||
int dist = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if ((data[(2*i) + 1] > data[2*i]) != syncBits[i]) { dist++; }
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
inline int HammingDistance(uint8_t* data, uint8_t* syncBits, int n) {
|
||||
int dist = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (data[i] != syncBits[i]) { dist++; }
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
class ManchesterDeframer : public generic_block<ManchesterDeframer> {
|
||||
public:
|
||||
ManchesterDeframer() {}
|
||||
|
||||
ManchesterDeframer(stream<float>* in, int frameLen, uint8_t* syncWord, int syncLen) { init(in, frameLen, syncWord, syncLen); }
|
||||
|
||||
void init(stream<float>* in, int frameLen, uint8_t* syncWord, int syncLen) {
|
||||
_in = in;
|
||||
_frameLen = frameLen;
|
||||
_syncword = new uint8_t[syncLen];
|
||||
_syncLen = syncLen;
|
||||
memcpy(_syncword, syncWord, syncLen);
|
||||
|
||||
buffer = new float[STREAM_BUFFER_SIZE + (syncLen * 2)];
|
||||
memset(buffer, 0, syncLen * 2 * sizeof(float));
|
||||
bufferStart = &buffer[syncLen * 2];
|
||||
|
||||
generic_block<ManchesterDeframer>::registerInput(_in);
|
||||
generic_block<ManchesterDeframer>::registerOutput(&out);
|
||||
generic_block<ManchesterDeframer>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<ManchesterDeframer>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ManchesterDeframer>::ctrlMtx);
|
||||
generic_block<ManchesterDeframer>::tempStop();
|
||||
generic_block<ManchesterDeframer>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ManchesterDeframer>::registerInput(_in);
|
||||
generic_block<ManchesterDeframer>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int readable;
|
||||
|
||||
// Copy data into work buffer
|
||||
memcpy(bufferStart, _in->readBuf, (count - 1) * sizeof(float));
|
||||
|
||||
// Iterate through all symbols
|
||||
for (int i = 0; i < count;) {
|
||||
|
||||
// If already in the process of reading bits
|
||||
if (bitsRead >= 0) {
|
||||
readable = std::min<int>(count - i, _frameLen - bitsRead);
|
||||
memcpy(&out.writeBuf[bitsRead], &buffer[i], readable * sizeof(float));
|
||||
bitsRead += readable;
|
||||
i += readable;
|
||||
if (bitsRead >= _frameLen) {
|
||||
out.swap(_frameLen);
|
||||
bitsRead = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Else, check for a header
|
||||
if (MachesterHammingDistance(&buffer[i], _syncword, _syncLen) <= 2) {
|
||||
bitsRead = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
}
|
||||
|
||||
// Keep last _syncLen symbols
|
||||
memcpy(buffer, &_in->readBuf[count - (_syncLen * 2)], _syncLen * 2 * sizeof(float));
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
float* buffer;
|
||||
float* bufferStart;
|
||||
uint8_t* _syncword;
|
||||
int count;
|
||||
int _frameLen;
|
||||
int _syncLen;
|
||||
int bitsRead = -1;
|
||||
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
class SymbolDeframer : public generic_block<SymbolDeframer> {
|
||||
public:
|
||||
SymbolDeframer() {}
|
||||
|
||||
SymbolDeframer(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) { init(in, frameLen, syncWord, syncLen); }
|
||||
|
||||
void init(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) {
|
||||
_in = in;
|
||||
_frameLen = frameLen;
|
||||
_syncword = new uint8_t[syncLen];
|
||||
_syncLen = syncLen;
|
||||
memcpy(_syncword, syncWord, syncLen);
|
||||
|
||||
buffer = new uint8_t[STREAM_BUFFER_SIZE + syncLen];
|
||||
memset(buffer, 0, syncLen);
|
||||
bufferStart = &buffer[syncLen];
|
||||
|
||||
generic_block<SymbolDeframer>::registerInput(_in);
|
||||
generic_block<SymbolDeframer>::registerOutput(&out);
|
||||
generic_block<SymbolDeframer>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<SymbolDeframer>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<SymbolDeframer>::ctrlMtx);
|
||||
generic_block<SymbolDeframer>::tempStop();
|
||||
generic_block<SymbolDeframer>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<SymbolDeframer>::registerInput(_in);
|
||||
generic_block<SymbolDeframer>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int readable;
|
||||
|
||||
// Copy data into work buffer
|
||||
memcpy(bufferStart, _in->readBuf, count - 1);
|
||||
|
||||
// Iterate through all symbols
|
||||
for (int i = 0; i < count;) {
|
||||
|
||||
// If already in the process of reading bits
|
||||
if (bitsRead >= 0) {
|
||||
readable = std::min<int>(count - i, _frameLen - bitsRead);
|
||||
memcpy(&out.writeBuf[bitsRead], &buffer[i], readable);
|
||||
bitsRead += readable;
|
||||
i += readable;
|
||||
if (bitsRead >= _frameLen) {
|
||||
out.swap(_frameLen);
|
||||
bitsRead = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Else, check for a header
|
||||
if (HammingDistance(&buffer[i], _syncword, _syncLen) <= 2) {
|
||||
bitsRead = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
}
|
||||
|
||||
// Keep last _syncLen symbols
|
||||
memcpy(buffer, &_in->readBuf[count - _syncLen], _syncLen);
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
private:
|
||||
uint8_t* buffer;
|
||||
uint8_t* bufferStart;
|
||||
uint8_t* _syncword;
|
||||
int count;
|
||||
int _frameLen;
|
||||
int _syncLen;
|
||||
int bitsRead = -1;
|
||||
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ManchesterDecoder : public generic_block<ManchesterDecoder> {
|
||||
public:
|
||||
ManchesterDecoder() {}
|
||||
|
||||
ManchesterDecoder(stream<float>* in, bool inverted) { init(in, inverted); }
|
||||
|
||||
void init(stream<float>* in, bool inverted) {
|
||||
_in = in;
|
||||
_inverted = inverted;
|
||||
generic_block<ManchesterDecoder>::registerInput(_in);
|
||||
generic_block<ManchesterDecoder>::registerOutput(&out);
|
||||
generic_block<ManchesterDecoder>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<ManchesterDecoder>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ManchesterDecoder>::ctrlMtx);
|
||||
generic_block<ManchesterDecoder>::tempStop();
|
||||
generic_block<ManchesterDecoder>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ManchesterDecoder>::registerInput(_in);
|
||||
generic_block<ManchesterDecoder>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (_inverted) {
|
||||
for (int i = 0; i < count; i += 2) {
|
||||
out.writeBuf[i/2] = (_in->readBuf[i + 1] < _in->readBuf[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < count; i += 2) {
|
||||
out.writeBuf[i/2] = (_in->readBuf[i + 1] > _in->readBuf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
out.swap(count / 2);
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
private:
|
||||
stream<float>* _in;
|
||||
bool _inverted;
|
||||
|
||||
};
|
||||
|
||||
class BitPacker : public generic_block<BitPacker> {
|
||||
public:
|
||||
BitPacker() {}
|
||||
|
||||
BitPacker(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
|
||||
generic_block<BitPacker>::registerInput(_in);
|
||||
generic_block<BitPacker>::registerOutput(&out);
|
||||
generic_block<BitPacker>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<BitPacker>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<BitPacker>::ctrlMtx);
|
||||
generic_block<BitPacker>::tempStop();
|
||||
generic_block<BitPacker>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<BitPacker>::registerInput(_in);
|
||||
generic_block<BitPacker>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if ((i % 8) == 0) { out.writeBuf[i / 8] = 0; }
|
||||
out.writeBuf[i / 8] |= (_in->readBuf[i] & 1) << (7 - (i % 8));
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
out.swap((count / 8) + (((count % 8) == 0) ? 0 : 1));
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
private:
|
||||
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
160
core/src/dsp/demod/am.h
Normal file
160
core/src/dsp/demod/am.h
Normal file
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../loop/agc.h"
|
||||
#include "../correction/dc_blocker.h"
|
||||
#include "../convert/mono_to_stereo.h"
|
||||
#include "../filter/fir.h"
|
||||
#include "../taps/low_pass.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
template <class T>
|
||||
class AM : public Processor<dsp::complex_t, T> {
|
||||
using base_type = Processor<dsp::complex_t, T>;
|
||||
public:
|
||||
enum AGCMode {
|
||||
CARRIER,
|
||||
AUDIO,
|
||||
};
|
||||
|
||||
AM() {}
|
||||
|
||||
AM(stream<complex_t>* in, AGCMode agcMode, double bandwidth, double agcAttack, double agcDecay, double dcBlockRate, double samplerate) { init(in, agcMode, bandwidth, agcAttack, agcDecay, dcBlockRate, samplerate); }
|
||||
|
||||
~AM() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
taps::free(lpfTaps);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, AGCMode agcMode, double bandwidth, double agcAttack, double agcDecay, double dcBlockRate, double samplerate) {
|
||||
_agcMode = agcMode;
|
||||
_bandwidth = bandwidth;
|
||||
_samplerate = samplerate;
|
||||
|
||||
carrierAgc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
|
||||
audioAgc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
|
||||
dcBlock.init(NULL, dcBlockRate);
|
||||
lpfTaps = taps::lowPass(bandwidth / 2.0, (bandwidth / 2.0) * 0.1, samplerate);
|
||||
lpf.init(NULL, lpfTaps);
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
audioAgc.out.free();
|
||||
}
|
||||
dcBlock.out.free();
|
||||
lpf.out.free();
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setAGCMode(AGCMode agcMode) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_agcMode = agcMode;
|
||||
reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
if (bandwidth == _bandwidth) { return; }
|
||||
_bandwidth = bandwidth;
|
||||
std::lock_guard<std::mutex> lck2(lpfMtx);
|
||||
taps::free(lpfTaps);
|
||||
lpfTaps = taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
|
||||
lpf.setTaps(lpfTaps);
|
||||
}
|
||||
|
||||
void setAGCAttack(double attack) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
carrierAgc.setAttack(attack);
|
||||
audioAgc.setAttack(attack);
|
||||
}
|
||||
|
||||
void setAGCDecay(double decay) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
carrierAgc.setDecay(decay);
|
||||
audioAgc.setDecay(decay);
|
||||
}
|
||||
|
||||
void setDCBlockRate(double rate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
dcBlock.setRate(rate);
|
||||
}
|
||||
|
||||
// TODO: Implement setSamplerate
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
carrierAgc.reset();
|
||||
audioAgc.reset();
|
||||
dcBlock.reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
int process(int count, complex_t* in, T* out) {
|
||||
// Apply carrier AGC if needed
|
||||
if (_agcMode == AGCMode::CARRIER) {
|
||||
carrierAgc.process(count, in, carrierAgc.out.writeBuf);
|
||||
in = carrierAgc.out.writeBuf;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
volk_32fc_magnitude_32f(out, (lv_32fc_t*)in, count);
|
||||
dcBlock.process(count, out, out);
|
||||
if (_agcMode == AGCMode::AUDIO) {
|
||||
audioAgc.process(count, out, out);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(lpfMtx);
|
||||
lpf.process(count, out, out);
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
volk_32fc_magnitude_32f(audioAgc.out.writeBuf, (lv_32fc_t*)in, count);
|
||||
dcBlock.process(count, audioAgc.out.writeBuf, audioAgc.out.writeBuf);
|
||||
if (_agcMode == AGCMode::AUDIO) {
|
||||
audioAgc.process(count, audioAgc.out.writeBuf, audioAgc.out.writeBuf);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(lpfMtx);
|
||||
lpf.process(count, audioAgc.out.writeBuf, audioAgc.out.writeBuf);
|
||||
}
|
||||
convert::MonoToStereo::process(count, audioAgc.out.writeBuf, out);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
AGCMode _agcMode;
|
||||
|
||||
double _samplerate;
|
||||
double _bandwidth;
|
||||
|
||||
loop::AGC<complex_t> carrierAgc;
|
||||
loop::AGC<float> audioAgc;
|
||||
correction::DCBlocker<float> dcBlock;
|
||||
tap<float> lpfTaps;
|
||||
filter::FIR<float, float> lpf;
|
||||
std::mutex lpfMtx;
|
||||
|
||||
};
|
||||
}
|
||||
259
core/src/dsp/demod/broadcast_fm.h
Normal file
259
core/src/dsp/demod/broadcast_fm.h
Normal file
@@ -0,0 +1,259 @@
|
||||
#pragma once
|
||||
#include "quadrature.h"
|
||||
#include "../taps/low_pass.h"
|
||||
#include "../taps/band_pass.h"
|
||||
#include "../filter/fir.h"
|
||||
#include "../loop/pll.h"
|
||||
#include "../convert/l_r_to_stereo.h"
|
||||
#include "../convert/real_to_complex.h"
|
||||
#include "../convert/complex_to_real.h"
|
||||
#include "../math/conjugate.h"
|
||||
#include "../math/delay.h"
|
||||
#include "../math/multiply.h"
|
||||
#include "../math/add.h"
|
||||
#include "../math/subtract.h"
|
||||
#include "../multirate/rational_resampler.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
class BroadcastFM : public Processor<complex_t, stereo_t> {
|
||||
using base_type = Processor<complex_t, stereo_t>;
|
||||
public:
|
||||
BroadcastFM() {}
|
||||
|
||||
BroadcastFM(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true, bool rdsOut = false) { init(in, deviation, samplerate, stereo, lowPass); }
|
||||
|
||||
~BroadcastFM() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
buffer::free(lmr);
|
||||
buffer::free(l);
|
||||
buffer::free(r);
|
||||
taps::free(pilotFirTaps);
|
||||
taps::free(audioFirTaps);
|
||||
}
|
||||
|
||||
virtual void init(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true, bool rdsOut = false) {
|
||||
_deviation = deviation;
|
||||
_samplerate = samplerate;
|
||||
_stereo = stereo;
|
||||
_lowPass = lowPass;
|
||||
_rdsOut = rdsOut;
|
||||
|
||||
demod.init(NULL, _deviation, _samplerate);
|
||||
pilotFirTaps = taps::bandPass<complex_t>(18750.0, 19250.0, 3000.0, _samplerate, true);
|
||||
pilotFir.init(NULL, pilotFirTaps);
|
||||
rtoc.init(NULL);
|
||||
pilotPLL.init(NULL, 25000.0 / _samplerate, 0.0, math::hzToRads(19000.0, _samplerate), math::hzToRads(18750.0, _samplerate), math::hzToRads(19250.0, _samplerate));
|
||||
lprDelay.init(NULL, ((pilotFirTaps.size - 1) / 2) + 1);
|
||||
lmrDelay.init(NULL, ((pilotFirTaps.size - 1) / 2) + 1);
|
||||
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
|
||||
alFir.init(NULL, audioFirTaps);
|
||||
arFir.init(NULL, audioFirTaps);
|
||||
xlator.init(NULL, -57000.0, samplerate);
|
||||
rdsResamp.init(NULL, samplerate, 5000.0);
|
||||
|
||||
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||
l = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||
|
||||
lprDelay.out.free();
|
||||
arFir.out.free();
|
||||
alFir.out.free();
|
||||
xlator.out.free();
|
||||
rdsResamp.out.free();
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setDeviation(double deviation) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_deviation = deviation;
|
||||
demod.setDeviation(_deviation, _samplerate);
|
||||
}
|
||||
|
||||
void setSamplerate(double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_samplerate = samplerate;
|
||||
|
||||
demod.setDeviation(_deviation, _samplerate);
|
||||
taps::free(pilotFirTaps);
|
||||
pilotFirTaps = taps::bandPass<complex_t>(18750.0, 19250.0, 3000.0, samplerate, true);
|
||||
pilotFir.setTaps(pilotFirTaps);
|
||||
|
||||
pilotPLL.setFrequencyLimits(math::hzToRads(18750.0, _samplerate), math::hzToRads(19250.0, _samplerate));
|
||||
pilotPLL.setInitialFreq(math::hzToRads(19000.0, _samplerate));
|
||||
lprDelay.setDelay(((pilotFirTaps.size - 1) / 2) + 1);
|
||||
lmrDelay.setDelay(((pilotFirTaps.size - 1) / 2) + 1);
|
||||
|
||||
taps::free(audioFirTaps);
|
||||
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
|
||||
alFir.setTaps(audioFirTaps);
|
||||
arFir.setTaps(audioFirTaps);
|
||||
|
||||
xlator.setOffset(-57000.0, samplerate);
|
||||
rdsResamp.setInSamplerate(samplerate);
|
||||
|
||||
reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setStereo(bool stereo) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_stereo = stereo;
|
||||
reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setLowPass(bool lowPass) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_lowPass = lowPass;
|
||||
reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setRDSOut(bool rdsOut) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_rdsOut = rdsOut;
|
||||
reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
demod.reset();
|
||||
pilotFir.reset();
|
||||
pilotPLL.reset();
|
||||
lprDelay.reset();
|
||||
lmrDelay.reset();
|
||||
alFir.reset();
|
||||
arFir.reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, complex_t* rdsout = NULL) {
|
||||
// Demodulate
|
||||
demod.process(count, in, demod.out.writeBuf);
|
||||
if (_stereo) {
|
||||
// Convert to complex
|
||||
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Filter out pilot and run through PLL
|
||||
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
|
||||
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
|
||||
|
||||
// Delay
|
||||
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
||||
lmrDelay.process(count, rtoc.out.writeBuf, lmrDelay.out.writeBuf);
|
||||
|
||||
// conjugate PLL output to down convert twice the L-R signal
|
||||
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
|
||||
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
|
||||
|
||||
// Do RDS demod
|
||||
if (_rdsOut) {
|
||||
// Translate to 0Hz
|
||||
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Resample to the output samplerate
|
||||
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||
}
|
||||
|
||||
// Convert output back to real for further processing
|
||||
convert::ComplexToReal::process(count, lmrDelay.out.writeBuf, lmr);
|
||||
|
||||
// Amplify by 2x
|
||||
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
|
||||
|
||||
// Do L = (L+R) + (L-R), R = (L+R) - (L-R)
|
||||
math::Add<float>::process(count, demod.out.writeBuf, lmr, l);
|
||||
math::Subtract<float>::process(count, demod.out.writeBuf, lmr, r);
|
||||
|
||||
// Filter if needed
|
||||
if (_lowPass) {
|
||||
alFir.process(count, l, l);
|
||||
arFir.process(count, r, r);
|
||||
}
|
||||
|
||||
// Interleave into stereo
|
||||
convert::LRToStereo::process(count, l, r, out);
|
||||
}
|
||||
else {
|
||||
// Process RDS if needed. Note: find a way to not have to copy half the code from the stereo demod
|
||||
if (_rdsOut) {
|
||||
// Convert to complex
|
||||
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Translate to 0Hz
|
||||
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||
|
||||
// Resample to the output samplerate
|
||||
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||
}
|
||||
|
||||
// Filter if needed
|
||||
if (_lowPass) {
|
||||
alFir.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
||||
}
|
||||
|
||||
// Interleave raw MPX to stereo
|
||||
convert::LRToStereo::process(count, demod.out.writeBuf, demod.out.writeBuf, out);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int rdsOutCount = 0;
|
||||
process(count, base_type::_in->readBuf, base_type::out.writeBuf, rdsOutCount, rdsOut.writeBuf);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!base_type::out.swap(count)) { return -1; }
|
||||
if (rdsOutCount && _rdsOut) {
|
||||
if (!rdsOut.swap(rdsOutCount)) { return -1; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> rdsOut;
|
||||
|
||||
protected:
|
||||
double _deviation;
|
||||
double _samplerate;
|
||||
bool _stereo;
|
||||
bool _lowPass;
|
||||
bool _rdsOut;
|
||||
|
||||
Quadrature demod;
|
||||
tap<complex_t> pilotFirTaps;
|
||||
filter::FIR<complex_t, complex_t> pilotFir;
|
||||
convert::RealToComplex rtoc;
|
||||
channel::FrequencyXlator xlator;
|
||||
loop::PLL pilotPLL;
|
||||
math::Delay<float> lprDelay;
|
||||
math::Delay<complex_t> lmrDelay;
|
||||
tap<float> audioFirTaps;
|
||||
filter::FIR<float, float> arFir;
|
||||
filter::FIR<float, float> alFir;
|
||||
multirate::RationalResampler<dsp::complex_t> rdsResamp;
|
||||
|
||||
float* lmr;
|
||||
float* l;
|
||||
float* r;
|
||||
|
||||
};
|
||||
}
|
||||
90
core/src/dsp/demod/cw.h
Normal file
90
core/src/dsp/demod/cw.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../channel/frequency_xlator.h"
|
||||
#include "../convert/complex_to_real.h"
|
||||
#include "../loop/agc.h"
|
||||
#include "../convert/mono_to_stereo.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
template <class T>
|
||||
class CW : public Processor<complex_t, T> {
|
||||
using base_type = Processor<complex_t, T>;
|
||||
public:
|
||||
CW() {}
|
||||
|
||||
CW(stream<complex_t>* in, double tone, double agcAttack, double agcDecay, double samplerate) { init(in, tone, agcAttack, agcDecay, samplerate); }
|
||||
|
||||
void init(stream<complex_t>* in, double tone, double agcAttack, double agcDecay, double samplerate) {
|
||||
_tone = tone;
|
||||
_samplerate = samplerate;
|
||||
|
||||
xlator.init(NULL, tone, samplerate);
|
||||
agc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
agc.out.free();
|
||||
}
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setTone(double tone) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_tone = tone;
|
||||
xlator.setOffset(_tone, _samplerate);
|
||||
}
|
||||
|
||||
void setAGCAttack(double attack) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
agc.setAttack(attack);
|
||||
}
|
||||
|
||||
void setAGCDecay(double decay) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
agc.setDecay(decay);
|
||||
}
|
||||
|
||||
void setSamplerate(double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_samplerate = samplerate;
|
||||
xlator.setOffset(_tone, _samplerate);
|
||||
}
|
||||
|
||||
inline int process(int count, const complex_t* in, T* out) {
|
||||
xlator.process(count, in, xlator.out.writeBuf);
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
dsp::convert::ComplexToReal::process(count, xlator.out.writeBuf, out);
|
||||
agc.process(count, out, out);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
dsp::convert::ComplexToReal::process(count, xlator.out.writeBuf, agc.out.writeBuf);
|
||||
agc.process(count, agc.out.writeBuf, agc.out.writeBuf);
|
||||
convert::MonoToStereo::process(count, agc.out.writeBuf, out);
|
||||
}
|
||||
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 _tone;
|
||||
double _samplerate;
|
||||
|
||||
dsp::channel::FrequencyXlator xlator;
|
||||
dsp::loop::AGC<float> agc;
|
||||
|
||||
};
|
||||
}
|
||||
163
core/src/dsp/demod/fm.h
Normal file
163
core/src/dsp/demod/fm.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "quadrature.h"
|
||||
#include "../filter/fir.h"
|
||||
#include "../taps/low_pass.h"
|
||||
#include "../taps/high_pass.h"
|
||||
#include "../taps/band_pass.h"
|
||||
#include "../convert/mono_to_stereo.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
template <class T>
|
||||
class FM : public dsp::Processor<dsp::complex_t, T> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, T>;
|
||||
public:
|
||||
FM() {}
|
||||
|
||||
FM(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) { init(in, samplerate, bandwidth, lowPass); }
|
||||
|
||||
~FM() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::taps::free(filterTaps);
|
||||
}
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass, bool highPass) {
|
||||
_samplerate = samplerate;
|
||||
_bandwidth = bandwidth;
|
||||
_lowPass = lowPass;
|
||||
_highPass = highPass;
|
||||
|
||||
demod.init(NULL, bandwidth / 2.0, _samplerate);
|
||||
loadDummyTaps();
|
||||
fir.init(NULL, filterTaps);
|
||||
|
||||
// Initialize taps
|
||||
updateFilter(lowPass, highPass);
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
demod.out.free();
|
||||
}
|
||||
fir.out.free();
|
||||
|
||||
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;
|
||||
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
||||
updateFilter(_lowPass, _highPass);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
if (bandwidth == _bandwidth) { return; }
|
||||
_bandwidth = bandwidth;
|
||||
demod.setDeviation(_bandwidth / 2.0, _samplerate);
|
||||
updateFilter(_lowPass, _highPass);
|
||||
}
|
||||
|
||||
void setLowPass(bool lowPass) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
updateFilter(lowPass, _highPass);
|
||||
}
|
||||
|
||||
void setHighPass(bool highPass) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
updateFilter(_lowPass, highPass);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
demod.reset();
|
||||
fir.reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, dsp::complex_t* in, T* out) {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
demod.process(count, in, out);
|
||||
if (filtering) {
|
||||
std::lock_guard<std::mutex> lck(filterMtx);
|
||||
fir.process(count, out, out);
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
demod.process(count, in, demod.out.writeBuf);
|
||||
if (filtering) {
|
||||
std::lock_guard<std::mutex> lck(filterMtx);
|
||||
fir.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
||||
}
|
||||
convert::MonoToStereo::process(count, demod.out.writeBuf, out);
|
||||
}
|
||||
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:
|
||||
void updateFilter(bool lowPass, bool highPass) {
|
||||
std::lock_guard<std::mutex> lck(filterMtx);
|
||||
|
||||
// Update values
|
||||
_lowPass = lowPass;
|
||||
_highPass = highPass;
|
||||
filtering = (lowPass || highPass);
|
||||
|
||||
// Free filter taps
|
||||
dsp::taps::free(filterTaps);
|
||||
|
||||
// Generate filter depending on low and high pass settings
|
||||
if (_lowPass && _highPass) {
|
||||
filterTaps = dsp::taps::bandPass<float>(300.0, _bandwidth / 2.0, 100.0, _samplerate);
|
||||
}
|
||||
else if (_highPass) {
|
||||
filterTaps = dsp::taps::highPass(300.0, 100.0, _samplerate);
|
||||
}
|
||||
else if (_lowPass) {
|
||||
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 _bandwidth;
|
||||
bool _lowPass;
|
||||
bool _highPass;
|
||||
bool filtering;
|
||||
|
||||
Quadrature demod;
|
||||
tap<float> filterTaps;
|
||||
filter::FIR<float, float> fir;
|
||||
std::mutex filterMtx;
|
||||
};
|
||||
}
|
||||
163
core/src/dsp/demod/gfsk.h
Normal file
163
core/src/dsp/demod/gfsk.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
#include "quadrature.h"
|
||||
#include "../taps/root_raised_cosine.h"
|
||||
#include "../filter/fir.h"
|
||||
#include "../clock_recovery/mm.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
// Note: I don't like how this demodulator reuses 90% of the code from the PSK demod. Same will be for the PM demod...
|
||||
class GFSK : public Processor<complex_t, float> {
|
||||
using base_type = Processor<complex_t, float>;
|
||||
public:
|
||||
GFSK() {}
|
||||
|
||||
GFSK(stream<complex_t>* in, double symbolrate, double samplerate, double deviation, int rrcTapCount, double rrcBeta, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
init(in, symbolrate, samplerate, deviation, rrcTapCount, rrcBeta, omegaGain, muGain);
|
||||
}
|
||||
|
||||
~GFSK() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
taps::free(rrcTaps);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, double symbolrate, double samplerate, double deviation, int rrcTapCount, double rrcBeta, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
_symbolrate = symbolrate;
|
||||
_samplerate = samplerate;
|
||||
_deviation = deviation;
|
||||
_rrcTapCount = rrcTapCount;
|
||||
_rrcBeta = rrcBeta;
|
||||
|
||||
demod.init(NULL, _deviation, _samplerate);
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.init(NULL, rrcTaps);
|
||||
recov.init(NULL, _samplerate / _symbolrate, omegaGain, muGain, omegaRelLimit);
|
||||
|
||||
demod.out.free();
|
||||
rrc.out.free();
|
||||
recov.out.free();
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setSymbolrate(double symbolrate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_symbolrate = symbolrate;
|
||||
taps::free(rrcTaps);
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.setTaps(rrcTaps);
|
||||
recov.setOmega(_samplerate / _symbolrate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setSamplerate(double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_samplerate = samplerate;
|
||||
demod.setDeviation(_deviation, _samplerate);
|
||||
taps::free(rrcTaps);
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.setTaps(rrcTaps);
|
||||
recov.setOmega(_samplerate / _symbolrate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setDeviation(double deviation) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_deviation = deviation;
|
||||
demod.setDeviation(_deviation, _samplerate);
|
||||
}
|
||||
|
||||
void setRRCParams(int rrcTapCount, double rrcBeta) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_rrcTapCount = rrcTapCount;
|
||||
_rrcBeta = rrcBeta;
|
||||
taps::free(rrcTaps);
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.setTaps(rrcTaps);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setRRCTapCount(int rrcTapCount) {
|
||||
setRRCParams(rrcTapCount, _rrcBeta);
|
||||
}
|
||||
|
||||
void setRRCBeta(int rrcBeta) {
|
||||
setRRCParams(_rrcTapCount, rrcBeta);
|
||||
}
|
||||
|
||||
void setMMParams(double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setOmegaGain(omegaGain);
|
||||
recov.setMuGain(muGain);
|
||||
recov.setOmegaRelLimit(omegaRelLimit);
|
||||
}
|
||||
|
||||
void setOmegaGain(double omegaGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setOmegaGain(omegaGain);
|
||||
}
|
||||
|
||||
void setMuGain(double muGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setMuGain(muGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(double omegaRelLimit) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setOmegaRelLimit(omegaRelLimit);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
demod.reset();
|
||||
rrc.reset();
|
||||
recov.reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, complex_t* in, float* out) {
|
||||
demod.process(count, in, out);
|
||||
rrc.process(count, out, out);
|
||||
return recov.process(count, out, out);
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
protected:
|
||||
double _symbolrate;
|
||||
double _samplerate;
|
||||
double _deviation;
|
||||
int _rrcTapCount;
|
||||
double _rrcBeta;
|
||||
|
||||
Quadrature demod;
|
||||
tap<float> rrcTaps;
|
||||
filter::FIR<float, float> rrc;
|
||||
clock_recovery::MM<float> recov;
|
||||
};
|
||||
}
|
||||
171
core/src/dsp/demod/psk.h
Normal file
171
core/src/dsp/demod/psk.h
Normal file
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
#include "../taps/root_raised_cosine.h"
|
||||
#include "../filter/fir.h"
|
||||
#include "../loop/fast_agc.h"
|
||||
#include "../loop/costas.h"
|
||||
#include "../clock_recovery/mm.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
template<int ORDER>
|
||||
class PSK : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t>;
|
||||
public:
|
||||
PSK() {}
|
||||
|
||||
PSK(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, omegaGain, muGain);
|
||||
}
|
||||
|
||||
~PSK() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
taps::free(rrcTaps);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
_symbolrate = symbolrate;
|
||||
_samplerate = samplerate;
|
||||
_rrcTapCount = rrcTapCount;
|
||||
_rrcBeta = rrcBeta;
|
||||
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.init(NULL, rrcTaps);
|
||||
agc.init(NULL, 1.0, 10e6, agcRate);
|
||||
costas.init(NULL, costasBandwidth);
|
||||
recov.init(NULL, _samplerate / _symbolrate, omegaGain, muGain, omegaRelLimit);
|
||||
|
||||
rrc.out.free();
|
||||
agc.out.free();
|
||||
costas.out.free();
|
||||
recov.out.free();
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setSymbolrate(double symbolrate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_symbolrate = symbolrate;
|
||||
taps::free(rrcTaps);
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.setTaps(rrcTaps);
|
||||
recov.setOmega(_samplerate / _symbolrate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setSamplerate(double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_samplerate = samplerate;
|
||||
taps::free(rrcTaps);
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.setTaps(rrcTaps);
|
||||
recov.setOmega(_samplerate / _symbolrate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setRRCParams(int rrcTapCount, double rrcBeta) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_rrcTapCount = rrcTapCount;
|
||||
_rrcBeta = rrcBeta;
|
||||
taps::free(rrcTaps);
|
||||
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
|
||||
rrc.setTaps(rrcTaps);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setRRCTapCount(int rrcTapCount) {
|
||||
setRRCParams(rrcTapCount, _rrcBeta);
|
||||
}
|
||||
|
||||
void setRRCBeta(int rrcBeta) {
|
||||
setRRCParams(_rrcTapCount, rrcBeta);
|
||||
}
|
||||
|
||||
void setAGCRate(double agcRate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
agc.setRate(agcRate);
|
||||
}
|
||||
|
||||
void setCostasBandwidth(double bandwidth) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
costas.setBandwidth(bandwidth);
|
||||
}
|
||||
|
||||
void setMMParams(double omegaGain, double muGain, double omegaRelLimit = 0.01) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setOmegaGain(omegaGain);
|
||||
recov.setMuGain(muGain);
|
||||
recov.setOmegaRelLimit(omegaRelLimit);
|
||||
}
|
||||
|
||||
void setOmegaGain(double omegaGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setOmegaGain(omegaGain);
|
||||
}
|
||||
|
||||
void setMuGain(double muGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setMuGain(muGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(double omegaRelLimit) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
recov.setOmegaRelLimit(omegaRelLimit);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
rrc.reset();
|
||||
agc.reset();
|
||||
costas.reset();
|
||||
recov.reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const complex_t* in, complex_t* out) {
|
||||
rrc.process(count, in, out);
|
||||
agc.process(count, out, out);
|
||||
costas.process(count, out, out);
|
||||
return recov.process(count, out, out);
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
protected:
|
||||
double _symbolrate;
|
||||
double _samplerate;
|
||||
int _rrcTapCount;
|
||||
double _rrcBeta;
|
||||
|
||||
tap<float> rrcTaps;
|
||||
filter::FIR<complex_t, float> rrc;
|
||||
loop::FastAGC<complex_t> agc;
|
||||
loop::Costas<ORDER> costas;
|
||||
clock_recovery::MM<complex_t> recov;
|
||||
};
|
||||
}
|
||||
69
core/src/dsp/demod/quadrature.h
Normal file
69
core/src/dsp/demod/quadrature.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../math/fast_atan2.h"
|
||||
#include "../math/hz_to_rads.h"
|
||||
#include "../math/normalize_phase.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
class Quadrature : public Processor<complex_t, float> {
|
||||
using base_type = Processor<complex_t, float>;
|
||||
public:
|
||||
Quadrature() {}
|
||||
|
||||
Quadrature(stream<complex_t>* in, double deviation) { init(in, deviation); }
|
||||
|
||||
Quadrature(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) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
float cphase = in[i].phase();
|
||||
out[i] = math::normalizePhase(cphase - phase) * _invDeviation;
|
||||
phase = cphase;
|
||||
}
|
||||
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;
|
||||
};
|
||||
}
|
||||
125
core/src/dsp/demod/ssb.h
Normal file
125
core/src/dsp/demod/ssb.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../channel/frequency_xlator.h"
|
||||
#include "../convert/complex_to_real.h"
|
||||
#include "../loop/agc.h"
|
||||
#include "../convert/mono_to_stereo.h"
|
||||
|
||||
namespace dsp::demod {
|
||||
template <class T>
|
||||
class SSB : public Processor<complex_t, T> {
|
||||
using base_type = Processor<complex_t, T>;
|
||||
public:
|
||||
enum Mode {
|
||||
USB,
|
||||
LSB,
|
||||
DSB
|
||||
};
|
||||
|
||||
SSB() {}
|
||||
|
||||
SSB(stream<complex_t>* in, Mode mode, double bandwidth, double samplerate, double agcAttack, double agcDecay) { init(in, mode, bandwidth, samplerate, agcAttack, agcDecay); }
|
||||
|
||||
void init(stream<complex_t>* in, Mode mode, double bandwidth, double samplerate, double agcAttack, double agcDecay) {
|
||||
_mode = mode;
|
||||
_bandwidth = bandwidth;
|
||||
_samplerate = samplerate;
|
||||
|
||||
xlator.init(NULL, getTranslation(), _samplerate);
|
||||
agc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
agc.out.free();
|
||||
}
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setMode(Mode mode) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_mode = mode;
|
||||
xlator.setOffset(getTranslation(), _samplerate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_bandwidth = bandwidth;
|
||||
xlator.setOffset(getTranslation(), _samplerate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setSamplerate(double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_samplerate = samplerate;
|
||||
xlator.setOffset(getTranslation(), _samplerate);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setAGCAttack(double attack) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
agc.setAttack(attack);
|
||||
}
|
||||
|
||||
void setAGCDecay(double decay) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
agc.setDecay(decay);
|
||||
}
|
||||
|
||||
int process(int count, const complex_t* in, T* out) {
|
||||
// Move back sideband
|
||||
xlator.process(count, in, xlator.out.writeBuf);
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
convert::ComplexToReal::process(count, xlator.out.writeBuf, out);
|
||||
agc.process(count, out, out);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
convert::ComplexToReal::process(count, xlator.out.writeBuf, agc.out.writeBuf);
|
||||
agc.process(count, agc.out.writeBuf, agc.out.writeBuf);
|
||||
convert::MonoToStereo::process(count, agc.out.writeBuf, out);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
double getTranslation() {
|
||||
if (_mode == Mode::USB) {
|
||||
return _bandwidth / 2.0;
|
||||
}
|
||||
else if (_mode == Mode::LSB) {
|
||||
return -_bandwidth / 2.0;
|
||||
}
|
||||
else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
Mode _mode;
|
||||
double _bandwidth;
|
||||
double _samplerate;
|
||||
channel::FrequencyXlator xlator;
|
||||
loop::AGC<float> agc;
|
||||
|
||||
};
|
||||
};
|
||||
@@ -1,834 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <volk/volk.h>
|
||||
#include <dsp/filter.h>
|
||||
#include <dsp/processing.h>
|
||||
#include <dsp/routing.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <dsp/pll.h>
|
||||
#include <dsp/clock_recovery.h>
|
||||
#include <dsp/math.h>
|
||||
#include <dsp/conversion.h>
|
||||
#include <dsp/audio.h>
|
||||
#include <dsp/stereo_fm.h>
|
||||
#include <dsp/correction.h>
|
||||
|
||||
#define FAST_ATAN2_COEF1 FL_M_PI / 4.0f
|
||||
#define FAST_ATAN2_COEF2 3.0f * FAST_ATAN2_COEF1
|
||||
|
||||
inline float fast_arctan2(float y, float x) {
|
||||
float abs_y = fabsf(y);
|
||||
float r, angle;
|
||||
if (x == 0.0f && y == 0.0f) { return 0.0f; }
|
||||
if (x>=0.0f) {
|
||||
r = (x - abs_y) / (x + abs_y);
|
||||
angle = FAST_ATAN2_COEF1 - FAST_ATAN2_COEF1 * r;
|
||||
}
|
||||
else {
|
||||
r = (x + abs_y) / (abs_y - x);
|
||||
angle = FAST_ATAN2_COEF2 - FAST_ATAN2_COEF1 * r;
|
||||
}
|
||||
if (y < 0.0f) {
|
||||
return -angle;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
namespace dsp {
|
||||
class FloatFMDemod : public generic_block<FloatFMDemod> {
|
||||
public:
|
||||
FloatFMDemod() {}
|
||||
|
||||
FloatFMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
|
||||
|
||||
void init(stream<complex_t>* in, float sampleRate, float deviation) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_deviation = deviation;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FloatFMDemod>::registerInput(_in);
|
||||
generic_block<FloatFMDemod>::registerOutput(&out);
|
||||
generic_block<FloatFMDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<FloatFMDemod>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FloatFMDemod>::ctrlMtx);
|
||||
generic_block<FloatFMDemod>::tempStop();
|
||||
generic_block<FloatFMDemod>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FloatFMDemod>::registerInput(_in);
|
||||
generic_block<FloatFMDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_block<FloatFMDemod>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FloatFMDemod>::ctrlMtx);
|
||||
generic_block<FloatFMDemod>::tempStop();
|
||||
_sampleRate = sampleRate;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FloatFMDemod>::tempStart();
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
assert(generic_block<FloatFMDemod>::_block_init);
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
void setDeviation(float deviation) {
|
||||
assert(generic_block<FloatFMDemod>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FloatFMDemod>::ctrlMtx);
|
||||
generic_block<FloatFMDemod>::tempStop();
|
||||
_deviation = deviation;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FloatFMDemod>::tempStart();
|
||||
}
|
||||
|
||||
float getDeviation() {
|
||||
assert(generic_block<FloatFMDemod>::_block_init);
|
||||
return _deviation;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// This is somehow faster than volk...
|
||||
float diff, currentPhase;
|
||||
for (int i = 0; i < count; i++) {
|
||||
currentPhase = fast_arctan2(_in->readBuf[i].im, _in->readBuf[i].re);
|
||||
diff = currentPhase - phase;
|
||||
if (diff > 3.1415926535f) { diff -= 2 * 3.1415926535f; }
|
||||
else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; }
|
||||
out.writeBuf[i] = diff / phasorSpeed;
|
||||
phase = currentPhase;
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
float phase = 0;
|
||||
float phasorSpeed, _sampleRate, _deviation;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class FMDemod : public generic_block<FMDemod> {
|
||||
public:
|
||||
FMDemod() {}
|
||||
|
||||
FMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
|
||||
|
||||
void init(stream<complex_t>* in, float sampleRate, float deviation) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_deviation = deviation;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FMDemod>::registerInput(_in);
|
||||
generic_block<FMDemod>::registerOutput(&out);
|
||||
generic_block<FMDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<FMDemod>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
|
||||
generic_block<FMDemod>::tempStop();
|
||||
generic_block<FMDemod>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FMDemod>::registerInput(_in);
|
||||
generic_block<FMDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_block<FMDemod>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
|
||||
generic_block<FMDemod>::tempStop();
|
||||
_sampleRate = sampleRate;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
generic_block<FMDemod>::tempStart();
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
assert(generic_block<FMDemod>::_block_init);
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
void setDeviation(float deviation) {
|
||||
assert(generic_block<FMDemod>::_block_init);
|
||||
_deviation = deviation;
|
||||
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
|
||||
}
|
||||
|
||||
float getDeviation() {
|
||||
assert(generic_block<FMDemod>::_block_init);
|
||||
return _deviation;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// This is somehow faster than volk...
|
||||
|
||||
float diff, currentPhase;
|
||||
for (int i = 0; i < count; i++) {
|
||||
currentPhase = fast_arctan2(_in->readBuf[i].im, _in->readBuf[i].re);
|
||||
diff = currentPhase - phase;
|
||||
if (diff > 3.1415926535f) { diff -= 2 * 3.1415926535f; }
|
||||
else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; }
|
||||
out.writeBuf[i].l = diff / phasorSpeed;
|
||||
out.writeBuf[i].r = diff / phasorSpeed;
|
||||
phase = currentPhase;
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
float phase = 0;
|
||||
float phasorSpeed, _sampleRate, _deviation;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class AMDemod : public generic_block<AMDemod> {
|
||||
public:
|
||||
AMDemod() {}
|
||||
|
||||
AMDemod(stream<complex_t>* in) { init(in); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<AMDemod>::registerInput(_in);
|
||||
generic_block<AMDemod>::registerOutput(&out);
|
||||
generic_block<AMDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<AMDemod>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<AMDemod>::ctrlMtx);
|
||||
generic_block<AMDemod>::tempStop();
|
||||
generic_block<AMDemod>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<AMDemod>::registerInput(_in);
|
||||
generic_block<AMDemod>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_magnitude_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
|
||||
_in->flush();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] -= avg;
|
||||
avg += out.writeBuf[i] * 10e-4;
|
||||
}
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
float avg = 0;
|
||||
|
||||
};
|
||||
|
||||
class SSBDemod : public generic_block<SSBDemod> {
|
||||
public:
|
||||
SSBDemod() {}
|
||||
|
||||
SSBDemod(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) { init(in, sampleRate, bandWidth, mode); }
|
||||
|
||||
~SSBDemod() {
|
||||
if (!generic_block<SSBDemod>::_block_init) { return; }
|
||||
generic_block<SSBDemod>::stop();
|
||||
delete[] buffer;
|
||||
generic_block<SSBDemod>::_block_init = false;
|
||||
}
|
||||
|
||||
enum {
|
||||
MODE_USB,
|
||||
MODE_LSB,
|
||||
MODE_DSB
|
||||
};
|
||||
|
||||
void init(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_bandWidth = bandWidth;
|
||||
_mode = mode;
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
buffer = new lv_32fc_t[STREAM_BUFFER_SIZE];
|
||||
generic_block<SSBDemod>::registerInput(_in);
|
||||
generic_block<SSBDemod>::registerOutput(&out);
|
||||
generic_block<SSBDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<SSBDemod>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<SSBDemod>::ctrlMtx);
|
||||
generic_block<SSBDemod>::tempStop();
|
||||
generic_block<SSBDemod>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<SSBDemod>::registerInput(_in);
|
||||
generic_block<SSBDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_block<SSBDemod>::_block_init);
|
||||
_sampleRate = sampleRate;
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setBandWidth(float bandWidth) {
|
||||
assert(generic_block<SSBDemod>::_block_init);
|
||||
_bandWidth = bandWidth;
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setMode(int mode) {
|
||||
assert(generic_block<SSBDemod>::_block_init);
|
||||
_mode = mode;
|
||||
switch (_mode) {
|
||||
case MODE_USB:
|
||||
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_LSB:
|
||||
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
|
||||
break;
|
||||
case MODE_DSB:
|
||||
phaseDelta = lv_cmake(1.0f, 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
volk_32fc_deinterleave_real_32f(out.writeBuf, buffer, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
int _mode;
|
||||
float _sampleRate, _bandWidth;
|
||||
stream<complex_t>* _in;
|
||||
lv_32fc_t* buffer;
|
||||
lv_32fc_t phase;
|
||||
lv_32fc_t phaseDelta;
|
||||
|
||||
};
|
||||
|
||||
class FSKDemod : public generic_hier_block<FSKDemod> {
|
||||
public:
|
||||
FSKDemod() {}
|
||||
|
||||
FSKDemod(stream<complex_t>* input, float sampleRate, float deviation, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
init(input, sampleRate, deviation, baudRate, omegaGain, muGain, omegaRelLimit);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* input, float sampleRate, float deviation, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
_sampleRate = sampleRate;
|
||||
_deviation = deviation;
|
||||
_baudRate = baudRate;
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
|
||||
demod.init(input, _sampleRate, _deviation);
|
||||
recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
|
||||
out = &recov.out;
|
||||
|
||||
generic_hier_block<FSKDemod>::registerBlock(&demod);
|
||||
generic_hier_block<FSKDemod>::registerBlock(&recov);
|
||||
generic_hier_block<FSKDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* input) {
|
||||
assert((generic_hier_block<FSKDemod>::_block_init));
|
||||
demod.setInput(input);
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_hier_block<FSKDemod>::_block_init);
|
||||
generic_hier_block<FSKDemod>::tempStop();
|
||||
_sampleRate = sampleRate;
|
||||
demod.setSampleRate(_sampleRate);
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
generic_hier_block<FSKDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setDeviation(float deviation) {
|
||||
assert(generic_hier_block<FSKDemod>::_block_init);
|
||||
_deviation = deviation;
|
||||
demod.setDeviation(deviation);
|
||||
}
|
||||
|
||||
void setBaudRate(float baudRate, float omegaRelLimit) {
|
||||
assert(generic_hier_block<FSKDemod>::_block_init);
|
||||
_baudRate = baudRate;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
}
|
||||
|
||||
void setMMGains(float omegaGain, float myGain) {
|
||||
assert(generic_hier_block<FSKDemod>::_block_init);
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = myGain;
|
||||
recov.setGains(_omegaGain, _muGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(float omegaRelLimit) {
|
||||
assert(generic_hier_block<FSKDemod>::_block_init);
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
recov.setOmegaRelLimit(_omegaRelLimit);
|
||||
}
|
||||
|
||||
stream<float>* out = NULL;
|
||||
|
||||
private:
|
||||
FloatFMDemod demod;
|
||||
MMClockRecovery<float> recov;
|
||||
|
||||
float _sampleRate;
|
||||
float _deviation;
|
||||
float _baudRate;
|
||||
float _omegaGain;
|
||||
float _muGain;
|
||||
float _omegaRelLimit;
|
||||
};
|
||||
|
||||
class GFSKDemod : public generic_hier_block<GFSKDemod> {
|
||||
public:
|
||||
GFSKDemod() {}
|
||||
|
||||
GFSKDemod(stream<complex_t>* input, float sampleRate, float deviation, float rrcAlpha, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
init(input, sampleRate, deviation, rrcAlpha, baudRate, omegaGain, muGain, omegaRelLimit);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* input, float sampleRate, float deviation, float rrcAlpha, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
_sampleRate = sampleRate;
|
||||
_deviation = deviation;
|
||||
_rrcAlpha = rrcAlpha;
|
||||
_baudRate = baudRate;
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
|
||||
demod.init(input, _sampleRate, _deviation);
|
||||
rrc.init(31, _sampleRate, _baudRate, _rrcAlpha);
|
||||
fir.init(&demod.out, &rrc);
|
||||
recov.init(&fir.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
|
||||
out = &recov.out;
|
||||
|
||||
generic_hier_block<GFSKDemod>::registerBlock(&demod);
|
||||
generic_hier_block<GFSKDemod>::registerBlock(&fir);
|
||||
generic_hier_block<GFSKDemod>::registerBlock(&recov);
|
||||
generic_hier_block<GFSKDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* input) {
|
||||
assert((generic_hier_block<GFSKDemod>::_block_init));
|
||||
demod.setInput(input);
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_hier_block<GFSKDemod>::_block_init);
|
||||
generic_hier_block<GFSKDemod>::tempStop();
|
||||
_sampleRate = sampleRate;
|
||||
demod.setSampleRate(_sampleRate);
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
rrc.setSampleRate(_sampleRate);
|
||||
fir.updateWindow(&rrc);
|
||||
generic_hier_block<GFSKDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setDeviation(float deviation) {
|
||||
assert(generic_hier_block<GFSKDemod>::_block_init);
|
||||
_deviation = deviation;
|
||||
demod.setDeviation(deviation);
|
||||
}
|
||||
|
||||
void setRRCAlpha(float rrcAlpha) {
|
||||
assert(generic_hier_block<GFSKDemod>::_block_init);
|
||||
_rrcAlpha = rrcAlpha;
|
||||
rrc.setAlpha(_rrcAlpha);
|
||||
fir.updateWindow(&rrc);
|
||||
}
|
||||
|
||||
void setBaudRate(float baudRate, float omegaRelLimit) {
|
||||
assert(generic_hier_block<GFSKDemod>::_block_init);
|
||||
_baudRate = baudRate;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
generic_hier_block<GFSKDemod>::tempStop();
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
rrc.setBaudRate(_baudRate);
|
||||
fir.updateWindow(&rrc);
|
||||
generic_hier_block<GFSKDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setMMGains(float omegaGain, float myGain) {
|
||||
assert(generic_hier_block<GFSKDemod>::_block_init);
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = myGain;
|
||||
recov.setGains(_omegaGain, _muGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(float omegaRelLimit) {
|
||||
assert(generic_hier_block<GFSKDemod>::_block_init);
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
recov.setOmegaRelLimit(_omegaRelLimit);
|
||||
}
|
||||
|
||||
stream<float>* out = NULL;
|
||||
|
||||
private:
|
||||
FloatFMDemod demod;
|
||||
RRCTaps rrc;
|
||||
FIR<float> fir;
|
||||
MMClockRecovery<float> recov;
|
||||
|
||||
float _sampleRate;
|
||||
float _deviation;
|
||||
float _rrcAlpha;
|
||||
float _baudRate;
|
||||
float _omegaGain;
|
||||
float _muGain;
|
||||
float _omegaRelLimit;
|
||||
};
|
||||
|
||||
template<int ORDER, bool OFFSET>
|
||||
class PSKDemod : public generic_hier_block<PSKDemod<ORDER, OFFSET>> {
|
||||
public:
|
||||
PSKDemod() {}
|
||||
|
||||
PSKDemod(stream<complex_t>* input, float sampleRate, float baudRate, int RRCTapCount = 31, float RRCAlpha = 0.32f, float agcRate = 10e-4, float costasLoopBw = 0.004f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
init(input, sampleRate, baudRate, RRCTapCount, RRCAlpha, agcRate, costasLoopBw, omegaGain, muGain, omegaRelLimit);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* input, float sampleRate, float baudRate, int RRCTapCount = 31, float RRCAlpha = 0.32f, float agcRate = 10e-4, float costasLoopBw = 0.004f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
_RRCTapCount = RRCTapCount;
|
||||
_RRCAlpha = RRCAlpha;
|
||||
_sampleRate = sampleRate;
|
||||
_agcRate = agcRate;
|
||||
_costasLoopBw = costasLoopBw;
|
||||
_baudRate = baudRate;
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
|
||||
agc.init(input, 1.0f, 65535, _agcRate);
|
||||
taps.init(_RRCTapCount, _sampleRate, _baudRate, _RRCAlpha);
|
||||
rrc.init(&agc.out, &taps);
|
||||
demod.init(&rrc.out, _costasLoopBw);
|
||||
|
||||
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&agc);
|
||||
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&rrc);
|
||||
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&demod);
|
||||
|
||||
if constexpr (OFFSET) {
|
||||
delay.init(&demod.out);
|
||||
recov.init(&delay.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
|
||||
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&delay);
|
||||
}
|
||||
else {
|
||||
recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
|
||||
}
|
||||
|
||||
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&recov);
|
||||
|
||||
out = &recov.out;
|
||||
|
||||
generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* input) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
agc.setInput(input);
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
_sampleRate = sampleRate;
|
||||
rrc.tempStop();
|
||||
recov.tempStop();
|
||||
taps.setSampleRate(_sampleRate);
|
||||
rrc.updateWindow(&taps);
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
rrc.tempStart();
|
||||
recov.tempStart();
|
||||
}
|
||||
|
||||
void setBaudRate(float baudRate) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
_baudRate = baudRate;
|
||||
rrc.tempStop();
|
||||
recov.tempStop();
|
||||
taps.setBaudRate(_baudRate);
|
||||
rrc.updateWindow(&taps);
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
rrc.tempStart();
|
||||
recov.tempStart();
|
||||
}
|
||||
|
||||
void setRRCParams(int RRCTapCount, float RRCAlpha) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
_RRCTapCount = RRCTapCount;
|
||||
_RRCAlpha = RRCAlpha;
|
||||
taps.setTapCount(_RRCTapCount);
|
||||
taps.setAlpha(RRCAlpha);
|
||||
rrc.updateWindow(&taps);
|
||||
}
|
||||
|
||||
void setAgcRate(float agcRate) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
_agcRate = agcRate;
|
||||
agc.setRate(_agcRate);
|
||||
}
|
||||
|
||||
void setCostasLoopBw(float costasLoopBw) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
_costasLoopBw = costasLoopBw;
|
||||
demod.setLoopBandwidth(_costasLoopBw);
|
||||
}
|
||||
|
||||
void setMMGains(float omegaGain, float myGain) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = myGain;
|
||||
recov.setGains(_omegaGain, _muGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(float omegaRelLimit) {
|
||||
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
recov.setOmegaRelLimit(_omegaRelLimit);
|
||||
}
|
||||
|
||||
stream<complex_t>* out = NULL;
|
||||
|
||||
private:
|
||||
dsp::ComplexAGC agc;
|
||||
dsp::RRCTaps taps;
|
||||
dsp::FIR<dsp::complex_t> rrc;
|
||||
CostasLoop<ORDER> demod;
|
||||
DelayImag delay;
|
||||
MMClockRecovery<dsp::complex_t> recov;
|
||||
|
||||
int _RRCTapCount;
|
||||
float _RRCAlpha;
|
||||
float _sampleRate;
|
||||
float _agcRate;
|
||||
float _baudRate;
|
||||
float _costasLoopBw;
|
||||
float _omegaGain;
|
||||
float _muGain;
|
||||
float _omegaRelLimit;
|
||||
};
|
||||
|
||||
class PMDemod : public generic_hier_block<PMDemod> {
|
||||
public:
|
||||
PMDemod() {}
|
||||
|
||||
PMDemod(stream<complex_t>* input, float sampleRate, float baudRate, float agcRate = 0.02e-3f, float pllLoopBandwidth = (0.06f*0.06f) / 4.0f, int rrcTapCount = 31, float rrcAlpha = 0.6f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
init(input, sampleRate, baudRate, agcRate, pllLoopBandwidth, rrcTapCount, rrcAlpha, omegaGain, muGain, omegaRelLimit);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* input, float sampleRate, float baudRate, float agcRate = 0.02e-3f, float pllLoopBandwidth = (0.06f*0.06f) / 4.0f, int rrcTapCount = 31, float rrcAlpha = 0.6f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
|
||||
_sampleRate = sampleRate;
|
||||
_baudRate = baudRate;
|
||||
_agcRate = agcRate;
|
||||
_pllLoopBandwidth = pllLoopBandwidth;
|
||||
_rrcTapCount = rrcTapCount;
|
||||
_rrcAlpha = rrcAlpha;
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
|
||||
agc.init(input, 1.0f, 65535, _agcRate);
|
||||
pll.init(&agc.out, _pllLoopBandwidth);
|
||||
rrcwin.init(_rrcTapCount, _sampleRate, _baudRate, _rrcAlpha);
|
||||
rrc.init(&pll.out, &rrcwin);
|
||||
recov.init(&rrc.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
|
||||
|
||||
out = &recov.out;
|
||||
|
||||
generic_hier_block<PMDemod>::registerBlock(&agc);
|
||||
generic_hier_block<PMDemod>::registerBlock(&pll);
|
||||
generic_hier_block<PMDemod>::registerBlock(&rrc);
|
||||
generic_hier_block<PMDemod>::registerBlock(&recov);
|
||||
generic_hier_block<PMDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* input) {
|
||||
assert(generic_hier_block<PMDemod>::_block_init);
|
||||
agc.setInput(input);
|
||||
}
|
||||
|
||||
void setAgcRate(float agcRate) {
|
||||
assert(generic_hier_block<PMDemod>::_block_init);
|
||||
_agcRate = agcRate;
|
||||
agc.setRate(_agcRate);
|
||||
}
|
||||
|
||||
void setPllLoopBandwidth(float pllLoopBandwidth) {
|
||||
assert(generic_hier_block<PMDemod>::_block_init);
|
||||
_pllLoopBandwidth = pllLoopBandwidth;
|
||||
pll.setLoopBandwidth(_pllLoopBandwidth);
|
||||
}
|
||||
|
||||
void setRRCParams(int rrcTapCount, float rrcAlpha) {
|
||||
assert(generic_hier_block<PMDemod>::_block_init);
|
||||
_rrcTapCount = rrcTapCount;
|
||||
_rrcAlpha = rrcAlpha;
|
||||
rrcwin.setTapCount(_rrcTapCount);
|
||||
rrcwin.setAlpha(_rrcAlpha);
|
||||
rrc.updateWindow(&rrcwin);
|
||||
}
|
||||
|
||||
void setMMGains(float omegaGain, float muGain) {
|
||||
assert(generic_hier_block<PMDemod>::_block_init);
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = muGain;
|
||||
recov.setGains(_omegaGain, _muGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(float omegaRelLimit) {
|
||||
assert(generic_hier_block<PMDemod>::_block_init);
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
recov.setOmegaRelLimit(_omegaRelLimit);
|
||||
}
|
||||
|
||||
stream<float>* out = NULL;
|
||||
|
||||
private:
|
||||
dsp::ComplexAGC agc;
|
||||
dsp::CarrierTrackingPLL<float> pll;
|
||||
dsp::RRCTaps rrcwin;
|
||||
dsp::FIR<float> rrc;
|
||||
dsp::MMClockRecovery<float> recov;
|
||||
|
||||
float _sampleRate;
|
||||
float _baudRate;
|
||||
float _agcRate;
|
||||
float _pllLoopBandwidth;
|
||||
int _rrcTapCount;
|
||||
float _rrcAlpha;
|
||||
float _omegaGain;
|
||||
float _muGain;
|
||||
float _omegaRelLimit;
|
||||
};
|
||||
|
||||
class StereoFMDemod : public generic_hier_block<StereoFMDemod> {
|
||||
public:
|
||||
StereoFMDemod() {}
|
||||
|
||||
StereoFMDemod(stream<complex_t>* input, float sampleRate, float deviation) {
|
||||
init(input, sampleRate, deviation);
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* input, float sampleRate, float deviation) {
|
||||
_sampleRate = sampleRate;
|
||||
|
||||
PilotFirWin.init(18750, 19250, 3000, _sampleRate);
|
||||
|
||||
demod.init(input, _sampleRate, deviation);
|
||||
|
||||
r2c.init(&demod.out);
|
||||
|
||||
pilotFilter.init(&r2c.out, &PilotFirWin);
|
||||
|
||||
demux.init(&pilotFilter.dataOut, &pilotFilter.pilotOut, 0.1f);
|
||||
|
||||
recon.init(&demux.AplusBOut, &demux.AminusBOut);
|
||||
|
||||
out = &recon.out;
|
||||
|
||||
generic_hier_block<StereoFMDemod>::registerBlock(&demod);
|
||||
generic_hier_block<StereoFMDemod>::registerBlock(&r2c);
|
||||
generic_hier_block<StereoFMDemod>::registerBlock(&pilotFilter);
|
||||
generic_hier_block<StereoFMDemod>::registerBlock(&demux);
|
||||
generic_hier_block<StereoFMDemod>::registerBlock(&recon);
|
||||
generic_hier_block<StereoFMDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* input) {
|
||||
assert(generic_hier_block<StereoFMDemod>::_block_init);
|
||||
r2c.setInput(input);
|
||||
}
|
||||
|
||||
void setDeviation(float deviation) {
|
||||
demod.setDeviation(deviation);
|
||||
}
|
||||
|
||||
stream<stereo_t>* out = NULL;
|
||||
|
||||
private:
|
||||
filter_window::BandPassBlackmanWindow PilotFirWin;
|
||||
|
||||
FloatFMDemod demod;
|
||||
|
||||
RealToComplex r2c;
|
||||
|
||||
FMStereoDemuxPilotFilter pilotFilter;
|
||||
|
||||
FMStereoDemux demux;
|
||||
|
||||
FMStereoReconstruct recon;
|
||||
|
||||
float _sampleRate;
|
||||
};
|
||||
}
|
||||
31
core/src/dsp/digital/binary_slicer.h
Normal file
31
core/src/dsp/digital/binary_slicer.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::digital {
|
||||
class BinarySlicer : public Processor<float, uint8_t> {
|
||||
using base_type = Processor<float, uint8_t>;
|
||||
public:
|
||||
BinarySlicer() {}
|
||||
|
||||
BinarySlicer(stream<float> *in) { base_type::init(in); }
|
||||
|
||||
static inline int process(int count, const float* in, uint8_t* out) {
|
||||
// TODO: Switch to volk
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = in[i] > 0.0f;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
65
core/src/dsp/digital/differential_decoder.h
Normal file
65
core/src/dsp/digital/differential_decoder.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::digital {
|
||||
class DifferentialDecoder : public Processor<uint8_t, uint8_t> {
|
||||
using base_type = Processor<uint8_t, uint8_t>;
|
||||
public:
|
||||
DifferentialDecoder() {}
|
||||
|
||||
DifferentialDecoder(stream<uint8_t> *in) { base_type::init(in); }
|
||||
|
||||
void init(stream<uint8_t> *in, uint8_t modulus, uint8_t initSym = 0) {
|
||||
_modulus = modulus;
|
||||
_initSym = initSym;
|
||||
|
||||
last = _initSym;
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setModulus(uint8_t modulus) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_modulus = modulus;
|
||||
}
|
||||
|
||||
void setInitSym(uint8_t initSym) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_initSym = initSym;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
last = _initSym;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const uint8_t* in, uint8_t* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = (in[i] - last + _modulus) % _modulus;
|
||||
last = in[i];
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t last;
|
||||
uint8_t _initSym;
|
||||
uint8_t _modulus;
|
||||
};
|
||||
}
|
||||
47
core/src/dsp/digital/manchester_decoder.h
Normal file
47
core/src/dsp/digital/manchester_decoder.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::digital {
|
||||
class ManchesterDecoder : public Processor<uint8_t, uint8_t> {
|
||||
using base_type = Processor<uint8_t, uint8_t>;
|
||||
public:
|
||||
ManchesterDecoder() {}
|
||||
|
||||
ManchesterDecoder(stream<uint8_t> *in) { base_type::init(in); }
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
offset = 0;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const uint8_t* in, uint8_t* out) {
|
||||
// TODO: NOT THIS BULLSHIT
|
||||
int outCount = 0;
|
||||
for (; offset < count; offset += 2) {
|
||||
out[outCount++] = in[offset];
|
||||
}
|
||||
offset -= count;
|
||||
return outCount;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
protected:
|
||||
int offset = 0;
|
||||
};
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
// WTF???
|
||||
extern "C"
|
||||
{
|
||||
#include <correct.h>
|
||||
}
|
||||
|
||||
const uint8_t toDB[] = {
|
||||
0x00, 0x7b, 0xaf, 0xd4, 0x99, 0xe2, 0x36, 0x4d, 0xfa, 0x81, 0x55, 0x2e, 0x63, 0x18, 0xcc, 0xb7, 0x86, 0xfd, 0x29, 0x52, 0x1f,
|
||||
0x64, 0xb0, 0xcb, 0x7c, 0x07, 0xd3, 0xa8, 0xe5, 0x9e, 0x4a, 0x31, 0xec, 0x97, 0x43, 0x38, 0x75, 0x0e, 0xda, 0xa1, 0x16, 0x6d, 0xb9, 0xc2, 0x8f, 0xf4,
|
||||
0x20, 0x5b, 0x6a, 0x11, 0xc5, 0xbe, 0xf3, 0x88, 0x5c, 0x27, 0x90, 0xeb, 0x3f, 0x44, 0x09, 0x72, 0xa6, 0xdd, 0xef, 0x94, 0x40, 0x3b, 0x76, 0x0d, 0xd9,
|
||||
0xa2, 0x15, 0x6e, 0xba, 0xc1, 0x8c, 0xf7, 0x23, 0x58, 0x69, 0x12, 0xc6, 0xbd, 0xf0, 0x8b, 0x5f, 0x24, 0x93, 0xe8, 0x3c, 0x47, 0x0a, 0x71, 0xa5, 0xde,
|
||||
0x03, 0x78, 0xac, 0xd7, 0x9a, 0xe1, 0x35, 0x4e, 0xf9, 0x82, 0x56, 0x2d, 0x60, 0x1b, 0xcf, 0xb4, 0x85, 0xfe, 0x2a, 0x51, 0x1c, 0x67, 0xb3, 0xc8, 0x7f,
|
||||
0x04, 0xd0, 0xab, 0xe6, 0x9d, 0x49, 0x32, 0x8d, 0xf6, 0x22, 0x59, 0x14, 0x6f, 0xbb, 0xc0, 0x77, 0x0c, 0xd8, 0xa3, 0xee, 0x95, 0x41, 0x3a, 0x0b, 0x70,
|
||||
0xa4, 0xdf, 0x92, 0xe9, 0x3d, 0x46, 0xf1, 0x8a, 0x5e, 0x25, 0x68, 0x13, 0xc7, 0xbc, 0x61, 0x1a, 0xce, 0xb5, 0xf8, 0x83, 0x57, 0x2c, 0x9b, 0xe0, 0x34,
|
||||
0x4f, 0x02, 0x79, 0xad, 0xd6, 0xe7, 0x9c, 0x48, 0x33, 0x7e, 0x05, 0xd1, 0xaa, 0x1d, 0x66, 0xb2, 0xc9, 0x84, 0xff, 0x2b, 0x50, 0x62, 0x19, 0xcd, 0xb6,
|
||||
0xfb, 0x80, 0x54, 0x2f, 0x98, 0xe3, 0x37, 0x4c, 0x01, 0x7a, 0xae, 0xd5, 0xe4, 0x9f, 0x4b, 0x30, 0x7d, 0x06, 0xd2, 0xa9, 0x1e, 0x65, 0xb1, 0xca, 0x87,
|
||||
0xfc, 0x28, 0x53, 0x8e, 0xf5, 0x21, 0x5a, 0x17, 0x6c, 0xb8, 0xc3, 0x74, 0x0f, 0xdb, 0xa0, 0xed, 0x96, 0x42, 0x39, 0x08, 0x73, 0xa7, 0xdc, 0x91, 0xea,
|
||||
0x3e, 0x45, 0xf2, 0x89, 0x5d, 0x26, 0x6b, 0x10, 0xc4, 0xbf
|
||||
};
|
||||
|
||||
const uint8_t fromDB[] = {
|
||||
0x00, 0xcc, 0xac, 0x60, 0x79, 0xb5, 0xd5, 0x19, 0xf0, 0x3c, 0x5c, 0x90, 0x89, 0x45, 0x25, 0xe9, 0xfd, 0x31, 0x51, 0x9d,
|
||||
0x84, 0x48, 0x28, 0xe4, 0x0d, 0xc1, 0xa1, 0x6d, 0x74, 0xb8, 0xd8, 0x14, 0x2e, 0xe2, 0x82, 0x4e, 0x57, 0x9b, 0xfb, 0x37, 0xde, 0x12, 0x72, 0xbe, 0xa7,
|
||||
0x6b, 0x0b, 0xc7, 0xd3, 0x1f, 0x7f, 0xb3, 0xaa, 0x66, 0x06, 0xca, 0x23, 0xef, 0x8f, 0x43, 0x5a, 0x96, 0xf6, 0x3a, 0x42, 0x8e, 0xee, 0x22, 0x3b, 0xf7,
|
||||
0x97, 0x5b, 0xb2, 0x7e, 0x1e, 0xd2, 0xcb, 0x07, 0x67, 0xab, 0xbf, 0x73, 0x13, 0xdf, 0xc6, 0x0a, 0x6a, 0xa6, 0x4f, 0x83, 0xe3, 0x2f, 0x36, 0xfa, 0x9a,
|
||||
0x56, 0x6c, 0xa0, 0xc0, 0x0c, 0x15, 0xd9, 0xb9, 0x75, 0x9c, 0x50, 0x30, 0xfc, 0xe5, 0x29, 0x49, 0x85, 0x91, 0x5d, 0x3d, 0xf1, 0xe8, 0x24, 0x44, 0x88,
|
||||
0x61, 0xad, 0xcd, 0x01, 0x18, 0xd4, 0xb4, 0x78, 0xc5, 0x09, 0x69, 0xa5, 0xbc, 0x70, 0x10, 0xdc, 0x35, 0xf9, 0x99, 0x55, 0x4c, 0x80, 0xe0, 0x2c, 0x38,
|
||||
0xf4, 0x94, 0x58, 0x41, 0x8d, 0xed, 0x21, 0xc8, 0x04, 0x64, 0xa8, 0xb1, 0x7d, 0x1d, 0xd1, 0xeb, 0x27, 0x47, 0x8b, 0x92, 0x5e, 0x3e, 0xf2, 0x1b, 0xd7,
|
||||
0xb7, 0x7b, 0x62, 0xae, 0xce, 0x02, 0x16, 0xda, 0xba, 0x76, 0x6f, 0xa3, 0xc3, 0x0f, 0xe6, 0x2a, 0x4a, 0x86, 0x9f, 0x53, 0x33, 0xff, 0x87, 0x4b, 0x2b,
|
||||
0xe7, 0xfe, 0x32, 0x52, 0x9e, 0x77, 0xbb, 0xdb, 0x17, 0x0e, 0xc2, 0xa2, 0x6e, 0x7a, 0xb6, 0xd6, 0x1a, 0x03, 0xcf, 0xaf, 0x63, 0x8a, 0x46, 0x26, 0xea,
|
||||
0xf3, 0x3f, 0x5f, 0x93, 0xa9, 0x65, 0x05, 0xc9, 0xd0, 0x1c, 0x7c, 0xb0, 0x59, 0x95, 0xf5, 0x39, 0x20, 0xec, 0x8c, 0x40, 0x54, 0x98, 0xf8, 0x34, 0x2d,
|
||||
0xe1, 0x81, 0x4d, 0xa4, 0x68, 0x08, 0xc4, 0xdd, 0x11, 0x71, 0xbd
|
||||
};
|
||||
|
||||
const uint8_t randVals[] = {
|
||||
0xFF, 0x48, 0x0E, 0xC0, 0x9A, 0x0D, 0x70, 0xBC, 0x8E, 0x2C, 0x93, 0xAD, 0xA7, 0xB7, 0x46, 0xCE,
|
||||
0x5A, 0x97, 0x7D, 0xCC, 0x32, 0xA2, 0xBF, 0x3E, 0x0A, 0x10, 0xF1, 0x88, 0x94, 0xCD, 0xEA, 0xB1,
|
||||
0xFE, 0x90, 0x1D, 0x81, 0x34, 0x1A, 0xE1, 0x79, 0x1C, 0x59, 0x27, 0x5B, 0x4F, 0x6E, 0x8D, 0x9C,
|
||||
0xB5, 0x2E, 0xFB, 0x98, 0x65, 0x45, 0x7E, 0x7C, 0x14, 0x21, 0xE3, 0x11, 0x29, 0x9B, 0xD5, 0x63,
|
||||
0xFD, 0x20, 0x3B, 0x02, 0x68, 0x35, 0xC2, 0xF2, 0x38, 0xB2, 0x4E, 0xB6, 0x9E, 0xDD, 0x1B, 0x39,
|
||||
0x6A, 0x5D, 0xF7, 0x30, 0xCA, 0x8A, 0xFC, 0xF8, 0x28, 0x43, 0xC6, 0x22, 0x53, 0x37, 0xAA, 0xC7,
|
||||
0xFA, 0x40, 0x76, 0x04, 0xD0, 0x6B, 0x85, 0xE4, 0x71, 0x64, 0x9D, 0x6D, 0x3D, 0xBA, 0x36, 0x72,
|
||||
0xD4, 0xBB, 0xEE, 0x61, 0x95, 0x15, 0xF9, 0xF0, 0x50, 0x87, 0x8C, 0x44, 0xA6, 0x6F, 0x55, 0x8F,
|
||||
0xF4, 0x80, 0xEC, 0x09, 0xA0, 0xD7, 0x0B, 0xC8, 0xE2, 0xC9, 0x3A, 0xDA, 0x7B, 0x74, 0x6C, 0xE5,
|
||||
0xA9, 0x77, 0xDC, 0xC3, 0x2A, 0x2B, 0xF3, 0xE0, 0xA1, 0x0F, 0x18, 0x89, 0x4C, 0xDE, 0xAB, 0x1F,
|
||||
0xE9, 0x01, 0xD8, 0x13, 0x41, 0xAE, 0x17, 0x91, 0xC5, 0x92, 0x75, 0xB4, 0xF6, 0xE8, 0xD9, 0xCB,
|
||||
0x52, 0xEF, 0xB9, 0x86, 0x54, 0x57, 0xE7, 0xC1, 0x42, 0x1E, 0x31, 0x12, 0x99, 0xBD, 0x56, 0x3F,
|
||||
0xD2, 0x03, 0xB0, 0x26, 0x83, 0x5C, 0x2F, 0x23, 0x8B, 0x24, 0xEB, 0x69, 0xED, 0xD1, 0xB3, 0x96,
|
||||
0xA5, 0xDF, 0x73, 0x0C, 0xA8, 0xAF, 0xCF, 0x82, 0x84, 0x3C, 0x62, 0x25, 0x33, 0x7A, 0xAC, 0x7F,
|
||||
0xA4, 0x07, 0x60, 0x4D, 0x06, 0xB8, 0x5E, 0x47, 0x16, 0x49, 0xD6, 0xD3, 0xDB, 0xA3, 0x67, 0x2D,
|
||||
0x4B, 0xBE, 0xE6, 0x19, 0x51, 0x5F, 0x9F, 0x05, 0x08, 0x78, 0xC4, 0x4A, 0x66, 0xF5, 0x58
|
||||
};
|
||||
|
||||
namespace dsp {
|
||||
class FalconRS : public generic_block<FalconRS> {
|
||||
public:
|
||||
FalconRS() {}
|
||||
|
||||
FalconRS(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
|
||||
for (int i = 0; i < 5; i++) { memset(buffers[i], 0, 255); }
|
||||
for (int i = 0; i < 5; i++) { memset(outBuffers[i], 0, 255); }
|
||||
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 120, 11, 16);
|
||||
if (rs == NULL) { printf("Error creating the reed solomon decoder\n"); }
|
||||
|
||||
generic_block<FalconRS>::registerInput(_in);
|
||||
generic_block<FalconRS>::registerOutput(&out);
|
||||
generic_block<FalconRS>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<FalconRS>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FalconRS>::ctrlMtx);
|
||||
generic_block<FalconRS>::tempStop();
|
||||
generic_block<FalconRS>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FalconRS>::registerInput(_in);
|
||||
generic_block<FalconRS>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
uint8_t* data = _in->readBuf + 4;
|
||||
|
||||
// Deinterleave
|
||||
for (int i = 0; i < 255*5; i++) {
|
||||
buffers[i%5][i/5] = fromDB[data[i]];
|
||||
}
|
||||
|
||||
// Reed the solomon :weary:
|
||||
int result = 0;
|
||||
result = correct_reed_solomon_decode(rs, buffers[0], 255, outBuffers[0]);
|
||||
if (result == -1) { _in->flush(); return count; }
|
||||
result = correct_reed_solomon_decode(rs, buffers[1], 255, outBuffers[1]);
|
||||
if (result == -1) { _in->flush(); return count; }
|
||||
result = correct_reed_solomon_decode(rs, buffers[2], 255, outBuffers[2]);
|
||||
if (result == -1) { _in->flush(); return count; }
|
||||
result = correct_reed_solomon_decode(rs, buffers[3], 255, outBuffers[3]);
|
||||
if (result == -1) { _in->flush(); return count; }
|
||||
result = correct_reed_solomon_decode(rs, buffers[4], 255, outBuffers[4]);
|
||||
if (result == -1) { _in->flush(); return count; }
|
||||
|
||||
// Reinterleave
|
||||
for (int i = 0; i < 255*5; i++) {
|
||||
out.writeBuf[i] = toDB[outBuffers[i%5][i/5]] ^ randVals[i % 255];
|
||||
}
|
||||
|
||||
out.swap(255*5);
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
uint8_t buffers[5][255];
|
||||
uint8_t outBuffers[5][255];
|
||||
correct_reed_solomon* rs;
|
||||
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace dsp {
|
||||
struct FalconFrameHeader {
|
||||
uint32_t counter;
|
||||
uint16_t packet;
|
||||
};
|
||||
|
||||
class FalconPacketSync : public generic_block<FalconPacketSync> {
|
||||
public:
|
||||
FalconPacketSync() {}
|
||||
|
||||
FalconPacketSync(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
|
||||
generic_block<FalconPacketSync>::registerInput(_in);
|
||||
generic_block<FalconPacketSync>::registerOutput(&out);
|
||||
generic_block<FalconPacketSync>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<FalconPacketSync>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FalconPacketSync>::ctrlMtx);
|
||||
generic_block<FalconPacketSync>::tempStop();
|
||||
generic_block<FalconPacketSync>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FalconPacketSync>::registerInput(_in);
|
||||
generic_block<FalconPacketSync>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Parse frame header
|
||||
FalconFrameHeader header;
|
||||
header.packet = (_in->readBuf[3] | ((_in->readBuf[2] & 0b111) << 8));
|
||||
header.counter = ((_in->readBuf[2] >> 3) | (_in->readBuf[1] << 5) | ((_in->readBuf[0] & 0b111111) << 13));
|
||||
|
||||
// Pointer to the data aera of the frame
|
||||
uint8_t* data = _in->readBuf + 4;
|
||||
int dataLen = 1191;
|
||||
|
||||
// If a frame was missed, cancel reading the current packet
|
||||
if (lastCounter + 1 != header.counter) {
|
||||
packetRead = -1;
|
||||
}
|
||||
lastCounter = header.counter;
|
||||
|
||||
// If frame is just a continuation of a single packet, save it
|
||||
// If we're not currently reading a packet
|
||||
if (header.packet == 2047 && packetRead >= 0) {
|
||||
memcpy(packet + packetRead, data, dataLen);
|
||||
packetRead += dataLen;
|
||||
_in->flush();
|
||||
printf("Wow, all data\n");
|
||||
return count;
|
||||
}
|
||||
else if (header.packet == 2047) {
|
||||
printf("Wow, all data\n");
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
// Finish reading the last package and send it
|
||||
if (packetRead >= 0) {
|
||||
memcpy(packet + packetRead, data, header.packet);
|
||||
memcpy(out.writeBuf, packet, packetRead + header.packet);
|
||||
out.swap(packetRead + header.packet);
|
||||
packetRead = -1;
|
||||
}
|
||||
|
||||
// Iterate through every packet of the frame
|
||||
for (int i = header.packet; i < dataLen;) {
|
||||
// First, check if we can read the header. If not, save and wait for next frame
|
||||
if (dataLen - i < 4) {
|
||||
packetRead = dataLen - i;
|
||||
memcpy(packet, &data[i], packetRead);
|
||||
break;
|
||||
}
|
||||
|
||||
// Extract packet length
|
||||
uint16_t length = (((data[i] & 0b1111) << 8) | data[i + 1]) + 2;
|
||||
|
||||
// Check if it's not an invalid zero length packet
|
||||
if (length <= 2) {
|
||||
packetRead = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t pktId = ((uint64_t)data[i + 2] << 56) | ((uint64_t)data[i + 3] << 48) | ((uint64_t)data[i + 4] << 40) | ((uint64_t)data[i + 5] << 32)
|
||||
| ((uint64_t)data[i + 6] << 24) | ((uint64_t)data[i + 7] << 16) | ((uint64_t)data[i + 8] << 8) | data[i + 9];
|
||||
|
||||
// If the packet doesn't fit the frame, save and go to next frame
|
||||
if (dataLen - i < length) {
|
||||
packetRead = dataLen - i;
|
||||
memcpy(packet, &data[i], packetRead);
|
||||
break;
|
||||
}
|
||||
|
||||
// Here, the package fits fully, read it and jump to the next
|
||||
memcpy(out.writeBuf, &data[i], length);
|
||||
out.swap(length);
|
||||
i += length;
|
||||
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
uint32_t lastCounter = 0;
|
||||
|
||||
int packetRead = -1;
|
||||
uint8_t packet[0x4008];
|
||||
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/window.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp {
|
||||
|
||||
template <class T>
|
||||
class FIR : public generic_block<FIR<T>> {
|
||||
public:
|
||||
FIR() {}
|
||||
|
||||
FIR(stream<T>* in, dsp::filter_window::generic_window* window) { init(in, window); }
|
||||
|
||||
~FIR() {
|
||||
if (!generic_block<FIR<T>>::_block_init) { return; }
|
||||
generic_block<FIR<T>>::stop();
|
||||
volk_free(buffer);
|
||||
volk_free(taps);
|
||||
generic_block<FIR<T>>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<T>* in, dsp::filter_window::generic_window* window) {
|
||||
_in = in;
|
||||
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
window->createTaps(taps, tapCount);
|
||||
|
||||
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
|
||||
bufStart = &buffer[tapCount];
|
||||
generic_block<FIR<T>>::registerInput(_in);
|
||||
generic_block<FIR<T>>::registerOutput(&out);
|
||||
generic_block<FIR<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<FIR<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FIR<T>>::ctrlMtx);
|
||||
generic_block<FIR<T>>::tempStop();
|
||||
generic_block<FIR<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FIR<T>>::registerInput(_in);
|
||||
generic_block<FIR<T>>::tempStart();
|
||||
}
|
||||
|
||||
void updateWindow(dsp::filter_window::generic_window* window) {
|
||||
assert(generic_block<FIR<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FIR<T>>::ctrlMtx);
|
||||
_window = window;
|
||||
volk_free(taps);
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
bufStart = &buffer[tapCount];
|
||||
window->createTaps(taps, tapCount);
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
generic_block<FIR<T>>::ctrlMtx.lock();
|
||||
|
||||
memcpy(bufStart, _in->readBuf, count * sizeof(T));
|
||||
_in->flush();
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
volk_32f_x2_dot_prod_32f((float*)&out.writeBuf[i], (float*)&buffer[i+1], taps, tapCount);
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[i], (lv_32fc_t*)&buffer[i+1], taps, tapCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
memmove(buffer, &buffer[count], tapCount * sizeof(T));
|
||||
|
||||
generic_block<FIR<T>>::ctrlMtx.unlock();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
stream<T>* _in;
|
||||
|
||||
dsp::filter_window::generic_window* _window;
|
||||
|
||||
T* bufStart;
|
||||
T* buffer;
|
||||
int tapCount;
|
||||
float* taps;
|
||||
|
||||
};
|
||||
|
||||
class ComplexFIR : public generic_block<ComplexFIR> {
|
||||
public:
|
||||
ComplexFIR() {}
|
||||
|
||||
ComplexFIR(stream<complex_t>* in, dsp::filter_window::generic_complex_window* window) { init(in, window); }
|
||||
|
||||
~ComplexFIR() {
|
||||
if (!generic_block<ComplexFIR>::_block_init) { return; }
|
||||
generic_block<ComplexFIR>::stop();
|
||||
volk_free(buffer);
|
||||
volk_free(taps);
|
||||
generic_block<ComplexFIR>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, dsp::filter_window::generic_complex_window* window) {
|
||||
_in = in;
|
||||
|
||||
tapCount = window->getTapCount();
|
||||
taps = (complex_t*)volk_malloc(tapCount * sizeof(complex_t), volk_get_alignment());
|
||||
window->createTaps(taps, tapCount);
|
||||
|
||||
buffer = (complex_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(complex_t) * 2, volk_get_alignment());
|
||||
bufStart = &buffer[tapCount];
|
||||
generic_block<ComplexFIR>::registerInput(_in);
|
||||
generic_block<ComplexFIR>::registerOutput(&out);
|
||||
generic_block<ComplexFIR>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<ComplexFIR>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexFIR>::ctrlMtx);
|
||||
generic_block<ComplexFIR>::tempStop();
|
||||
generic_block<ComplexFIR>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexFIR>::registerInput(_in);
|
||||
generic_block<ComplexFIR>::tempStart();
|
||||
}
|
||||
|
||||
void updateWindow(dsp::filter_window::generic_complex_window* window) {
|
||||
assert(generic_block<ComplexFIR>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexFIR>::ctrlMtx);
|
||||
_window = window;
|
||||
volk_free(taps);
|
||||
tapCount = window->getTapCount();
|
||||
taps = (complex_t*)volk_malloc(tapCount * sizeof(complex_t), volk_get_alignment());
|
||||
bufStart = &buffer[tapCount];
|
||||
window->createTaps(taps, tapCount);
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
generic_block<ComplexFIR>::ctrlMtx.lock();
|
||||
|
||||
memcpy(bufStart, _in->readBuf, count * sizeof(complex_t));
|
||||
_in->flush();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[i], (lv_32fc_t*)&buffer[i+1], (lv_32fc_t*)taps, tapCount);
|
||||
}
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
|
||||
memmove(buffer, &buffer[count], tapCount * sizeof(complex_t));
|
||||
|
||||
generic_block<ComplexFIR>::ctrlMtx.unlock();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
|
||||
dsp::filter_window::generic_complex_window* _window;
|
||||
|
||||
complex_t* bufStart;
|
||||
complex_t* buffer;
|
||||
int tapCount;
|
||||
complex_t* taps;
|
||||
|
||||
};
|
||||
|
||||
class BFMDeemp : public generic_block<BFMDeemp> {
|
||||
public:
|
||||
BFMDeemp() {}
|
||||
|
||||
BFMDeemp(stream<stereo_t>* in, float sampleRate, float tau) { init(in, sampleRate, tau); }
|
||||
|
||||
void init(stream<stereo_t>* in, float sampleRate, float tau) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_tau = tau;
|
||||
float dt = 1.0f / _sampleRate;
|
||||
alpha = dt / (_tau + dt);
|
||||
generic_block<BFMDeemp>::registerInput(_in);
|
||||
generic_block<BFMDeemp>::registerOutput(&out);
|
||||
generic_block<BFMDeemp>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<stereo_t>* in) {
|
||||
assert(generic_block<BFMDeemp>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<BFMDeemp>::ctrlMtx);
|
||||
generic_block<BFMDeemp>::tempStop();
|
||||
generic_block<BFMDeemp>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<BFMDeemp>::registerInput(_in);
|
||||
generic_block<BFMDeemp>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_block<BFMDeemp>::_block_init);
|
||||
_sampleRate = sampleRate;
|
||||
float dt = 1.0f / _sampleRate;
|
||||
alpha = dt / (_tau + dt);
|
||||
}
|
||||
|
||||
void setTau(float tau) {
|
||||
assert(generic_block<BFMDeemp>::_block_init);
|
||||
_tau = tau;
|
||||
float dt = 1.0f / _sampleRate;
|
||||
alpha = dt / (_tau + dt);
|
||||
}
|
||||
|
||||
int run() {
|
||||
count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (bypass) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(stereo_t));
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
if (isnan(lastOutL)) {
|
||||
lastOutL = 0.0f;
|
||||
}
|
||||
if (isnan(lastOutR)) {
|
||||
lastOutR = 0.0f;
|
||||
}
|
||||
out.writeBuf[0].l = (alpha * _in->readBuf[0].l) + ((1-alpha) * lastOutL);
|
||||
out.writeBuf[0].r = (alpha * _in->readBuf[0].r) + ((1-alpha) * lastOutR);
|
||||
for (int i = 1; i < count; i++) {
|
||||
out.writeBuf[i].l = (alpha * _in->readBuf[i].l) + ((1 - alpha) * out.writeBuf[i - 1].l);
|
||||
out.writeBuf[i].r = (alpha * _in->readBuf[i].r) + ((1 - alpha) * out.writeBuf[i - 1].r);
|
||||
}
|
||||
lastOutL = out.writeBuf[count - 1].l;
|
||||
lastOutR = out.writeBuf[count - 1].r;
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
bool bypass = false;
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
int count;
|
||||
float lastOutL = 0.0f;
|
||||
float lastOutR = 0.0f;
|
||||
float alpha;
|
||||
float _tau;
|
||||
float _sampleRate;
|
||||
stream<stereo_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
88
core/src/dsp/filter/decimating_fir.h
Normal file
88
core/src/dsp/filter/decimating_fir.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
#include "fir.h"
|
||||
|
||||
namespace dsp::filter {
|
||||
template <class D, class T>
|
||||
class DecimatingFIR : public FIR<D, T> {
|
||||
using base_type = FIR<D, T>;
|
||||
public:
|
||||
DecimatingFIR() {}
|
||||
|
||||
DecimatingFIR(stream<D>* in, tap<T>& taps, int decimation) { init(in, taps, decimation); }
|
||||
|
||||
void init(stream<D>* in, tap<T>& taps, int decimation) {
|
||||
_decimation = decimation;
|
||||
base_type::init(in, taps);
|
||||
}
|
||||
|
||||
void setTaps(tap<T>& taps) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
offset = 0;
|
||||
base_type::setTaps(taps);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setDecimation(int decimation) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_decimation = decimation;
|
||||
offset = 0;
|
||||
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;
|
||||
base_type::reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const D* in, D* out) {
|
||||
// Copy data to work buffer
|
||||
memcpy(base_type::bufStart, in, count * sizeof(D));
|
||||
|
||||
// Do convolution
|
||||
int outCount = 0;
|
||||
for (; offset < count; offset += _decimation) {
|
||||
if constexpr (std::is_same_v<D, float> && std::is_same_v<T, float>) {
|
||||
volk_32f_x2_dot_prod_32f(&out[outCount++], &base_type::buffer[offset], base_type::_taps.taps, base_type::_taps.size);
|
||||
}
|
||||
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, float>) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out[outCount++], (lv_32fc_t*)&base_type::buffer[offset], base_type::_taps.taps, base_type::_taps.size);
|
||||
}
|
||||
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, complex_t>) {
|
||||
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out[outCount++], (lv_32fc_t*)&base_type::buffer[offset], (lv_32fc_t*)base_type::_taps.taps, base_type::_taps.size);
|
||||
}
|
||||
}
|
||||
offset -= count;
|
||||
|
||||
// Move unused data
|
||||
memmove(base_type::buffer, &base_type::buffer[count], (base_type::_taps.size - 1) * sizeof(D));
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
protected:
|
||||
int _decimation;
|
||||
int offset = 0;
|
||||
};
|
||||
}
|
||||
102
core/src/dsp/filter/deephasis.h
Normal file
102
core/src/dsp/filter/deephasis.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
|
||||
namespace dsp::filter {
|
||||
template<class T>
|
||||
class Deemphasis : public Processor<T, T> {
|
||||
using base_type = Processor<T, T>;
|
||||
public:
|
||||
Deemphasis() {}
|
||||
|
||||
Deemphasis(stream<T>* in, double tau, double samplerate) {}
|
||||
|
||||
void init(stream<T>* in, double tau, double samplerate) {
|
||||
_tau = tau;
|
||||
_samplerate = samplerate;
|
||||
|
||||
updateAlpha();
|
||||
|
||||
// Initialize state
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
lastOut = 0;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
lastOut = { 0, 0 };
|
||||
}
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setTau(double tau) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_tau = tau;
|
||||
updateAlpha();
|
||||
}
|
||||
|
||||
void setSamplerate(double samplerate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_samplerate = samplerate;
|
||||
updateAlpha();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
lastOut = 0;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
lastOut = { 0, 0 };
|
||||
}
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const T* in, T* out) {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
out[0] = (alpha * in[0]) + ((1 - alpha) * lastOut);
|
||||
for (int i = 1; i < count; i++) {
|
||||
out[i] = (alpha * in[i]) + ((1 - alpha) * out[i - 1]);
|
||||
}
|
||||
lastOut = out[count - 1];
|
||||
}
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
out[0].l = (alpha * in[0].l) + ((1 - alpha) * lastOut.l);
|
||||
out[0].r = (alpha * in[0].r) + ((1 - alpha) * lastOut.r);
|
||||
for (int i = 1; i < count; i++) {
|
||||
out[i].l = (alpha * in[i].l) + ((1 - alpha) * out[i - 1].l);
|
||||
out[i].r = (alpha * in[i].r) + ((1 - alpha) * out[i - 1].r);
|
||||
}
|
||||
lastOut.l = out[count - 1].l;
|
||||
lastOut.r = out[count - 1].r;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//DEFAULT_PROC_RUN();
|
||||
|
||||
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:
|
||||
void updateAlpha() {
|
||||
float dt = 1.0f / _samplerate;
|
||||
alpha = dt / (_tau + dt);
|
||||
}
|
||||
|
||||
double _tau;
|
||||
double _samplerate;
|
||||
|
||||
float alpha;
|
||||
T lastOut;
|
||||
};
|
||||
}
|
||||
101
core/src/dsp/filter/fir.h
Normal file
101
core/src/dsp/filter/fir.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../taps/tap.h"
|
||||
|
||||
namespace dsp::filter {
|
||||
template <class D, class T>
|
||||
class FIR : public Processor<D, D> {
|
||||
using base_type = Processor<D, D>;
|
||||
public:
|
||||
FIR() {}
|
||||
|
||||
FIR(stream<D>* in, tap<T>& taps) { init(in, taps); }
|
||||
|
||||
~FIR() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
buffer::free(buffer);
|
||||
}
|
||||
|
||||
virtual void init(stream<D>* in, tap<T>& taps) {
|
||||
_taps = taps;
|
||||
|
||||
// Allocate and clear buffer
|
||||
buffer = buffer::alloc<D>(STREAM_BUFFER_SIZE + 64000);
|
||||
bufStart = &buffer[_taps.size - 1];
|
||||
buffer::clear<D>(buffer, _taps.size - 1);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
virtual void setTaps(tap<T>& taps) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
|
||||
int oldTC = _taps.size;
|
||||
_taps = taps;
|
||||
|
||||
// Update start of buffer
|
||||
bufStart = &buffer[_taps.size - 1];
|
||||
|
||||
// Move existing data to make transition seemless
|
||||
if (_taps.size < oldTC) {
|
||||
memmove(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D));
|
||||
}
|
||||
else if (_taps.size > oldTC) {
|
||||
memmove(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D));
|
||||
buffer::clear<D>(buffer, _taps.size - oldTC);
|
||||
}
|
||||
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
virtual void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
buffer::clear<D>(buffer, _taps.size - 1);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const D* in, D* out) {
|
||||
// Copy data to work buffer
|
||||
memcpy(bufStart, in, count * sizeof(D));
|
||||
|
||||
// Do convolution
|
||||
for (int i = 0; i < count; i++) {
|
||||
if constexpr (std::is_same_v<D, float> && std::is_same_v<T, float>) {
|
||||
volk_32f_x2_dot_prod_32f(&out[i], &buffer[i], _taps.taps, _taps.size);
|
||||
}
|
||||
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, float>) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out[i], (lv_32fc_t*)&buffer[i], _taps.taps, _taps.size);
|
||||
}
|
||||
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, complex_t>) {
|
||||
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out[i], (lv_32fc_t*)&buffer[i], (lv_32fc_t*)_taps.taps, _taps.size);
|
||||
}
|
||||
}
|
||||
|
||||
// Move unused data
|
||||
memmove(buffer, &buffer[count], (_taps.size - 1) * sizeof(D));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
virtual 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:
|
||||
tap<T> _taps;
|
||||
D* buffer;
|
||||
D* bufStart;
|
||||
};
|
||||
}
|
||||
83
core/src/dsp/hier_block.h
Normal file
83
core/src/dsp/hier_block.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
#include "block.h"
|
||||
|
||||
namespace dsp {
|
||||
class hier_block : public generic_block {
|
||||
public:
|
||||
virtual void init() {}
|
||||
|
||||
virtual ~hier_block() {
|
||||
if (!_block_init) { return; }
|
||||
stop();
|
||||
_block_init = false;
|
||||
}
|
||||
|
||||
virtual void start() {
|
||||
assert(_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
doStart();
|
||||
}
|
||||
|
||||
virtual void stop() {
|
||||
assert(_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
doStop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void tempStart() {
|
||||
assert(_block_init);
|
||||
if (!tempStopDepth || --tempStopDepth) { return; }
|
||||
if (tempStopped) {
|
||||
doStart();
|
||||
tempStopped = false;
|
||||
}
|
||||
}
|
||||
|
||||
void tempStop() {
|
||||
assert(_block_init);
|
||||
if (tempStopDepth++) { return; }
|
||||
if (running && !tempStopped) {
|
||||
doStop();
|
||||
tempStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void doStart() {
|
||||
for (auto& block : blocks) {
|
||||
block->start();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void doStop() {
|
||||
for (auto& block : blocks) {
|
||||
block->stop();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<generic_block*> blocks;
|
||||
bool tempStopped = false;
|
||||
bool running = false;
|
||||
int tempStopDepth = 0;
|
||||
|
||||
protected:
|
||||
void registerBlock(generic_block* block) {
|
||||
blocks.push_back(block);
|
||||
}
|
||||
|
||||
void unregisterBlock(generic_block* block) {
|
||||
blocks.erase(std::remove(blocks.begin(), blocks.end(), block), blocks.end());
|
||||
}
|
||||
|
||||
bool _block_init = false;
|
||||
std::recursive_mutex ctrlMtx;
|
||||
};
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
const int INTERP_TAP_COUNT = 8;
|
||||
const int INTERP_STEPS = 128;
|
||||
|
||||
const float INTERP_TAPS[INTERP_STEPS + 1][INTERP_TAP_COUNT] = {
|
||||
{ 0.00000e+00, 0.00000e+00, 0.00000e+00, 1.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00 },
|
||||
{ -1.98993e-04, 1.24642e-03, -5.41054e-03, 9.98534e-01, 7.89295e-03, -2.76968e-03, 8.53777e-04, -1.54700e-04 },
|
||||
{ -3.96391e-04, 2.47942e-03, -1.07209e-02, 9.96891e-01, 1.58840e-02, -5.55134e-03, 1.70888e-03, -3.09412e-04 },
|
||||
{ -5.92100e-04, 3.69852e-03, -1.59305e-02, 9.95074e-01, 2.39714e-02, -8.34364e-03, 2.56486e-03, -4.64053e-04 },
|
||||
{ -7.86031e-04, 4.90322e-03, -2.10389e-02, 9.93082e-01, 3.21531e-02, -1.11453e-02, 3.42130e-03, -6.18544e-04 },
|
||||
{ -9.78093e-04, 6.09305e-03, -2.60456e-02, 9.90917e-01, 4.04274e-02, -1.39548e-02, 4.27773e-03, -7.72802e-04 },
|
||||
{ -1.16820e-03, 7.26755e-03, -3.09503e-02, 9.88580e-01, 4.87921e-02, -1.67710e-02, 5.13372e-03, -9.26747e-04 },
|
||||
{ -1.35627e-03, 8.42626e-03, -3.57525e-02, 9.86071e-01, 5.72454e-02, -1.95925e-02, 5.98883e-03, -1.08030e-03 },
|
||||
{ -1.54221e-03, 9.56876e-03, -4.04519e-02, 9.83392e-01, 6.57852e-02, -2.24178e-02, 6.84261e-03, -1.23337e-03 },
|
||||
{ -1.72594e-03, 1.06946e-02, -4.50483e-02, 9.80543e-01, 7.44095e-02, -2.52457e-02, 7.69462e-03, -1.38589e-03 },
|
||||
{ -1.90738e-03, 1.18034e-02, -4.95412e-02, 9.77526e-01, 8.31162e-02, -2.80746e-02, 8.54441e-03, -1.53777e-03 },
|
||||
{ -2.08645e-03, 1.28947e-02, -5.39305e-02, 9.74342e-01, 9.19033e-02, -3.09033e-02, 9.39154e-03, -1.68894e-03 },
|
||||
{ -2.26307e-03, 1.39681e-02, -5.82159e-02, 9.70992e-01, 1.00769e-01, -3.37303e-02, 1.02356e-02, -1.83931e-03 },
|
||||
{ -2.43718e-03, 1.50233e-02, -6.23972e-02, 9.67477e-01, 1.09710e-01, -3.65541e-02, 1.10760e-02, -1.98880e-03 },
|
||||
{ -2.60868e-03, 1.60599e-02, -6.64743e-02, 9.63798e-01, 1.18725e-01, -3.93735e-02, 1.19125e-02, -2.13733e-03 },
|
||||
{ -2.77751e-03, 1.70776e-02, -7.04471e-02, 9.59958e-01, 1.27812e-01, -4.21869e-02, 1.27445e-02, -2.28483e-03 },
|
||||
{ -2.94361e-03, 1.80759e-02, -7.43154e-02, 9.55956e-01, 1.36968e-01, -4.49929e-02, 1.35716e-02, -2.43121e-03 },
|
||||
{ -3.10689e-03, 1.90545e-02, -7.80792e-02, 9.51795e-01, 1.46192e-01, -4.77900e-02, 1.43934e-02, -2.57640e-03 },
|
||||
{ -3.26730e-03, 2.00132e-02, -8.17385e-02, 9.47477e-01, 1.55480e-01, -5.05770e-02, 1.52095e-02, -2.72032e-03 },
|
||||
{ -3.42477e-03, 2.09516e-02, -8.52933e-02, 9.43001e-01, 1.64831e-01, -5.33522e-02, 1.60193e-02, -2.86289e-03 },
|
||||
{ -3.57923e-03, 2.18695e-02, -8.87435e-02, 9.38371e-01, 1.74242e-01, -5.61142e-02, 1.68225e-02, -3.00403e-03 },
|
||||
{ -3.73062e-03, 2.27664e-02, -9.20893e-02, 9.33586e-01, 1.83711e-01, -5.88617e-02, 1.76185e-02, -3.14367e-03 },
|
||||
{ -3.87888e-03, 2.36423e-02, -9.53307e-02, 9.28650e-01, 1.93236e-01, -6.15931e-02, 1.84071e-02, -3.28174e-03 },
|
||||
{ -4.02397e-03, 2.44967e-02, -9.84679e-02, 9.23564e-01, 2.02814e-01, -6.43069e-02, 1.91877e-02, -3.41815e-03 },
|
||||
{ -4.16581e-03, 2.53295e-02, -1.01501e-01, 9.18329e-01, 2.12443e-01, -6.70018e-02, 1.99599e-02, -3.55283e-03 },
|
||||
{ -4.30435e-03, 2.61404e-02, -1.04430e-01, 9.12947e-01, 2.22120e-01, -6.96762e-02, 2.07233e-02, -3.68570e-03 },
|
||||
{ -4.43955e-03, 2.69293e-02, -1.07256e-01, 9.07420e-01, 2.31843e-01, -7.23286e-02, 2.14774e-02, -3.81671e-03 },
|
||||
{ -4.57135e-03, 2.76957e-02, -1.09978e-01, 9.01749e-01, 2.41609e-01, -7.49577e-02, 2.22218e-02, -3.94576e-03 },
|
||||
{ -4.69970e-03, 2.84397e-02, -1.12597e-01, 8.95936e-01, 2.51417e-01, -7.75620e-02, 2.29562e-02, -4.07279e-03 },
|
||||
{ -4.82456e-03, 2.91609e-02, -1.15113e-01, 8.89984e-01, 2.61263e-01, -8.01399e-02, 2.36801e-02, -4.19774e-03 },
|
||||
{ -4.94589e-03, 2.98593e-02, -1.17526e-01, 8.83893e-01, 2.71144e-01, -8.26900e-02, 2.43930e-02, -4.32052e-03 },
|
||||
{ -5.06363e-03, 3.05345e-02, -1.19837e-01, 8.77666e-01, 2.81060e-01, -8.52109e-02, 2.50946e-02, -4.44107e-03 },
|
||||
{ -5.17776e-03, 3.11866e-02, -1.22047e-01, 8.71305e-01, 2.91006e-01, -8.77011e-02, 2.57844e-02, -4.55932e-03 },
|
||||
{ -5.28823e-03, 3.18153e-02, -1.24154e-01, 8.64812e-01, 3.00980e-01, -9.01591e-02, 2.64621e-02, -4.67520e-03 },
|
||||
{ -5.39500e-03, 3.24205e-02, -1.26161e-01, 8.58189e-01, 3.10980e-01, -9.25834e-02, 2.71272e-02, -4.78866e-03 },
|
||||
{ -5.49804e-03, 3.30021e-02, -1.28068e-01, 8.51437e-01, 3.21004e-01, -9.49727e-02, 2.77794e-02, -4.89961e-03 },
|
||||
{ -5.59731e-03, 3.35600e-02, -1.29874e-01, 8.44559e-01, 3.31048e-01, -9.73254e-02, 2.84182e-02, -5.00800e-03 },
|
||||
{ -5.69280e-03, 3.40940e-02, -1.31581e-01, 8.37557e-01, 3.41109e-01, -9.96402e-02, 2.90433e-02, -5.11376e-03 },
|
||||
{ -5.78446e-03, 3.46042e-02, -1.33189e-01, 8.30432e-01, 3.51186e-01, -1.01915e-01, 2.96543e-02, -5.21683e-03 },
|
||||
{ -5.87227e-03, 3.50903e-02, -1.34699e-01, 8.23188e-01, 3.61276e-01, -1.04150e-01, 3.02507e-02, -5.31716e-03 },
|
||||
{ -5.95620e-03, 3.55525e-02, -1.36111e-01, 8.15826e-01, 3.71376e-01, -1.06342e-01, 3.08323e-02, -5.41467e-03 },
|
||||
{ -6.03624e-03, 3.59905e-02, -1.37426e-01, 8.08348e-01, 3.81484e-01, -1.08490e-01, 3.13987e-02, -5.50931e-03 },
|
||||
{ -6.11236e-03, 3.64044e-02, -1.38644e-01, 8.00757e-01, 3.91596e-01, -1.10593e-01, 3.19495e-02, -5.60103e-03 },
|
||||
{ -6.18454e-03, 3.67941e-02, -1.39767e-01, 7.93055e-01, 4.01710e-01, -1.12650e-01, 3.24843e-02, -5.68976e-03 },
|
||||
{ -6.25277e-03, 3.71596e-02, -1.40794e-01, 7.85244e-01, 4.11823e-01, -1.14659e-01, 3.30027e-02, -5.77544e-03 },
|
||||
{ -6.31703e-03, 3.75010e-02, -1.41727e-01, 7.77327e-01, 4.21934e-01, -1.16618e-01, 3.35046e-02, -5.85804e-03 },
|
||||
{ -6.37730e-03, 3.78182e-02, -1.42566e-01, 7.69305e-01, 4.32038e-01, -1.18526e-01, 3.39894e-02, -5.93749e-03 },
|
||||
{ -6.43358e-03, 3.81111e-02, -1.43313e-01, 7.61181e-01, 4.42134e-01, -1.20382e-01, 3.44568e-02, -6.01374e-03 },
|
||||
{ -6.48585e-03, 3.83800e-02, -1.43968e-01, 7.52958e-01, 4.52218e-01, -1.22185e-01, 3.49066e-02, -6.08674e-03 },
|
||||
{ -6.53412e-03, 3.86247e-02, -1.44531e-01, 7.44637e-01, 4.62289e-01, -1.23933e-01, 3.53384e-02, -6.15644e-03 },
|
||||
{ -6.57836e-03, 3.88454e-02, -1.45004e-01, 7.36222e-01, 4.72342e-01, -1.25624e-01, 3.57519e-02, -6.22280e-03 },
|
||||
{ -6.61859e-03, 3.90420e-02, -1.45387e-01, 7.27714e-01, 4.82377e-01, -1.27258e-01, 3.61468e-02, -6.28577e-03 },
|
||||
{ -6.65479e-03, 3.92147e-02, -1.45682e-01, 7.19116e-01, 4.92389e-01, -1.28832e-01, 3.65227e-02, -6.34530e-03 },
|
||||
{ -6.68698e-03, 3.93636e-02, -1.45889e-01, 7.10431e-01, 5.02377e-01, -1.30347e-01, 3.68795e-02, -6.40135e-03 },
|
||||
{ -6.71514e-03, 3.94886e-02, -1.46009e-01, 7.01661e-01, 5.12337e-01, -1.31800e-01, 3.72167e-02, -6.45388e-03 },
|
||||
{ -6.73929e-03, 3.95900e-02, -1.46043e-01, 6.92808e-01, 5.22267e-01, -1.33190e-01, 3.75341e-02, -6.50285e-03 },
|
||||
{ -6.75943e-03, 3.96678e-02, -1.45993e-01, 6.83875e-01, 5.32164e-01, -1.34515e-01, 3.78315e-02, -6.54823e-03 },
|
||||
{ -6.77557e-03, 3.97222e-02, -1.45859e-01, 6.74865e-01, 5.42025e-01, -1.35775e-01, 3.81085e-02, -6.58996e-03 },
|
||||
{ -6.78771e-03, 3.97532e-02, -1.45641e-01, 6.65779e-01, 5.51849e-01, -1.36969e-01, 3.83650e-02, -6.62802e-03 },
|
||||
{ -6.79588e-03, 3.97610e-02, -1.45343e-01, 6.56621e-01, 5.61631e-01, -1.38094e-01, 3.86006e-02, -6.66238e-03 },
|
||||
{ -6.80007e-03, 3.97458e-02, -1.44963e-01, 6.47394e-01, 5.71370e-01, -1.39150e-01, 3.88151e-02, -6.69300e-03 },
|
||||
{ -6.80032e-03, 3.97077e-02, -1.44503e-01, 6.38099e-01, 5.81063e-01, -1.40136e-01, 3.90083e-02, -6.71985e-03 },
|
||||
{ -6.79662e-03, 3.96469e-02, -1.43965e-01, 6.28739e-01, 5.90706e-01, -1.41050e-01, 3.91800e-02, -6.74291e-03 },
|
||||
{ -6.78902e-03, 3.95635e-02, -1.43350e-01, 6.19318e-01, 6.00298e-01, -1.41891e-01, 3.93299e-02, -6.76214e-03 },
|
||||
{ -6.77751e-03, 3.94578e-02, -1.42658e-01, 6.09836e-01, 6.09836e-01, -1.42658e-01, 3.94578e-02, -6.77751e-03 },
|
||||
{ -6.76214e-03, 3.93299e-02, -1.41891e-01, 6.00298e-01, 6.19318e-01, -1.43350e-01, 3.95635e-02, -6.78902e-03 },
|
||||
{ -6.74291e-03, 3.91800e-02, -1.41050e-01, 5.90706e-01, 6.28739e-01, -1.43965e-01, 3.96469e-02, -6.79662e-03 },
|
||||
{ -6.71985e-03, 3.90083e-02, -1.40136e-01, 5.81063e-01, 6.38099e-01, -1.44503e-01, 3.97077e-02, -6.80032e-03 },
|
||||
{ -6.69300e-03, 3.88151e-02, -1.39150e-01, 5.71370e-01, 6.47394e-01, -1.44963e-01, 3.97458e-02, -6.80007e-03 },
|
||||
{ -6.66238e-03, 3.86006e-02, -1.38094e-01, 5.61631e-01, 6.56621e-01, -1.45343e-01, 3.97610e-02, -6.79588e-03 },
|
||||
{ -6.62802e-03, 3.83650e-02, -1.36969e-01, 5.51849e-01, 6.65779e-01, -1.45641e-01, 3.97532e-02, -6.78771e-03 },
|
||||
{ -6.58996e-03, 3.81085e-02, -1.35775e-01, 5.42025e-01, 6.74865e-01, -1.45859e-01, 3.97222e-02, -6.77557e-03 },
|
||||
{ -6.54823e-03, 3.78315e-02, -1.34515e-01, 5.32164e-01, 6.83875e-01, -1.45993e-01, 3.96678e-02, -6.75943e-03 },
|
||||
{ -6.50285e-03, 3.75341e-02, -1.33190e-01, 5.22267e-01, 6.92808e-01, -1.46043e-01, 3.95900e-02, -6.73929e-03 },
|
||||
{ -6.45388e-03, 3.72167e-02, -1.31800e-01, 5.12337e-01, 7.01661e-01, -1.46009e-01, 3.94886e-02, -6.71514e-03 },
|
||||
{ -6.40135e-03, 3.68795e-02, -1.30347e-01, 5.02377e-01, 7.10431e-01, -1.45889e-01, 3.93636e-02, -6.68698e-03 },
|
||||
{ -6.34530e-03, 3.65227e-02, -1.28832e-01, 4.92389e-01, 7.19116e-01, -1.45682e-01, 3.92147e-02, -6.65479e-03 },
|
||||
{ -6.28577e-03, 3.61468e-02, -1.27258e-01, 4.82377e-01, 7.27714e-01, -1.45387e-01, 3.90420e-02, -6.61859e-03 },
|
||||
{ -6.22280e-03, 3.57519e-02, -1.25624e-01, 4.72342e-01, 7.36222e-01, -1.45004e-01, 3.88454e-02, -6.57836e-03 },
|
||||
{ -6.15644e-03, 3.53384e-02, -1.23933e-01, 4.62289e-01, 7.44637e-01, -1.44531e-01, 3.86247e-02, -6.53412e-03 },
|
||||
{ -6.08674e-03, 3.49066e-02, -1.22185e-01, 4.52218e-01, 7.52958e-01, -1.43968e-01, 3.83800e-02, -6.48585e-03 },
|
||||
{ -6.01374e-03, 3.44568e-02, -1.20382e-01, 4.42134e-01, 7.61181e-01, -1.43313e-01, 3.81111e-02, -6.43358e-03 },
|
||||
{ -5.93749e-03, 3.39894e-02, -1.18526e-01, 4.32038e-01, 7.69305e-01, -1.42566e-01, 3.78182e-02, -6.37730e-03 },
|
||||
{ -5.85804e-03, 3.35046e-02, -1.16618e-01, 4.21934e-01, 7.77327e-01, -1.41727e-01, 3.75010e-02, -6.31703e-03 },
|
||||
{ -5.77544e-03, 3.30027e-02, -1.14659e-01, 4.11823e-01, 7.85244e-01, -1.40794e-01, 3.71596e-02, -6.25277e-03 },
|
||||
{ -5.68976e-03, 3.24843e-02, -1.12650e-01, 4.01710e-01, 7.93055e-01, -1.39767e-01, 3.67941e-02, -6.18454e-03 },
|
||||
{ -5.60103e-03, 3.19495e-02, -1.10593e-01, 3.91596e-01, 8.00757e-01, -1.38644e-01, 3.64044e-02, -6.11236e-03 },
|
||||
{ -5.50931e-03, 3.13987e-02, -1.08490e-01, 3.81484e-01, 8.08348e-01, -1.37426e-01, 3.59905e-02, -6.03624e-03 },
|
||||
{ -5.41467e-03, 3.08323e-02, -1.06342e-01, 3.71376e-01, 8.15826e-01, -1.36111e-01, 3.55525e-02, -5.95620e-03 },
|
||||
{ -5.31716e-03, 3.02507e-02, -1.04150e-01, 3.61276e-01, 8.23188e-01, -1.34699e-01, 3.50903e-02, -5.87227e-03 },
|
||||
{ -5.21683e-03, 2.96543e-02, -1.01915e-01, 3.51186e-01, 8.30432e-01, -1.33189e-01, 3.46042e-02, -5.78446e-03 },
|
||||
{ -5.11376e-03, 2.90433e-02, -9.96402e-02, 3.41109e-01, 8.37557e-01, -1.31581e-01, 3.40940e-02, -5.69280e-03 },
|
||||
{ -5.00800e-03, 2.84182e-02, -9.73254e-02, 3.31048e-01, 8.44559e-01, -1.29874e-01, 3.35600e-02, -5.59731e-03 },
|
||||
{ -4.89961e-03, 2.77794e-02, -9.49727e-02, 3.21004e-01, 8.51437e-01, -1.28068e-01, 3.30021e-02, -5.49804e-03 },
|
||||
{ -4.78866e-03, 2.71272e-02, -9.25834e-02, 3.10980e-01, 8.58189e-01, -1.26161e-01, 3.24205e-02, -5.39500e-03 },
|
||||
{ -4.67520e-03, 2.64621e-02, -9.01591e-02, 3.00980e-01, 8.64812e-01, -1.24154e-01, 3.18153e-02, -5.28823e-03 },
|
||||
{ -4.55932e-03, 2.57844e-02, -8.77011e-02, 2.91006e-01, 8.71305e-01, -1.22047e-01, 3.11866e-02, -5.17776e-03 },
|
||||
{ -4.44107e-03, 2.50946e-02, -8.52109e-02, 2.81060e-01, 8.77666e-01, -1.19837e-01, 3.05345e-02, -5.06363e-03 },
|
||||
{ -4.32052e-03, 2.43930e-02, -8.26900e-02, 2.71144e-01, 8.83893e-01, -1.17526e-01, 2.98593e-02, -4.94589e-03 },
|
||||
{ -4.19774e-03, 2.36801e-02, -8.01399e-02, 2.61263e-01, 8.89984e-01, -1.15113e-01, 2.91609e-02, -4.82456e-03 },
|
||||
{ -4.07279e-03, 2.29562e-02, -7.75620e-02, 2.51417e-01, 8.95936e-01, -1.12597e-01, 2.84397e-02, -4.69970e-03 },
|
||||
{ -3.94576e-03, 2.22218e-02, -7.49577e-02, 2.41609e-01, 9.01749e-01, -1.09978e-01, 2.76957e-02, -4.57135e-03 },
|
||||
{ -3.81671e-03, 2.14774e-02, -7.23286e-02, 2.31843e-01, 9.07420e-01, -1.07256e-01, 2.69293e-02, -4.43955e-03 },
|
||||
{ -3.68570e-03, 2.07233e-02, -6.96762e-02, 2.22120e-01, 9.12947e-01, -1.04430e-01, 2.61404e-02, -4.30435e-03 },
|
||||
{ -3.55283e-03, 1.99599e-02, -6.70018e-02, 2.12443e-01, 9.18329e-01, -1.01501e-01, 2.53295e-02, -4.16581e-03 },
|
||||
{ -3.41815e-03, 1.91877e-02, -6.43069e-02, 2.02814e-01, 9.23564e-01, -9.84679e-02, 2.44967e-02, -4.02397e-03 },
|
||||
{ -3.28174e-03, 1.84071e-02, -6.15931e-02, 1.93236e-01, 9.28650e-01, -9.53307e-02, 2.36423e-02, -3.87888e-03 },
|
||||
{ -3.14367e-03, 1.76185e-02, -5.88617e-02, 1.83711e-01, 9.33586e-01, -9.20893e-02, 2.27664e-02, -3.73062e-03 },
|
||||
{ -3.00403e-03, 1.68225e-02, -5.61142e-02, 1.74242e-01, 9.38371e-01, -8.87435e-02, 2.18695e-02, -3.57923e-03 },
|
||||
{ -2.86289e-03, 1.60193e-02, -5.33522e-02, 1.64831e-01, 9.43001e-01, -8.52933e-02, 2.09516e-02, -3.42477e-03 },
|
||||
{ -2.72032e-03, 1.52095e-02, -5.05770e-02, 1.55480e-01, 9.47477e-01, -8.17385e-02, 2.00132e-02, -3.26730e-03 },
|
||||
{ -2.57640e-03, 1.43934e-02, -4.77900e-02, 1.46192e-01, 9.51795e-01, -7.80792e-02, 1.90545e-02, -3.10689e-03 },
|
||||
{ -2.43121e-03, 1.35716e-02, -4.49929e-02, 1.36968e-01, 9.55956e-01, -7.43154e-02, 1.80759e-02, -2.94361e-03 },
|
||||
{ -2.28483e-03, 1.27445e-02, -4.21869e-02, 1.27812e-01, 9.59958e-01, -7.04471e-02, 1.70776e-02, -2.77751e-03 },
|
||||
{ -2.13733e-03, 1.19125e-02, -3.93735e-02, 1.18725e-01, 9.63798e-01, -6.64743e-02, 1.60599e-02, -2.60868e-03 },
|
||||
{ -1.98880e-03, 1.10760e-02, -3.65541e-02, 1.09710e-01, 9.67477e-01, -6.23972e-02, 1.50233e-02, -2.43718e-03 },
|
||||
{ -1.83931e-03, 1.02356e-02, -3.37303e-02, 1.00769e-01, 9.70992e-01, -5.82159e-02, 1.39681e-02, -2.26307e-03 },
|
||||
{ -1.68894e-03, 9.39154e-03, -3.09033e-02, 9.19033e-02, 9.74342e-01, -5.39305e-02, 1.28947e-02, -2.08645e-03 },
|
||||
{ -1.53777e-03, 8.54441e-03, -2.80746e-02, 8.31162e-02, 9.77526e-01, -4.95412e-02, 1.18034e-02, -1.90738e-03 },
|
||||
{ -1.38589e-03, 7.69462e-03, -2.52457e-02, 7.44095e-02, 9.80543e-01, -4.50483e-02, 1.06946e-02, -1.72594e-03 },
|
||||
{ -1.23337e-03, 6.84261e-03, -2.24178e-02, 6.57852e-02, 9.83392e-01, -4.04519e-02, 9.56876e-03, -1.54221e-03 },
|
||||
{ -1.08030e-03, 5.98883e-03, -1.95925e-02, 5.72454e-02, 9.86071e-01, -3.57525e-02, 8.42626e-03, -1.35627e-03 },
|
||||
{ -9.26747e-04, 5.13372e-03, -1.67710e-02, 4.87921e-02, 9.88580e-01, -3.09503e-02, 7.26755e-03, -1.16820e-03 },
|
||||
{ -7.72802e-04, 4.27773e-03, -1.39548e-02, 4.04274e-02, 9.90917e-01, -2.60456e-02, 6.09305e-03, -9.78093e-04 },
|
||||
{ -6.18544e-04, 3.42130e-03, -1.11453e-02, 3.21531e-02, 9.93082e-01, -2.10389e-02, 4.90322e-03, -7.86031e-04 },
|
||||
{ -4.64053e-04, 2.56486e-03, -8.34364e-03, 2.39714e-02, 9.95074e-01, -1.59305e-02, 3.69852e-03, -5.92100e-04 },
|
||||
{ -3.09412e-04, 1.70888e-03, -5.55134e-03, 1.58840e-02, 9.96891e-01, -1.07209e-02, 2.47942e-03, -3.96391e-04 },
|
||||
{ -1.54700e-04, 8.53777e-04, -2.76968e-03, 7.89295e-03, 9.98534e-01, -5.41054e-03, 1.24642e-03, -1.98993e-04 },
|
||||
{ 0.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00, 1.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00 },
|
||||
};
|
||||
136
core/src/dsp/loop/agc.h
Normal file
136
core/src/dsp/loop/agc.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::loop {
|
||||
template <class T>
|
||||
class AGC : public Processor<T, T> {
|
||||
using base_type = Processor<T, T>;
|
||||
public:
|
||||
AGC() {}
|
||||
|
||||
AGC(stream<T>* in, double setPoint, double attack, double decay, double maxGain, double maxOutputAmp, double initGain = 1.0) { init(in, setPoint, attack, decay, maxGain, maxOutputAmp, initGain); }
|
||||
|
||||
void init(stream<T>* in, double setPoint, double attack, double decay, double maxGain, double maxOutputAmp, double initGain = 1.0) {
|
||||
_setPoint = setPoint;
|
||||
_attack = attack;
|
||||
_invAttack = 1.0f - _attack;
|
||||
_decay = decay;
|
||||
_invDecay = 1.0f - _decay;
|
||||
_maxGain = maxGain;
|
||||
_maxOutputAmp = maxOutputAmp;
|
||||
_initGain = initGain;
|
||||
amp = _setPoint / _initGain;
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setSetPoint(double setPoint) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_setPoint = setPoint;
|
||||
}
|
||||
|
||||
void setAttack(double attack) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_attack = attack;
|
||||
_invAttack = 1.0f - _attack;
|
||||
}
|
||||
|
||||
void setDecay(double decay) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_decay = decay;
|
||||
_invDecay = 1.0f - _decay;
|
||||
}
|
||||
|
||||
void setMaxGain(double maxGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_maxGain = maxGain;
|
||||
}
|
||||
|
||||
void setMaxOutputAmp(double maxOutputAmp) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_maxOutputAmp = maxOutputAmp;
|
||||
}
|
||||
|
||||
void setInitialGain(double initGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_initGain = initGain;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
amp = _setPoint / _initGain;
|
||||
}
|
||||
|
||||
inline int process(int count, T* in, T* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Get signal amplitude
|
||||
float inAmp, gain;
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
inAmp = in[i].amplitude();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
inAmp = fabsf(in[i]);
|
||||
}
|
||||
|
||||
// Update average amplitude
|
||||
if (inAmp != 0.0f) {
|
||||
amp = (inAmp > amp) ? ((amp * _invAttack) + (inAmp * _attack)) : ((amp * _invDecay) + (inAmp * _decay));
|
||||
gain = std::min<float>(_setPoint / amp, _maxGain);
|
||||
}
|
||||
else {
|
||||
gain = 1.0f;
|
||||
}
|
||||
|
||||
// If clipping is detected look ahead and correct
|
||||
if (inAmp*gain > _maxOutputAmp) {
|
||||
float maxAmp = 0;
|
||||
for (int j = i; j < count; j++) {
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
inAmp = in[j].amplitude();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
inAmp = fabsf(in[j]);
|
||||
}
|
||||
if (inAmp > maxAmp) { maxAmp = inAmp; }
|
||||
}
|
||||
amp = maxAmp;
|
||||
gain = std::min<float>(_setPoint / amp, _maxGain);
|
||||
}
|
||||
|
||||
// Scale output by gain
|
||||
out[i] = in[i] * gain;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
float _setPoint;
|
||||
float _attack;
|
||||
float _invAttack;
|
||||
float _decay;
|
||||
float _invDecay;
|
||||
float _maxGain;
|
||||
float _maxOutputAmp;
|
||||
float _initGain;
|
||||
|
||||
float amp = 1.0;
|
||||
|
||||
};
|
||||
}
|
||||
22
core/src/dsp/loop/carrier_tracking_pll.h
Normal file
22
core/src/dsp/loop/carrier_tracking_pll.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "pll.h"
|
||||
|
||||
namespace dsp::loop {
|
||||
class CarrierTrackingPLL : public PLL {
|
||||
using base_type = PLL;
|
||||
public:
|
||||
CarrierTrackingPLL() {}
|
||||
|
||||
CarrierTrackingPLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
|
||||
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
|
||||
}
|
||||
|
||||
inline int process(int count, complex_t* in, complex_t* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = in[i] * math::phasor(-pcl.phase);
|
||||
pcl.advance(math::normalizePhase(in[i].phase() - pcl.phase));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
}
|
||||
47
core/src/dsp/loop/costas.h
Normal file
47
core/src/dsp/loop/costas.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "pll.h"
|
||||
#include "../math/step.h"
|
||||
|
||||
namespace dsp::loop {
|
||||
template<int ORDER>
|
||||
class Costas : public PLL {
|
||||
static_assert(ORDER == 2 || ORDER == 4 || ORDER == 8, "Invalid costas order");
|
||||
using base_type = PLL;
|
||||
public:
|
||||
Costas() {}
|
||||
|
||||
Costas(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
|
||||
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
|
||||
}
|
||||
|
||||
inline int process(int count, complex_t* in, complex_t* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = in[i] * math::phasor(-pcl.phase);
|
||||
pcl.advance(errorFunction(out[i]));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
inline float errorFunction(complex_t val) {
|
||||
float err;
|
||||
if constexpr (ORDER == 2) {
|
||||
err = val.re * val.im;
|
||||
}
|
||||
if constexpr (ORDER == 4) {
|
||||
err = (math::step(val.re) * val.im) - (math::step(val.im) * val.re);
|
||||
}
|
||||
if constexpr (ORDER == 8) {
|
||||
// The way this works is it compresses order 4 constellations into the quadrants
|
||||
const float K = sqrtf(2.0) - 1.0;
|
||||
if (fabsf(val.re) >= fabsf(val.im)) {
|
||||
err = (math::step(val.re) * val.im) - (math::step(val.im) * val.re * K);
|
||||
}
|
||||
else {
|
||||
err = (math::step(val.re) * val.im * K) - (math::step(val.im) * val.re);
|
||||
}
|
||||
}
|
||||
return std::clamp<float>(err, -1.0f, 1.0f);
|
||||
}
|
||||
};
|
||||
}
|
||||
100
core/src/dsp/loop/fast_agc.h
Normal file
100
core/src/dsp/loop/fast_agc.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::loop {
|
||||
template <class T>
|
||||
class FastAGC : public Processor<T, T> {
|
||||
using base_type = Processor<T, T>;
|
||||
public:
|
||||
FastAGC() {}
|
||||
|
||||
FastAGC(stream<T>* in, double setPoint, double maxGain, double rate, double initGain = 1.0) { init(in, setPoint, maxGain, rate, initGain); }
|
||||
|
||||
void init(stream<T>* in, double setPoint, double maxGain, double rate, double initGain = 1.0) {
|
||||
_setPoint = setPoint;
|
||||
_maxGain = maxGain;
|
||||
_rate = rate;
|
||||
_initGain = initGain;
|
||||
|
||||
_gain = _initGain;
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setSetPoint(double setPoint) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_setPoint = setPoint;
|
||||
}
|
||||
|
||||
void setMaxGain(double maxGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_maxGain = maxGain;
|
||||
}
|
||||
|
||||
void setRate(double rate) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_rate = rate;
|
||||
}
|
||||
|
||||
void setInitGain(double initGain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_initGain = initGain;
|
||||
}
|
||||
|
||||
void setGain(double gain) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_gain = gain;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_gain = _initGain;
|
||||
}
|
||||
|
||||
inline int process(int count, T* in, T* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Output scaled input
|
||||
out[i] = in[i] * _gain;
|
||||
|
||||
// Calculate output amplitude
|
||||
float amp;
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
amp = fabsf(out[i]);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
amp = out[i].amplitude();
|
||||
}
|
||||
|
||||
// Update and clamp gain
|
||||
_gain += (_setPoint - amp) * _rate;
|
||||
if (_gain > _maxGain) { _gain = _maxGain; }
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
float _gain;
|
||||
float _setPoint;
|
||||
float _rate;
|
||||
float _maxGain;
|
||||
float _initGain;
|
||||
|
||||
};
|
||||
}
|
||||
95
core/src/dsp/loop/phase_control_loop.h
Normal file
95
core/src/dsp/loop/phase_control_loop.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include "../types.h"
|
||||
|
||||
namespace dsp::loop {
|
||||
template<class T, bool CLAMP_PHASE = true>
|
||||
class PhaseControlLoop {
|
||||
public:
|
||||
PhaseControlLoop() {}
|
||||
|
||||
PhaseControlLoop(T alpha, T beta, T phase, T minPhase, T maxPhase, T freq, T minFreq, T maxFreq) {
|
||||
init(alpha, beta, phase, minPhase, maxPhase, freq, minFreq, maxFreq);
|
||||
}
|
||||
|
||||
void init(T alpha, T beta, T phase, T minPhase, T maxPhase, T freq, T minFreq, T maxFreq) {
|
||||
assert(maxPhase > minPhase);
|
||||
assert(maxFreq > minFreq);
|
||||
_alpha = alpha;
|
||||
_beta = beta;
|
||||
this->phase = phase;
|
||||
_minPhase = minPhase;
|
||||
_maxPhase = maxPhase;
|
||||
this->freq = freq;
|
||||
_minFreq = minFreq;
|
||||
_maxFreq = maxFreq;
|
||||
|
||||
phaseDelta = _maxPhase - _minPhase;
|
||||
}
|
||||
|
||||
static inline void criticallyDamped(T bandwidth, T& alpha, T& beta) {
|
||||
T dampningFactor = sqrt(2.0) / 2.0;
|
||||
T denominator = (1.0 + 2.0*dampningFactor*bandwidth + bandwidth*bandwidth);
|
||||
alpha = (4 * dampningFactor * bandwidth) / denominator;
|
||||
beta = (4 * bandwidth * bandwidth) / denominator;
|
||||
}
|
||||
|
||||
void setCoefficients(T alpha, T beta) {
|
||||
_alpha = alpha;
|
||||
_beta = beta;
|
||||
}
|
||||
|
||||
void setPhaseLimits(T minPhase, T maxPhase) {
|
||||
assert(maxPhase > minPhase);
|
||||
_minPhase = minPhase;
|
||||
_maxPhase = maxPhase;
|
||||
phaseDelta = _maxPhase - _minPhase;
|
||||
clampPhase();
|
||||
}
|
||||
|
||||
void setFreqLimits(T minFreq, T maxFreq) {
|
||||
assert(maxFreq > minFreq);
|
||||
_minFreq = minFreq;
|
||||
_maxFreq = maxFreq;
|
||||
clampFreq();
|
||||
}
|
||||
|
||||
inline void advance(T error) {
|
||||
// Increment and clamp frequency
|
||||
freq += _beta * error;
|
||||
clampFreq();
|
||||
|
||||
// Increment and clamp phase
|
||||
phase += freq + (_alpha * error);
|
||||
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||
}
|
||||
|
||||
inline void advancePhase() {
|
||||
phase += freq;
|
||||
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||
}
|
||||
|
||||
T freq;
|
||||
T phase;
|
||||
|
||||
protected:
|
||||
inline void clampFreq() {
|
||||
if (freq > _maxFreq) { freq = _maxFreq; }
|
||||
else if (freq < _minFreq) { freq = _minFreq; }
|
||||
}
|
||||
|
||||
inline void clampPhase() {
|
||||
while (phase > _maxPhase) { phase -= phaseDelta; }
|
||||
while (phase < _minPhase) { phase += phaseDelta; }
|
||||
}
|
||||
|
||||
T _alpha;
|
||||
T _beta;
|
||||
T _minPhase;
|
||||
T _maxPhase;
|
||||
T _minFreq;
|
||||
T _maxFreq;
|
||||
T phaseDelta;
|
||||
};
|
||||
}
|
||||
90
core/src/dsp/loop/pll.h
Normal file
90
core/src/dsp/loop/pll.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
#include "../math/normalize_phase.h"
|
||||
#include "../math/phasor.h"
|
||||
#include "phase_control_loop.h"
|
||||
|
||||
namespace dsp::loop {
|
||||
class PLL : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t>;
|
||||
public:
|
||||
PLL() {}
|
||||
|
||||
PLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) { init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq); }
|
||||
|
||||
void init(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
|
||||
_initPhase = initPhase;
|
||||
_initFreq = initFreq;
|
||||
|
||||
// Init phase control loop
|
||||
float alpha, beta;
|
||||
PhaseControlLoop<float>::criticallyDamped(bandwidth, alpha, beta);
|
||||
pcl.init(alpha, beta, initPhase, -FL_M_PI, FL_M_PI, initFreq, minFreq, maxFreq);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setBandwidth(double bandwidth) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
float alpha, beta;
|
||||
PhaseControlLoop<float>::criticallyDamped(bandwidth, alpha, beta);
|
||||
pcl.setCoefficients(alpha, beta);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void setInitialPhase(double initPhase) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_initPhase = initPhase;
|
||||
}
|
||||
|
||||
void setInitialFreq(double initFreq) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
_initFreq = initFreq;
|
||||
}
|
||||
|
||||
void setFrequencyLimits(double minFreq, double maxFreq) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
pcl.setFreqLimits(minFreq, maxFreq);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
pcl.phase = _initPhase;
|
||||
pcl.freq = _initFreq;
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
virtual inline int process(int count, complex_t* in, complex_t* out) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = math::phasor(pcl.phase);
|
||||
pcl.advance(math::normalizePhase(in[i].phase() - pcl.phase));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
protected:
|
||||
PhaseControlLoop<float> pcl;
|
||||
float _initPhase;
|
||||
float _initFreq;
|
||||
complex_t lastVCO = { 1.0f, 0.0f };
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <volk/volk.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class Add : public generic_block<Add<T>> {
|
||||
public:
|
||||
Add() {}
|
||||
|
||||
Add(stream<T>* a, stream<T>* b) { init(a, b); }
|
||||
|
||||
void init(stream<T>* a, stream<T>* b) {
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Add<T>>::registerInput(a);
|
||||
generic_block<Add<T>>::registerInput(b);
|
||||
generic_block<Add<T>>::registerOutput(&out);
|
||||
generic_block<Add<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInputs(stream<T>* a, stream<T>* b) {
|
||||
assert(generic_block<Add<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Add<T>>::ctrlMtx);
|
||||
generic_block<Add<T>>::tempStop();
|
||||
generic_block<Add<T>>::unregisterInput(_a);
|
||||
generic_block<Add<T>>::unregisterInput(_b);
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Add<T>>::registerInput(_a);
|
||||
generic_block<Add<T>>::registerInput(_b);
|
||||
generic_block<Add<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInputA(stream<T>* a) {
|
||||
assert(generic_block<Add<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Add<T>>::ctrlMtx);
|
||||
generic_block<Add<T>>::tempStop();
|
||||
generic_block<Add<T>>::unregisterInput(_a);
|
||||
_a = a;
|
||||
generic_block<Add<T>>::registerInput(_a);
|
||||
generic_block<Add<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInputB(stream<T>* b) {
|
||||
assert(generic_block<Add<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Add<T>>::ctrlMtx);
|
||||
generic_block<Add<T>>::tempStop();
|
||||
generic_block<Add<T>>::unregisterInput(_b);
|
||||
_b = b;
|
||||
generic_block<Add<T>>::registerInput(_b);
|
||||
generic_block<Add<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int a_count = _a->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
int b_count = _b->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
// TODO: Switch this out for volk_32fc_x2_add_32fc with a check for old volk versions that don't have it (eg. Ubuntu 18.04 that has volk 1.3)
|
||||
volk_32f_x2_add_32f((float*)out.writeBuf, (float*)_a->readBuf, (float*)_b->readBuf, a_count * 2);
|
||||
}
|
||||
else {
|
||||
volk_32f_x2_add_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
|
||||
}
|
||||
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
if (!out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
stream<T>* _a;
|
||||
stream<T>* _b;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class Subtract : public generic_block<Subtract<T>> {
|
||||
public:
|
||||
Subtract() {}
|
||||
|
||||
Subtract(stream<T>* a, stream<T>* b) { init(a, b); }
|
||||
|
||||
void init(stream<T>* a, stream<T>* b) {
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Subtract<T>>::registerInput(a);
|
||||
generic_block<Subtract<T>>::registerInput(b);
|
||||
generic_block<Subtract<T>>::registerOutput(&out);
|
||||
generic_block<Subtract<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInputs(stream<T>* a, stream<T>* b) {
|
||||
assert(generic_block<Subtract<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Subtract<T>>::ctrlMtx);
|
||||
generic_block<Subtract<T>>::tempStop();
|
||||
generic_block<Subtract<T>>::unregisterInput(_a);
|
||||
generic_block<Subtract<T>>::unregisterInput(_b);
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Subtract<T>>::registerInput(_a);
|
||||
generic_block<Subtract<T>>::registerInput(_b);
|
||||
generic_block<Subtract<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInputA(stream<T>* a) {
|
||||
assert(generic_block<Subtract<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Subtract<T>>::ctrlMtx);
|
||||
generic_block<Subtract<T>>::tempStop();
|
||||
generic_block<Subtract<T>>::unregisterInput(_a);
|
||||
_a = a;
|
||||
generic_block<Subtract<T>>::registerInput(_a);
|
||||
generic_block<Subtract<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInputB(stream<T>* b) {
|
||||
assert(generic_block<Subtract<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Subtract<T>>::ctrlMtx);
|
||||
generic_block<Subtract<T>>::tempStop();
|
||||
generic_block<Subtract<T>>::unregisterInput(_b);
|
||||
_b = b;
|
||||
generic_block<Subtract<T>>::registerInput(_b);
|
||||
generic_block<Subtract<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int a_count = _a->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
int b_count = _b->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
volk_32f_x2_subtract_32f((float*)out.writeBuf, (float*)_a->readBuf, (float*)_b->readBuf, a_count * 2);
|
||||
}
|
||||
else {
|
||||
volk_32f_x2_subtract_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
|
||||
}
|
||||
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
if (!out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
stream<T>* _a;
|
||||
stream<T>* _b;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class Multiply : public generic_block<Multiply<T>> {
|
||||
public:
|
||||
Multiply() {}
|
||||
|
||||
Multiply(stream<T>* a, stream<T>* b) { init(a, b); }
|
||||
|
||||
void init(stream<T>* a, stream<T>* b) {
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Multiply<T>>::registerInput(a);
|
||||
generic_block<Multiply<T>>::registerInput(b);
|
||||
generic_block<Multiply<T>>::registerOutput(&out);
|
||||
generic_block<Multiply<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInputs(stream<T>* a, stream<T>* b) {
|
||||
assert(generic_block<Multiply<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Multiply<T>>::ctrlMtx);
|
||||
generic_block<Multiply<T>>::tempStop();
|
||||
generic_block<Multiply<T>>::unregisterInput(_a);
|
||||
generic_block<Multiply<T>>::unregisterInput(_b);
|
||||
_a = a;
|
||||
_b = b;
|
||||
generic_block<Multiply<T>>::registerInput(_a);
|
||||
generic_block<Multiply<T>>::registerInput(_b);
|
||||
generic_block<Multiply<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInputA(stream<T>* a) {
|
||||
assert(generic_block<Multiply<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Multiply<T>>::ctrlMtx);
|
||||
generic_block<Multiply<T>>::tempStop();
|
||||
generic_block<Multiply<T>>::unregisterInput(_a);
|
||||
_a = a;
|
||||
generic_block<Multiply<T>>::registerInput(_a);
|
||||
generic_block<Multiply<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInputB(stream<T>* b) {
|
||||
assert(generic_block<Multiply<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Multiply<T>>::ctrlMtx);
|
||||
generic_block<Multiply<T>>::tempStop();
|
||||
generic_block<Multiply<T>>::unregisterInput(_b);
|
||||
_b = b;
|
||||
generic_block<Multiply<T>>::registerInput(_b);
|
||||
generic_block<Multiply<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int a_count = _a->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
int b_count = _b->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
volk_32fc_x2_multiply_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_a->readBuf, (lv_32fc_t*)_b->readBuf, a_count);
|
||||
}
|
||||
else {
|
||||
volk_32f_x2_multiply_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
|
||||
}
|
||||
|
||||
_a->flush();
|
||||
_b->flush();
|
||||
if (!out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
stream<T>* _a;
|
||||
stream<T>* _b;
|
||||
|
||||
};
|
||||
}
|
||||
42
core/src/dsp/math/add.h
Normal file
42
core/src/dsp/math/add.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include "../operator.h"
|
||||
|
||||
namespace dsp::math {
|
||||
template <class T>
|
||||
class Add : public Operator<T, T, T> {
|
||||
using base_type = Operator<T, T, T>;
|
||||
public:
|
||||
Add() {}
|
||||
|
||||
Add(stream<T>* a, stream<T>* b) { base_type::init(a, b); }
|
||||
|
||||
static inline int process(int count, const T* a, const T*b, T* out) {
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
volk_32f_x2_add_32f((float*)out, (float*)a, (float*)b, count * 2);
|
||||
}
|
||||
else {
|
||||
volk_32f_x2_add_32f(out, a, b, count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int a_count = base_type::_a->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
int b_count = base_type::_b->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
base_type::_a->flush();
|
||||
base_type::_b->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
process(a_count, base_type::_a->readBuf, base_type::_b->readBuf, base_type::out.writeBuf);
|
||||
|
||||
base_type::_a->flush();
|
||||
base_type::_b->flush();
|
||||
if (!base_type::out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
};
|
||||
}
|
||||
28
core/src/dsp/math/conjugate.h
Normal file
28
core/src/dsp/math/conjugate.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::math {
|
||||
class Conjugate : public Processor<complex_t, complex_t> {
|
||||
using base_type = Processor<complex_t, complex_t>;
|
||||
public:
|
||||
Conjugate() {}
|
||||
|
||||
Conjugate(stream<complex_t>* in) { base_type::init(in); }
|
||||
|
||||
inline static int process(int count, const complex_t* in, complex_t* out) {
|
||||
volk_32fc_conjugate_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
virtual 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
7
core/src/dsp/math/constants.h
Normal file
7
core/src/dsp/math/constants.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define DB_M_PI 3.14159265358979323846
|
||||
#define FL_M_PI 3.1415926535f
|
||||
|
||||
#define DB_M_SQRT2 1.4142135623730951
|
||||
#define FL_M_SQRT2 1.4142135623f
|
||||
76
core/src/dsp/math/delay.h
Normal file
76
core/src/dsp/math/delay.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include "../processor.h"
|
||||
|
||||
namespace dsp::math {
|
||||
template<class T>
|
||||
class Delay : public Processor<T, T> {
|
||||
using base_type = Processor<T, T>;
|
||||
public:
|
||||
Delay() {}
|
||||
|
||||
Delay(stream<T>* in, int delay) { init(in, delay); }
|
||||
|
||||
~Delay() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
buffer::free(buffer);
|
||||
}
|
||||
|
||||
void init(stream<T>* in, int delay) {
|
||||
_delay = delay;
|
||||
|
||||
buffer = buffer::alloc<T>(STREAM_BUFFER_SIZE + 64000);
|
||||
bufStart = &buffer[_delay];
|
||||
buffer::clear(buffer, _delay);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
void setDelay(int delay) {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
_delay = delay;
|
||||
bufStart = &buffer[_delay];
|
||||
reset();
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
assert(base_type::_block_init);
|
||||
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
|
||||
base_type::tempStop();
|
||||
buffer::clear(buffer, _delay);
|
||||
base_type::tempStart();
|
||||
}
|
||||
|
||||
inline int process(int count, const T* in, T* out) {
|
||||
// Copy data into delay buffer
|
||||
memcpy(bufStart, in, count * sizeof(T));
|
||||
|
||||
// Copy data out of the delay buffer
|
||||
memcpy(out, buffer, count * sizeof(T));
|
||||
|
||||
// Move end of the delay buffer to the front
|
||||
memmove(buffer, &buffer[count], _delay * sizeof(T));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
virtual 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:
|
||||
int _delay;
|
||||
T* buffer;
|
||||
T* bufStart;
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user